Files
development/www/lib/CoreLibs/Basic.php

2828 lines
98 KiB
PHP

<?php declare(strict_types=1);
/*********************************************************************
* AUTHOR: Clemens Schwaighofer
* CREATED: 2003/03/24
* VERSION: 2.0.0
* RELEASED LICENSE: GNU GPL 3
* SHORT DESCRIPTION:
* 2018/3/23, the whole class system is transformed to namespaces
* also all internal class calls are converted to camel case
*
* basic class start class for ALL clases, holds basic vars, infos, methods, etc
*
* PUBLIC VARIABLES
*
* These are if there is any debug to print out at all at the end
* debug_output_all - general yes no
* It's recommended to use the method "debug_for" to turn on of the array vars
* debug_output - turn on for one level (Array)
* debug_output_not - turn off for one level (array)
*
* Print out the debug at thend of the html
* echo_output_all
* echo_output
* echo_output_not
*
* Write debug to file
* print_output_all
* print_output
* print_output_not
*
* PRIVATE VARIABLES
* error_msg -> array that holds all the error messages, should not be written from outside, use debug method
* error_id
* error_string
*
* PUBLIC METHODS
* debug -> calls with "level", "string" and flag to turn off (0) the newline at the end
* debugFor -> sets debug on/off for a type (error, echo, print) for a certain level
* printErrorMsg -> prints out the error message, optional parameter is a header prefix
* fdebug -> prints line directly to debug_file.log in tmp
*
* printTime -> prints time + microtime, optional flag to turn off (0) microtime printout
* info -> info about that class
* runningTime -> prints out the time of start/end (automatically called on created and error printout
* checked -> returnes checked or selected for var & array
* magicLinks -> parses text and makes <a href> out of links
* getPageName -> get the filename of the current page
* arraySearchRecursive -> search for a value/key combination in an array of arrays
* byteStringFormat -> format bytes into KB, MB, GB, ...
* timeStringFormat -> format a timestamp (seconds) into days, months, ... also with ms
* stringToTime -> reverste a TimeStringFormat to a timestamp
* genAssocArray -> generactes a new associativ array from an existing array
* checkDate -> checks if a date is valid
* compareDate -> compares two dates. -1 if the first is smaller, 0 if they are equal, 1 if the first is bigger
* compareDateTime -> compares two dates with time. -1 if the first is smaller, 0 if they are equal, 1 if the first is bigger
* __crc32b -> behaves like the hash("crc32b") in php < 5.2.8. this function will flip the hash like it was (wrong)
* before if a new php version is found
* crypt* -> encrypt and decrypt login string data, used by Login class
* setFormToken/validateFormToken -> form protection with token
*
* PRIVATE METHODS
* fdebug_fp -> opens and closes file, called from fdebug method
* write_error_msg -> writes error msg to file if requested
*
* HISTORY:
* 2010/12/24 (cs) add crypt classes with auto detect what crypt we can use, add php version check class
* 2008/08/07 (cs) fixed strange DEBUG_ALL on off behavour. data was written even thought DBEUG_ALL was off. now no debug logging is done at all if DEBUG_ALL is off
* 2007/11/13 (cs) add Comparedate function
* 2007/11/05 (cs) added GenAssocArray and CheckDate functions
* 2007/10/10 (cs) magic links function can use http:///path as a local prefix. blank target is removed & http:// also
* 2006/03/09 (cs) added Byte/TimeStringFormat functions
* 2006/02/21 (cs) fix various problems with the mime magic function: || not always working, fix prefix replacement, etc
* 2006/02/09 (cs) added _mb_mime_encode function, replacement for php internal one
* 2005/07/12 (cs) added some small stylesheet defs to debug output
* 2005/06/24 (cs) made the check selected/checked function way easier
* 2005/06/24 (cs) added a function to wrap around print_r for html formatted array print
* 2005/06/21 (cs) made the error_msg file writing immediatly after something is written with debug method
* 2005/06/20 (cs) added a quick to file write function, removed the mobile detect code
* 2005/06/20 (cs) test debug method, add surpress of <br> in debug output
* 2005/06/17 (cs) error_msg is an array, to put in various levels of error reporting
* 2005/04/06 (cs) added filename for error page when print to file
* 2005/05/31 (cs) added file printout of errors
* 2005/03/01 (cs) set a global regex for checking the email
* 2005/01/27 (cs) updated checked, haystack can be valur or array
* 2004/11/16 (cs) removed mobile detection here
* 2004/11/15 (cs) error_msg is no longer echoed, but returned
* 2004/11/15 (cs) added new functions: checked, magic_links, get_page_name
* 2004/08/06 (cs) bug with $_GLOBALS, should be $GLOBALS
* 2004/07/15 (cs) added print_error_msg method, updated to new schema
* 2003-06-09: added "detect_mobile" class for japanese mobile phone
* detection
* 2003-03-24: start of stub/basic class
*********************************************************************/
namespace CoreLibs;
/** Basic core class declaration */
class Basic
{
// define check vars for the flags we can have
const CLASS_STRICT_MODE = 1;
const CLASS_OFF_COMPATIBLE_MODE = 2;
// control vars
/** @var bool compatible mode sets variable even if it is not defined */
private $set_compatible = true;
/** @var bool strict mode throws an error if the variable is not defined */
private $set_strict_mode = false;
// page and host name
public $page_name;
public $host_name;
public $host_port;
// internal error reporting vars
protected $error_id; // error ID for errors in classes
protected $error_msg = array(); // the "connection" to the outside errors
// debug output prefix
public $error_msg_prefix = ''; // prefix to the error string (the class name)
// debug flags
public $debug_output; // if this is true, show debug on desconstructor
public $debug_output_not;
public $debug_output_all;
public $echo_output; // errors: echo out, default is 1
public $echo_output_not;
public $echo_output_all;
public $print_output; // errors: print to file, default is 0
public $print_output_not;
public $print_output_all;
// debug flags/settings
public $debug_fp; // filepointer for writing to file
public $debug_filename = 'debug_file.log'; // where to write output
public $hash_algo = 'crc32b'; // the hash algo used for the internal debug uid
public $running_uid = ''; // unique ID set on class init and used in logging as prefix
// log file name
private $log_file_name_ext = 'log'; // use this for date rotate
public $log_max_filesize = 0; // set in kilobytes
private $log_print_file = 'error_msg##LOGID####LEVEL####CLASS####PAGENAME####DATE##';
private $log_file_unique_id; // a unique ID set only once for call derived from this class
public $log_print_file_date = 1; // if set add Y-m-d and do automatic daily rotation
private $log_file_id = ''; // a alphanumeric name that has to be set as global definition
public $log_per_level = false; // set, it will split per level (first parameter in debug call)
public $log_per_class = false; // set, will split log per class
public $log_per_page = false; // set, will split log per called file
public $log_per_run = false; // create a new log file per run (time stamp + unique ID)
// run time messurements
private $starttime; // start time if time debug is used
private $endtime; // end time if time debug is used
public $runningtime_string; // the running time as a string with info text
private $hr_starttime; // start time
private $hr_endtime; // end time
private $hr_runtime = 0; // run time
// script running time
private $script_starttime;
// email valid checks
public $email_regex_check = array();
public $mobile_email_type = array();
public $mobile_email_type_short = array();
public $email_regex; // regex var for email check
public $keitai_email_regex; // regex var for email check
// data path for files
public $data_path = array();
// error char for the char conver
public $mbErrorChar;
// [!!! DEPRECATED !!!] crypt saslt prefix
public $cryptSaltPrefix = '';
public $cryptSaltSuffix = '';
public $cryptIterationCost = 7; // this is for staying backwards compatible with the old ones
public $cryptSaltSize = 22; // default 22 chars for blowfish, 2 for STD DES, 8 for MD5,
// new better password management
protected $password_options = array();
// session name
private $session_name = '';
private $session_id = '';
// key generation
private $key_range = array();
private $one_key_length;
private $key_length;
private $max_key_length = 256; // max allowed length
// form token (used for form validation)
private $form_token = '';
// ajax flag
protected $ajax_page_flag = false;
// METHOD: __construct
// PARAMS: set_control_flag [current sets set/get var errors]
// RETURN: none
// DESC : class constructor
/**
* main Basic constructor to init and check base settings
* @param int $set_control_flag 0/1/2/3 to set internal class parameter check
*/
public function __construct(int $set_control_flag = 0)
{
// init flags
$this->__setControlFlag($set_control_flag);
// set per run UID for logging
$this->running_uid = hash($this->hash_algo, uniqid((string)rand(), true));
// running time start for script
$this->script_starttime = microtime(true);
// before we start any work, we should check that all MUST constants are defined
$abort = false;
foreach (array(
'DS', 'DIR', 'BASE', 'ROOT', 'LIB', 'INCLUDES', 'LAYOUT', 'PICTURES', 'FLASH', 'VIDEOS', 'DOCUMENTS', 'PDFS', 'BINARIES', 'ICONS',
'UPLOADS', 'CSV', 'JS', 'CSS', 'TABLE_ARRAYS', 'SMARTY', 'LANG', 'CACHE', 'TMP', 'LOG', 'TEMPLATES', 'TEMPLATES_C',
'DEFAULT_LANG', 'DEFAULT_ENCODING', 'DEFAULT_HASH',
'DEFAULT_ACL_LEVEL', 'LOGOUT_TARGET', 'PASSWORD_CHANGE', 'AJAX_REQUEST_TYPE', 'USE_PROTOTYPE', 'USE_SCRIPTACULOUS', 'USE_JQUERY',
'PAGE_WIDTH', 'MASTER_TEMPLATE_NAME', 'PUBLIC_SCHEMA', 'TEST_SCHEMA', 'DEV_SCHEMA', 'LIVE_SCHEMA', 'DB_CONFIG_NAME', 'DB_CONFIG', 'TARGET', 'DEBUG', 'SHOW_ALL_ERRORS'
) as $constant) {
if (!defined($constant)) {
echo "Constant $constant misssing<br>";
$abort = true;
}
}
if ($abort === true) {
die('Core Constant missing. Check config file.');
}
// set ajax page flag based on the AJAX_PAGE varaibles
// convert to true/false so if AJAX_PAGE is 0 or false it is
// always boolean false
$this->ajax_page_flag = isset($GLOBALS['AJAX_PAGE']) && $GLOBALS['AJAX_PAGE'] ? true : false;
// set the page name
$this->page_name = $this->getPageName();
$this->host_name = $this->getHostName();
// init the log file id
// * GLOBALS
// * CONSTANT
// can be overridden with basicSetLogFileId
if (isset($GLOBALS['LOG_FILE_ID'])) {
$this->basicSetLogId($GLOBALS['LOG_FILE_ID']);
} elseif (defined('LOG_FILE_ID')) {
$this->basicSetLogId(LOG_FILE_ID);
}
// set the paths matching to the valid file types
$this->data_path = array(
'P' => PICTURES,
'F' => FLASH,
'V' => VIDEOS,
'D' => DOCUMENTS,
'A' => PDFS,
'B' => BINARIES
);
// if given via parameters, only for all
$this->debug_output_all = false;
$this->echo_output_all = false;
$this->print_output_all = false;
// globals overrule given settings, for one (array), eg $ECHO['db'] = 1;
if (isset($GLOBALS['DEBUG']) && is_array($GLOBALS['DEBUG'])) {
$this->debug_output = $GLOBALS['DEBUG'];
}
if (isset($GLOBALS['ECHO']) && is_array($GLOBALS['ECHO'])) {
$this->echo_output = $GLOBALS['ECHO'];
}
if (isset($GLOBALS['PRINT']) && is_array($GLOBALS['PRINT'])) {
$this->print_output = $GLOBALS['PRINT'];
}
// exclude these ones from output
if (isset($GLOBALS['DEBUG_NOT']) && is_array($GLOBALS['DEBUG_NOT'])) {
$this->debug_output_not = $GLOBALS['DEBUG_NOT'];
}
if (isset($GLOBALS['ECHO_NOT']) && is_array($GLOBALS['ECHO_NOT'])) {
$this->echo_output_not = $GLOBALS['ECHO_NOT'];
}
if (isset($GLOBALS['PRINT_NOT']) && is_array($GLOBALS['PRINT_NOT'])) {
$this->print_output_not = $GLOBALS['PRINT_NOT'];
}
// all overrule
if (isset($GLOBALS['DEBUG_ALL'])) {
$this->debug_output_all = $GLOBALS['DEBUG_ALL'];
}
if (isset($GLOBALS['ECHO_ALL'])) {
$this->echo_output_all = $GLOBALS['ECHO_ALL'];
}
if (isset($GLOBALS['PRINT_ALL'])) {
$this->print_output_all = $GLOBALS['PRINT_ALL'];
}
// GLOBAL rules for log writing
if (isset($GLOBALS['LOG_PRINT_FILE_DATE'])) {
$this->log_print_file_date = $GLOBALS['LOG_PRINT_FILE_DATE'];
}
if (isset($GLOBALS['LOG_PER_LEVEL'])) {
$this->log_per_level = $GLOBALS['LOG_PER_LEVEL'];
}
if (isset($GLOBALS['LOG_PER_CLASS'])) {
$this->log_per_class = $GLOBALS['LOG_PER_CLASS'];
}
if (isset($GLOBALS['LOG_PER_PAGE'])) {
$this->log_per_page = $GLOBALS['LOG_PER_PAGE'];
}
if (isset($GLOBALS['LOG_PER_RUN'])) {
$this->log_per_run = $GLOBALS['LOG_PER_RUN'];
}
// set the regex for checking emails
$this->email_regex = "^[A-Za-z0-9!#$%&'*+\-\/=?^_`{|}~][A-Za-z0-9!#$%:\(\)&'*+\-\/=?^_`{|}~\.]{0,63}@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]{1,})*\.([a-zA-Z]{2,}){1}$";
// this is for error check parts in where the email regex failed
$this->email_regex_check = array(
1 => "@(.*)@(.*)", // double @
2 => "^[A-Za-z0-9!#$%&'*+-\/=?^_`{|}~][A-Za-z0-9!#$%:\(\)&'*+-\/=?^_`{|}~\.]{0,63}@", // wrong part before @
3 => "@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]{1,})*\.([a-zA-Z]{2,}){1}$", // wrong part after @
4 => "@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]{1,})*\.", // wrong domain name part
5 => "\.([a-zA-Z]{2,6}){1}$", // wrong top level part
6 => "@(.*)\.{2,}", // double .. in domain name part
7 => "@.*\.$" // ends with a dot, top level, domain missing
);
// the array with the mobile types that are valid
$this->mobile_email_type = array(
'.*@docomo\.ne\.jp$' => 'keitai_docomo',
'.*@([a-z0-9]{2}\.)?ezweb\.ne\.jp$' => 'keitai_kddi_ezweb', # correct are a[2-4], b2, c[1-9], e[2-9], h[2-4], t[1-9]
'.*@(ez[a-j]{1}\.)?ido\.ne\.jp$' => 'keitai_kddi_ido', # ez[a-j] or nothing
'.*@([a-z]{2}\.)?sky\.tu-ka\.ne\.jp$' => 'keitai_kddi_tu-ka', # (sky group)
'.*@([a-z]{2}\.)?sky\.tk[kc]{1}\.ne\.jp$' => 'keitai_kddi_sky', # (sky group) [tkk,tkc only]
'.*@([a-z]{2}\.)?sky\.dtg\.ne\.jp$' => 'keitai_kddi_dtg', # dtg (sky group)
'.*@[tkdhcrnsq]{1}\.vodafone\.ne\.jp$' => 'keitai_softbank_vodafone', # old vodafone [t,k,d,h,c,r,n,s,q]
'.*@jp-[dhtkrsnqc]{1}\.ne\.jp$' => 'keitai_softbank_j-phone', # very old j-phone (pre vodafone) [d,h,t,k,r,s,n,q,c]
'.*@([dhtcrknsq]{1}\.)?softbank\.ne\.jp$' => 'keitai_softbank', # add i for iphone also as keitai, others similar to the vodafone group
'.*@i{1}\.softbank(\.ne)?\.jp$' => 'smartphone_softbank_iphone', # add iPhone also as keitai and not as pc
'.*@disney\.ne\.jp$' => 'keitai_softbank_disney', # (kids)
'.*@willcom\.ne\.jp$' => 'keitai_willcom',
'.*@willcom\.com$' => 'keitai_willcom', # new for pdx.ne.jp address
'.*@wcm\.ne\.jp$' => 'keitai_willcom', # old willcom wcm.ne.jp
'.*@pdx\.ne\.jp$' => 'keitai_willcom_pdx', # old pdx address for willcom
'.*@bandai\.jp$' => 'keitai_willcom_bandai', # willcom paipo! (kids)
'.*@pipopa\.ne\.jp$' => 'keitai_willcom_pipopa', # willcom paipo! (kids)
'.*@([a-z0-9]{2,4}\.)?pdx\.ne\.jp$' => 'keitai_willcom_pdx', # actually only di,dj,dk,wm -> all others are "wrong", but none also allowed?
'.*@ymobile([1]{1})?\.ne\.jp$' => 'keitai_willcom_ymobile', # ymobile, ymobile1 techincally not willcom, but I group them there (softbank sub)
'.*@y-mobile\.ne\.jp$' => 'keitai_willcom_ymobile', # y-mobile techincally not willcom, but I group them there (softbank sub)
'.*@emnet\.ne\.jp$' => 'keitai_willcom_emnet', # e-mobile, group will willcom
'.*@emobile\.ne\.jp$' => 'keitai_willcom_emnet', # e-mobile, group will willcom
'.*@emobile-s\.ne\.jp$' => 'keitai_willcom_emnet' # e-mobile, group will willcom
);
// short list for mobile email types
$this->mobile_email_type_short = array(
'keitai_docomo' => 'docomo',
'keitai_kddi_ezweb' => 'kddi',
'keitai_kddi' => 'kddi',
'keitai_kddi_tu-ka' => 'kddi',
'keitai_kddi_sky' => 'kddi',
'keitai_softbank' => 'softbank',
'smartphone_softbank_iphone' => 'iphone',
'keitai_softbank_disney' => 'softbank',
'keitai_softbank_vodafone' => 'softbank',
'keitai_softbank_j-phone' => 'softbank',
'keitai_willcom' => 'willcom',
'keitai_willcom_pdx' => 'willcom',
'keitai_willcom_bandai' => 'willcom',
'keitai_willcom_pipopa' => 'willcom',
'keitai_willcom_ymobile' => 'willcom',
'keitai_willcom_emnet' => 'willcom',
'pc_html' => 'pc',
// old sets -> to be removed later
'docomo' => 'docomo',
'kddi_ezweb' => 'kddi',
'kddi' => 'kddi',
'kddi_tu-ka' => 'kddi',
'kddi_sky' => 'kddi',
'softbank' => 'softbank',
'keitai_softbank_iphone' => 'iphone',
'softbank_iphone' => 'iphone',
'softbank_disney' => 'softbank',
'softbank_vodafone' => 'softbank',
'softbank_j-phone' => 'softbank',
'willcom' => 'willcom',
'willcom_pdx' => 'willcom',
'willcom_bandai' => 'willcom',
'willcom_pipopa' => 'willcom',
'willcom_ymobile' => 'willcom',
'willcom_emnet' => 'willcom',
'pc' => 'pc'
);
// initial the session if there is no session running already
if (!session_id()) {
// check if we have an external session name given, else skip this step
if (defined('SET_SESSION_NAME')) {
// set the session name for possible later check
$this->session_name = SET_SESSION_NAME;
}
// override with global if set
if (isset($GLOBALS['SET_SESSION_NAME'])) {
$this->session_name = $GLOBALS['SET_SESSION_NAME'];
}
// if set, set special session name
if ($this->session_name) {
session_name($this->session_name);
}
// start session
session_start();
// set internal session id, we can use that later for protection check
$this->session_id = session_id();
}
// new better password init
$this->passwordInit();
// key generation init
$this->initRandomKeyData();
}
// METHOD: __destruct
// PARAMS: none
// RETURN: if debug is on, return error data
// DESC : basic deconstructor (should be called from all deconstructors in higher classes)
// writes out $error_msg to global var
public function __destruct()
{
// this has to be changed, not returned here, this is the last class to close
// return $this->error_msg;
// close open file handles
// $this->fdebugFP('c');
}
// *************************************************************
// INTERAL VARIABLE ERROR HANDLER
// *************************************************************
/**
* sets internal control flags for class variable check
* 0 -> turn of all, works like default php class
* CLASS_STRICT_MODE: 1 -> if set throws error on unset class variable
* CLASS_OFF_COMPATIBLE_MODE: 2 -> if set turns of auto set for unset variables
* 3 -> sets error on unset and does not set variable (strict)
* @param int $set_control_flag control flag as 0/1/2/3
* @return void
*/
private function __setControlFlag(int $set_control_flag): void
{
// is there either a constant or global set to override the control flag
if (defined('CLASS_VARIABLE_ERROR_MODE')) {
$set_control_flag = CLASS_VARIABLE_ERROR_MODE;
}
if (isset($GLOBALS['CLASS_VARIABLE_ERROR_MODE'])) {
$set_control_flag = $GLOBALS['CLASS_VARIABLE_ERROR_MODE'];
}
// bit wise check of int and set
if ($set_control_flag & self::CLASS_OFF_COMPATIBLE_MODE) {
$this->set_compatible = false;
} else {
$this->set_compatible = true;
}
if ($set_control_flag & self::CLASS_STRICT_MODE) {
$this->set_strict_mode = true;
} else {
$this->set_strict_mode = false;
}
}
/**
* if strict mode is set, throws an error if the class variable is not set
* if compatible mode is set, also auto sets variable even if not declared
* default is strict mode false and compatible mode on
* @param mixed $name class variable name
* @return void
*/
public function __set($name, $value): void
{
if ($this->set_strict_mode === true && !property_exists($this, $name)) {
trigger_error('Undefined property via __set(): '.$name, E_USER_NOTICE);
}
// use this for fallback as to work like before to set unset
if ($this->set_compatible === true) {
$this->{$name} = $value;
}
}
/**
* if strict mode is set, throws an error if the class variable is not set
* default is strict mode false
* @param mixed $name class variable name
* @return mixed return set variable content
*/
public function &__get($name)
{
if ($this->set_strict_mode === true && !property_exists($this, $name)) {
trigger_error('Undefined property via __get(): '.$name, E_USER_NOTICE);
}
// on set return
if (property_exists($this, $name)) {
return $this->$name;
} elseif ($this->set_compatible === true && !property_exists($this, $name)) {
// if it is not set, and we are in compatible mode we need to init.
// This is so that $class->array['key'] = 'bar'; works
$this->{$name} = null;
return $this->$name;
}
}
// *************************************************************
// GENERAL METHODS
// *************************************************************
/**
* sets the internal log file prefix id
* string must be a alphanumeric string
* if non valid string is given it returns the previous set one only
* @param string $string log file id string value
* @return string returns the set log file id string
*/
public function basicSetLogId(string $string): string
{
if (preg_match("/^\w+$/", $string)) {
$this->log_file_id = $string;
}
return $this->log_file_id;
}
// ****** DEBUG/ERROR FUNCTIONS ******
/**
* for messure run time between two calls for this method
* uses the hrtime() for running time
* first call sets start time and returns 0,
* second call sets end time and returns the run time
* the out_time parameter can be:
* n/ns (nano), y/ys (micro), m/ms (milli), s
* default is milliseconds
* @param string $out_time set return time adjustment calculation
* @return float running time without out_time suffix
*/
public function hrRunningTime(string $out_time = 'ms'): float
{
// if start time not set, set start time
if (!$this->hr_starttime) {
$this->hr_starttime = hrtime(true);
$this->hr_runtime = 0;
} else {
$this->hr_endtime = hrtime(true);
$this->hr_runtime = $this->hr_endtime - $this->hr_starttime;
// reset start and end time past run
$this->hr_starttime = 0;
$this->hr_endtime = 0;
}
// init divisor, just in case
$divisor = 1;
// check through valid out time, if nothing matches default to ms
switch ($out_time) {
case 'n':
case 'ns':
$divisor = 1;
break;
case 'y':
case 'ys':
$divisor = 1000;
break;
case 'm':
case 'ms':
$divisor = 1000000;
break;
case 's':
$divisor = 1000000000;
break;
// default is ms
default:
$divisor = 1000000;
break;
}
// return the run time in converted format
$this->hr_runtime /= $divisor;
return $this->hr_runtime;
}
/**
* prints start or end time in text format. On first call sets start time
* on second call it sends the end time and then also prints the running time
* Sets the internal runningtime_string variable with Start/End/Run time string
* NOTE: for pure running time check it is recommended to use hrRunningTime method
* @param bool|boolean $simple if true prints HTML strings, default text only
* @return float running time as float number
*/
public function runningTime(bool $simple = false): float
{
list($micro, $timestamp) = explode(' ', microtime());
$running_time = 0;
// set start & end time
if (!$this->starttime) {
// always reset running time string on first call
$this->runningtime_string = '';
$this->starttime = ((float)$micro + (float)$timestamp);
$this->runningtime_string .= $simple ? 'Start: ' : "<b>Started at</b>: ";
} else {
$this->endtime = ((float)$micro + (float)$timestamp);
$this->runningtime_string .= $simple ? 'End: ' : "<b>Stopped at</b>: ";
}
$this->runningtime_string .= date('Y-m-d H:i:s', (int)$timestamp);
$this->runningtime_string .= ' '.$micro.($simple ? ', ' : '<br>');
// if both are set
if ($this->starttime && $this->endtime) {
$running_time = $this->endtime - $this->starttime;
$this->runningtime_string .= ($simple ? 'Run: ' : "<b>Script running time</b>: ").$running_time." s";
// reset start & end time after run
$this->starttime = 0;
$this->endtime = 0;
}
return $running_time;
}
/**
* wrapper around microtime function to print out y-m-d h:i:s.ms
* @param int $set_microtime -1 to set micro time, 0 for none, positive for rounding
* @return string formated datetime string with microtime
*/
public static function printTime(int $set_microtime = -1): string
{
list($microtime, $timestamp) = explode(' ', microtime());
$string = date("Y-m-d H:i:s", (int)$timestamp);
// if microtime flag is -1 no round, if 0, no microtime, if >= 1, round that size
if ($set_microtime == -1) {
$string .= substr($microtime, 1);
} elseif ($set_microtime >= 1) {
// in round case we run this through number format to always get the same amount of digits
$string .= substr(number_format(round((float)$microtime, $set_microtime), $set_microtime), 1);
}
return $string;
}
/**
* writes a string to a file immediatly, for fast debug output
* @param string $string string to write to the file
* @param boolean $enter default true, if set adds a linebreak \n at the end
* @return void has no return
*/
public function fdebug(string $string, bool $enter = true): void
{
if ($this->debug_filename) {
$this->fdebugFP();
if ($enter === true) {
$string .= "\n";
}
$string = "[".$this->printTime()."] [".$this->getPageName(2)."] - ".$string;
fwrite($this->debug_fp, $string);
$this->fdebugFP();
}
}
/**
* if no debug_fp found, opens a new one; if fp exists close it
* @param string $flag default '', 'o' -> open, 'c' -> close
* @return void has no return
*/
private function fdebugFP(string $flag = ''): void
{
if (!$this->debug_fp || $flag == 'o') {
$fn = BASE.LOG.$this->debug_filename;
$this->debug_fp = @fopen($fn, 'a');
} elseif ($this->debug_fp || $flag == 'c') {
fclose($this->debug_fp);
}
}
/**
* passes list of level names, to turn on debug
* eg $foo->debugFor('print', 'on', array('LOG', 'DEBUG', 'INFO'));
* @param string $type error, echo, print
* @param string $flag on/off
* array $array of levels to turn on/off debug
* @return void has no return
*/
public function debugFor(string $type, string $flag): void
{
$debug_on = func_get_args();
array_shift($debug_on); // kick out type
array_shift($debug_on); // kick out flag (on/off)
if (count($debug_on) >= 1) {
foreach ($debug_on as $level) {
$switch = $type.'_output';
if ($flag == 'off') {
$switch .= '_not';
}
$this->{$switch}[$level] = 1;
}
}
}
/**
* write debug data to error_msg array
* @param string $level id for error message, groups messages together
* @param string $string the actual error message
* @param bool|boolean $strip default on false, if set to true,
* all html tags will be stripped and <br> changed to \n
* this is only used for debug output
* @return void has no return
*/
public function debug(string $level, string $string, bool $strip = false): void
{
if (($this->debug_output[$level] || $this->debug_output_all) && !$this->debug_output_not[$level]) {
if (!isset($this->error_msg[$level])) {
$this->error_msg[$level] = '';
}
$error_string = '<div>';
$error_string .= '[<span style="font-weight: bold; color: #5e8600;">'.$this->printTime().'</span>] ';
$error_string .= '[<span style="font-weight: bold; color: #c56c00;">'.$level.'</span>] ';
$error_string .= '[<span style="color: #b000ab;">'.$this->host_name.'</span>] ';
$error_string .= '[<span style="color: #08b369;">'.$this->page_name.'</span>] ';
$error_string .= '[<span style="color: #0062A2;">'.$this->running_uid.'</span>] ';
$error_string .= '{<span style="font-style: italic; color: #928100;">'.get_class($this).'</span>} - '.$string;
$error_string .= "</div><!--#BR#-->";
if ($strip) {
// find any <br> and replace them with \n
$string = str_replace('<br>', "\n", $string);
// strip rest of html elements
$string = preg_replace("/(<\/?)(\w+)([^>]*>)/", '', $string);
}
// same string put for print (no html crap inside)
$error_string_print = '['.$this->printTime().'] ['.$this->host_name.'] ['.$this->getPageName(2).'] ['.$this->running_uid.'] {'.get_class($this).'} <'.$level.'> - '.$string;
$error_string_print .= "\n";
// write to file if set
$this->writeErrorMsg($level, $error_string_print);
// write to error level
if (($this->echo_output[$level] || $this->echo_output_all) && !$this->echo_output_not[$level]) {
$this->error_msg[$level] .= $error_string;
}
}
}
/**
* if there is a need to find out which parent method called a child method,
* eg for debugging, this function does this
* call this method in the child method and you get the parent function that called
* @param int $level debug level, default 2
* @return ?string null or the function that called the function where this method is called
*/
public function getCallerMethod(int $level = 2): ?string
{
$traces = debug_backtrace();
// extended info (later)
/*$file = $trace[$level]['file'];
$line = $trace[$level]['line'];
$object = $trace[$level]['object'];
if (is_object($object)) {
$object = get_class($object);
}
return "Where called: line $line of $object \n(in $file)";*/
// sets the start point here, and in level two (the sub call) we find this
if (isset($traces[$level])) {
return $traces[$level]['function'];
}
return null;
}
/**
* merges the given error array with the one from this class
* only merges visible ones
* @param array $error_msg error array
* @return void has no return
*/
public function mergeErrors(array $error_msg = array()): void
{
if (!is_array($error_msg)) {
$error_msg = array();
}
foreach ($error_msg as $level => $msg) {
$this->error_msg[$level] .= $msg;
}
}
/**
* prints out the error string
* @param string $string prefix string for header
* @return string error msg for all levels
*/
public function printErrorMsg(string $string = ''): string
{
$string_output = '';
if ($this->debug_output_all) {
if ($this->error_msg_prefix) {
$string = $this->error_msg_prefix;
}
$script_end = microtime(true) - $this->script_starttime;
foreach ($this->error_msg as $level => $temp_debug_output) {
if (($this->debug_output[$level] || $this->debug_output_all) && !$this->debug_output_not[$level]) {
if (($this->echo_output[$level] || $this->echo_output_all) && !$this->echo_output_not[$level]) {
$string_output .= '<div style="font-size: 12px;">[<span style="font-style: italic; color: #c56c00;">'.$level.'</span>] '.(($string) ? "<b>**** ".$this->htmlent($string)." ****</b>\n" : "").'</div>';
$string_output .= $temp_debug_output;
} // echo it out
} // do printout
} // for each level
// create the output wrapper around, so we have a nice formated output per class
if ($string_output) {
$string_prefix = '<div style="text-align: left; padding: 5px; font-size: 10px; font-family: sans-serif; border-top: 1px solid black; border-bottom: 1px solid black; margin: 10px 0 10px 0; background-color: white; color: black;">';
$string_prefix .= '<div style="font-size: 12px;">{<span style="font-style: italic; color: #928100;">'.get_class($this).'</span>}</div>';
$string_output = $string_prefix.$string_output;
$string_output .= '<div><span style="font-style: italic; color: #108db3;">Script Run Time:</span> '.$script_end.'</div>';
$string_output .= '</div>';
}
}
return $string_output;
}
/**
* writes error msg data to file for current level
* @param string $level the level to write
* @param string $error_string error string to write
* @return void has no return
*/
private function writeErrorMsg(string $level, string $error_string): void
{
if (($this->debug_output[$level] || $this->debug_output_all) && !$this->debug_output_not[$level]) {
// only write if write is requested
if (($this->print_output[$level] || $this->print_output_all) && !$this->print_output_not[$level]) {
// replace all html tags
// $error_string = preg_replace("/(<\/?)(\w+)([^>]*>)/", "##\\2##", $error_string);
// $error_string = preg_replace("/(<\/?)(\w+)([^>]*>)/", "", $error_string);
// replace special line break tag
// $error_string = str_replace('<!--#BR#-->', "\n", $error_string);
// init output variable
$output = $error_string; // output formated error string to output file
// init base file path
$fn = BASE.LOG.$this->log_print_file.'.'.$this->log_file_name_ext;
// log ID prefix settings, if not valid, replace with empty
if (preg_match("/^[A-Za-z0-9]+$/", $this->log_file_id)) {
$rpl_string = '_'.$this->log_file_id;
} else {
$rpl_string = '';
}
$fn = str_replace('##LOGID##', $rpl_string, $fn); // log id (like a log file prefix)
if ($this->log_per_run) {
if (isset($GLOBALS['LOG_FILE_UNIQUE_ID'])) {
$this->log_file_unique_id = $GLOBALS['LOG_FILE_UNIQUE_ID'];
}
if (!$this->log_file_unique_id) {
$GLOBALS['LOG_FILE_UNIQUE_ID'] = $this->log_file_unique_id = date('Y-m-d_His').'_U_'.substr(hash('sha1', uniqid((string)mt_rand(), true)), 0, 8);
}
$rpl_string = '_'.$this->log_file_unique_id; // add 8 char unique string
} else {
$rpl_string = !$this->log_print_file_date ? '' : '_'.date('Y-m-d'); // add date to file
}
$fn = str_replace('##DATE##', $rpl_string, $fn); // create output filename
$rpl_string = !$this->log_per_level ? '' : '_'.$level; // if request to write to one file
$fn = str_replace('##LEVEL##', $rpl_string, $fn); // create output filename
$rpl_string = !$this->log_per_class ? '' : '_'.str_replace('\\', '-', get_class($this)); // set sub class settings
$fn = str_replace('##CLASS##', $rpl_string, $fn); // create output filename
$rpl_string = !$this->log_per_page ? '' : '_'.$this->getPageName(1); // if request to write to one file
$fn = str_replace('##PAGENAME##', $rpl_string, $fn); // create output filename
// write to file
// first check if max file size is is set and file is bigger
if ($this->log_max_filesize > 0 && ((filesize($fn) / 1024) > $this->log_max_filesize)) {
// for easy purpose, rename file only to attach timestamp, nur sequence numbering
rename($fn, $fn.'.'.date("YmdHis"));
}
$fp = fopen($fn, 'a');
if ($fp !== false) {
fwrite($fp, $output);
fclose($fp);
} else {
echo "<!-- could not open file: $fn //-->";
}
} // do write to file
}
}
/**
* unsests the error message array
* can be used if writing is primary to file
* if no level given resets all
* @param string $level optional level
* @return void has no return
*/
public function resetErrorMsg(string $level = ''): void
{
if (!$level) {
$this->error_msg = array();
} elseif (isset($this->error_msg[$level])) {
unset($this->error_msg[$level]);
}
}
/**
* prints a html formatted (pre) array
* @param array $array any array
* @return string formatted array for output with <pre> tag added
*/
public static function printAr(array $array): string
{
return "<pre>".print_r($array, true)."</pre>";
}
// ****** DEBUG/ERROR FUNCTIONS ******
// ****** RANDOM KEY GEN ******
// METHOD: initRandomKeyData
// PARAMS: none
// RETURN: none
// DESC : sets the random key range with the default values
/**
* sets the random key range with the default values
* @return void has no return
*/
private function initRandomKeyData()
{
// random key generation
$this->key_range = array_merge(range('A', 'Z'), range('a', 'z'), range('0', '9'));
$this->one_key_length = count($this->key_range);
// pow($this->one_key_length, 4);
// default set to 4, should be more than enought (62*62*62*62)
$this->key_length = 4;
}
/**
* validates they key length for random string generation
* @param int $key_length key length
* @return bool true for valid, false for invalid length
*/
private function validateRandomKeyLenght(int $key_length): bool
{
if (is_numeric($key_length) &&
$key_length > 0 &&
$key_length <= $this->max_key_length
) {
return true;
} else {
return false;
}
}
/**
* sets the key length and checks that they key given is valid
* if failed it will not change the default key length and return false
* @param int $key_length key length
* @return bool true/false for set status
*/
public function initRandomKeyLength(int $key_length): bool
{
// only if valid int key with valid length
if ($this->validateRandomKeyLenght($key_length) === true) {
$this->key_length = $key_length;
return true;
} else {
return false;
}
}
/**
* creates a random key based on the key_range with key_length
* if override key length is set, it will check on valid key and use this
* this will not set the class key length variable
* @param int $key_length key length override, -1 for use default
* @return string random key
*/
public function randomKeyGen(int $key_length = -1): string
{
$use_key_length = 0;
// only if valid int key with valid length
if ($this->validateRandomKeyLenght($key_length) === true) {
$use_key_length = $key_length;
} else {
$use_key_length = $this->key_length;
}
return join(
'',
array_map(
function ($value) {
return $this->key_range[rand(0, $this->one_key_length - 1)];
},
range(1, $use_key_length)
)
);
}
// ****** RANDOM KEY GEN ******
/**
* returns 'checked' or 'selected' if okay
* $needle is a var, $haystack an array or a string
* **** THE RETURN: VALUE WILL CHANGE TO A DEFAULT NULL IF NOT FOUND ****
* @param array|string $haystack (search in) haystack can be an array or a string
* @param string $needle needle (search for)
* @param int $type type: 0: returns selected, 1, returns checked
* @return ?string returns checked or selected, else returns null
*/
public static function checked($haystack, $needle, int $type = 0): ?string
{
if (is_array($haystack)) {
if (in_array((string)$needle, $haystack)) {
return (($type) ? "checked" : "selected");
}
} else {
if ($haystack == $needle) {
return (($type) ? "checked" : "selected");
}
}
return null;
}
/**
* tries to find mailto:user@bubu.at and changes it into -> <a href="mailto:user@bubu.at">E-Mail senden</a>
* or tries to take any url (http, ftp, etc) and transform it into a valid URL
* the string is in the format: some url|name#css|, same for email
* @param string $string data to transform to a valud HTML url
* @param string $target target string, default _blank
* @return string correctly formed html url link
*/
public function magicLinks(string $string, string $target = "_blank"): string
{
$output = $string;
$protList = array("http", "https", "ftp", "news", "nntp");
// find urls w/o protocol
$output = preg_replace("/([^\/])www\.([\w\.-]+)\.([a-zA-Z]{2,4})/", "\\1http://www.\\2.\\3", $output);
$output = preg_replace("/([^\/])ftp\.([\w\.-]+)\.([a-zA-Z]{2,4})/", "\\1ftp://ftp.\\2.\\3", $output);
// remove doubles, generate protocol-regex
// DIRTY HACK
$protRegex = "";
foreach ($protList as $protocol) {
if ($protRegex) {
$protRegex .= "|";
}
$protRegex .= "$protocol:\/\/";
}
// find urls w/ protocol
// cs: escaped -, added / for http urls
// added | |, this time mandatory, todo: if no | |use \\1\\2
// backslash at the end of a url also allowed now
// do not touch <.*=".."> things!
// _1: URL or email
// _2: atag (>)
// _3: (_1) part of url or email [main url or email pre @ part]
// _4: (_2) parameters of url or email post @ part
// _5: (_3) parameters of url or tld part of email
// _7: link name/email link name
// _9: style sheet class
$self = $this;
// $this->debug('URL', 'Before: '.$output);
$output = preg_replace_callback(
"/(href=\")?(\>)?\b($protRegex)([\w\.\-?&=+%#~,;\/]+)\b([\.\-?&=+%#~,;\/]*)(\|([^\||^#]+)(#([^\|]+))?\|)?/",
function ($matches) use ($self) {
return @$self->createUrl($matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[7], $matches[9]);
},
$output
);
// find email-addresses, but not mailto prefix ones
$output = preg_replace_callback(
"/(mailto:)?(\>)?\b([\w\.-]+)@([\w\.\-]+)\.([a-zA-Z]{2,4})\b(\|([^\||^#]+)(#([^\|]+))?\|)?/",
function ($matches) use ($self) {
return @$self->createEmail($matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[7], $matches[9]);
},
$output
);
$this->debug('URL', 'After: '.$output);
// we have one slashes after the Protocol -> internal link no domain, strip out the proto
// $output = preg_replace("/($protRegex)\/(.*)/e", "\\2", $ouput);
// $this->debug('URL', "$output");
// post processing
$output = str_replace("{TARGET}", $target, $output);
$output = str_replace("##LT##", "<", $output);
$output = str_replace("##GT##", ">", $output);
$output = str_replace("##QUOT##", "\"", $output);
return $output;
}
/**
* internal function, called by the magic url create functions.
* checks if title $_4 exists, if not, set url as title
* @param string $href url link
* @param string $atag anchor tag (define both type or url)
* @param string $_1 part of the URL, if atag is set, _1 is not used
* @param string $_2 part of the URL
* @param string $_3 part of the URL
* @param string $name name for the url, if not given _2 + _3 is used
* @param string $class style sheet
* @return string correct string for url href process
*/
private function createUrl($href, $atag, $_1, $_2, $_3, $name, $class): string
{
// $this->debug('URL', "1: $_1 - 2: $_2 - $_3 - atag: $atag - name: $name - class: $class");
// if $_1 ends with //, then we strip $_1 complete & target is also blanked (its an internal link)
if (preg_match("/\/\/$/", $_1) && preg_match("/^\//", $_2)) {
$_1 = '';
$target = '';
} else {
$target = '{TARGET}';
}
// if it is a link already just return the original link do not touch anything
if (!$href && !$atag) {
return "##LT##a href=##QUOT##".$_1.$_2.$_3."##QUOT##".(($class) ? ' class=##QUOT##'.$class.'##QUOT##' : '').(($target) ? " target=##QUOT##".$target."##QUOT##" : '')."##GT##".(($name) ? $name : $_2.$_3)."##LT##/a##GT##";
} elseif ($href && !$atag) {
return "href=##QUOT##$_1$_2$_3##QUOT##";
} elseif ($atag) {
return $atag.$_2.$_3;
}
}
/**
* internal function for createing email, returns data to magic_url method
* @param string $mailto email address
* @param string $atag atag (define type of url)
* @param string $_1 parts of the email _1 before @, 3_ tld
* @param string $_2 _2 domain part after @
* @param string $_3 _3 tld
* @param string $title name for the link, if not given use email
* @param string $class style sheet
* @return string created html email a href string
*/
private function createEmail($mailto, $atag, $_1, $_2, $_3, $title, $class)
{
$email = $_1."@".$_2.".".$_3;
if (!$mailto && !$atag) {
return "##LT##a href=##QUOT##mailto:".$email."##QUOT##".(($class) ? ' class=##QUOT##'.$class.'##QUOT##' : '')."##GT##".(($title) ? $title : $email)."##LT##/a##GT##";
} elseif ($mailto && !$atag) {
return "mailto:".$email;
} elseif ($atag) {
return $atag.$email;
}
}
/**
* get the host name without the port as given by the SELF var
* @return string host name
*/
public function getHostName(): string
{
$port = '';
if ($_SERVER['HTTP_HOST'] && preg_match("/:/", $_SERVER['HTTP_HOST'])) {
list($host_name, $port) = explode(":", $_SERVER['HTTP_HOST']);
} elseif ($_SERVER['HTTP_HOST']) {
$host_name = $_SERVER['HTTP_HOST'];
} else {
$host_name = 'NA';
}
$this->host_port = $port ? $port : 80;
$this->host_name = $host_name;
// also return for old type call
return $host_name;
}
/**
* get the page name of the curronte page
* @param int $strip_ext 1: strip page file name extension
* 0: keep filename as is
* 2: keep filename as is, but add dirname too
* @return string filename
*/
public static function getPageName(int $strip_ext = 0): string
{
// get the file info
$page_temp = pathinfo($_SERVER["PHP_SELF"]);
if ($strip_ext == 1) {
return $page_temp['filename'];
} elseif ($strip_ext == 2) {
return $_SERVER['PHP_SELF'];
} else {
return $page_temp['basename'];
}
}
/**
* quick return the extension of the given file name
* @param string $filename file name
* @return string extension of the file name
*/
public static function getFilenameEnding(string $filename): string
{
$page_temp = pathinfo($filename);
return $page_temp['extension'];
}
/**
* searches key = value in an array / array
* only returns the first one found
* @param string|int $needle needle (search for)
* @param array $haystack haystack (search in)
* @param string|null $key_lookin the key to look out for, default empty
* @return array array with the elements where the needle can be
* found in the haystack array
*/
public static function arraySearchRecursive($needle, array $haystack, ?string $key_lookin = null): array
{
$path = array();
if (!is_array($haystack)) {
$haystack = array();
}
if ($key_lookin != null &&
!empty($key_lookin) &&
array_key_exists($key_lookin, $haystack) &&
$needle === $haystack[$key_lookin]
) {
$path[] = $key_lookin;
} else {
foreach ($haystack as $key => $val) {
if (is_scalar($val) && $val === $needle && empty($key_lookin)) {
break;
} elseif (is_scalar($val) && !empty($key_lookin) && $key === $key_lookin && $val == $needle) {
$path[] = $key;
break;
} elseif (is_array($val) && $path = Basic::arraySearchRecursive($needle, $val, $key_lookin)) {
array_unshift($path, $key);
break;
}
}
}
return $path;
}
/**
* recursive array search function, which returns all found not only the first one
* @param string|int $needle needle (search for)
* @param array $haystack haystack (search in)
* @param string|int $key the key to look for in
* @param array $path recursive call for previous path
* @return ?array all array elements paths where the element was found
*/
public static function arraySearchRecursiveAll($needle, array $haystack, $key, $path = null): ?array
{
if (!isset($path['level'])) {
$path['level'] = 0;
}
if (!isset($path['work'])) {
$path['work'] = array();
}
if (!is_array($haystack)) {
$haystack = array();
}
// go through the array,
foreach ($haystack as $_key => $_value) {
if (is_scalar($_value) && $_value == $needle && !$key) {
// only value matches
$path['work'][$path['level']] = $_key;
$path['found'][] = $path['work'];
} elseif (is_scalar($_value) && $_value == $needle && $_key == $key) {
// key and value matches
$path['work'][$path['level']] = $_key;
$path['found'][] = $path['work'];
} elseif (is_array($_value)) {
// add position to working
$path['work'][$path['level']] = $_key;
// we will up a level
$path['level'] += 1;
// call recursive
$path = Basic::arraySearchRecursiveAll($needle, $_value, $key, $path);
}
}
// cut all that is >= level
array_splice($path['work'], $path['level']);
// step back a level
$path['level'] -= 1;
return $path;
}
/**
* array search simple. looks for key, value combination, if found, returns true
* @param array $array array(search in)
* @param string|int $key key (key to search in)
* @param string|int $value value (what to find)
* @return bool true on found, false on not found
*/
public static function arraySearchSimple(array $array, $key, $value): bool
{
if (!is_array($array)) {
$array = array();
}
foreach ($array as $_key => $_value) {
// if value is an array, we search
if (is_array($_value)) {
// call recursive, and return result if it is true, else continue
if (($result = Basic::arraySearchSimple($_value, $key, $value)) !== false) {
return $result;
}
} elseif ($_key == $key && $_value == $value) {
return true;
}
}
// no true returned, not found
return false;
}
/**
* correctly recursive merges as an array as array_merge_recursive just glues things together
* array first array to merge
* array second array to merge
* ... etc
* bool key flag: true: handle keys as string or int
* default false: all keys are string
* @return array|bool merged array
*/
public static function arrayMergeRecursive()
{
// croak on not enough arguemnts (we need at least two)
if (func_num_args() < 2) {
trigger_error(__FUNCTION__ .' needs two or more array arguments', E_USER_WARNING);
return false;
}
// default key is not string
$key_is_string = false;
$arrays = func_get_args();
// if last is not array, then assume it is trigger for key is always string
if (!is_array(end($arrays))) {
if (array_pop($arrays)) {
$key_is_string = true;
}
}
// check that arrays count is at least two, else we don't have enough to do anything
if (count($arrays) < 2) {
trigger_error(__FUNCTION__.' needs two or more array arguments', E_USER_WARNING);
return false;
}
$merged = array();
while ($arrays) {
$array = array_shift($arrays);
if (!is_array($array)) {
trigger_error(__FUNCTION__ .' encountered a non array argument', E_USER_WARNING);
return false;
}
if (!$array) {
continue;
}
foreach ($array as $key => $value) {
// if string or if key is assumed to be string do key match else add new entry
if (is_string($key) || $key_is_string === false) {
if (is_array($value) && array_key_exists($key, $merged) && is_array($merged[$key])) {
// $merged[$key] = call_user_func(__METHOD__, $merged[$key], $value, $key_is_string);
$merged[$key] = Basic::arrayMergeRecursive($merged[$key], $value, $key_is_string);
} else {
$merged[$key] = $value;
}
} else {
$merged[] = $value;
}
}
}
return $merged;
}
/**
* search for the needle array elements in haystack and return the ones found as an array,
* is there nothing found, it returns FALSE (boolean)
* @param array $needle elements to search for
* @param array $haystack array where the $needle elements should be searched int
* @return array|bool either the found elements or false for nothing found or error
*/
public static function inArrayAny(array $needle, array $haystack)
{
if (!is_array($needle)) {
return false;
}
if (!is_array($haystack)) {
return false;
}
$found = array();
foreach ($needle as $element) {
if (in_array($element, $haystack)) {
$found[] = $element;
}
}
if (count($found) == 0) {
return false;
} else {
return $found;
}
}
/**
* creates out of a normal db_return array an assoc array
* @param array $db_array return array from the database
* @param string|int|bool $key key set, false for not set
* @param string|int|bool $value value set, false for not set
* @param bool $set_only flag to return all (default), or set only
* @return array associative array
*/
public static function genAssocArray(array $db_array, $key, $value, bool $set_only = false): array
{
$ret_array = array();
// do this to only run count once
for ($i = 0, $iMax = count($db_array); $i < $iMax; $i ++) {
// if no key then we make an order reference
if ($key !== false &&
$value !== false &&
(($set_only && $db_array[$i][$value]) || (!$set_only))
) {
$ret_array[$db_array[$i][$key]] = $db_array[$i][$value];
} elseif ($key === false && $value !== false) {
$ret_array[] = $db_array[$i][$value];
} elseif ($key !== false && $value === false) {
$ret_array[$db_array[$i][$key]] = $i;
}
}
return $ret_array;
}
/**
* [NOTE]: This is an old function and is deprecated
* wrapper for join, but checks if input is an array and if not returns null
* @param array $array array to convert
* @param string $connect_char connection character
* @return string joined string
* @deprecated use join() instead
*/
public static function arrayToString(array $array, string $connect_char): string
{
trigger_error('Method '.__METHOD__.' is deprecated, use join()', E_USER_DEPRECATED);
if (!is_array($array)) {
$array = array();
}
return join($connect_char, $array);
}
/**
* converts multi dimensional array to a flat array
* does NOT preserve keys
* @param array $array ulti dimensionial array
* @return array flattened array
*/
public static function flattenArray(array $array): array
{
$return = array();
array_walk_recursive(
$array,
function ($value) use (&$return) {
$return[] = $value;
}
);
return $return;
}
/**
* will loop through an array recursivly and write the array keys back
* @param array $array multidemnsional array to flatten
* @return array flattened keys array
*/
public static function flattenArrayKey(array $array/*, array $return = array()*/): array
{
$return = array();
array_walk_recursive(
$array,
function ($value, $key) use (&$return) {
$return [] = $key;
}
);
return $return;
}
/**
* searches for key -> value in an array tree and writes the value one level up
* this will remove this leaf will all other values
* @param array $array array(nested)
* @param string|int $search key to find that has no sub leaf and will be pushed up
* @return array modified, flattened array
*/
public static function arrayFlatForKey(array $array, $search): array
{
if (!is_array($array)) {
$array = array();
}
foreach ($array as $key => $value) {
// if it is not an array do just nothing
if (is_array($value)) {
// probe it has search key
if (isset($value[$search])) {
// set as current
$array[$key] = $value[$search];
} else {
// call up next node down
// $array[$key] = call_user_func(__METHOD__, $value, $search);
$array[$key] = Basic::arrayFlatForKey($value, $search);
}
}
}
return $array;
}
/**
* wrapper function for mb mime convert, for correct conversion with long strings
* @param string $string string to encode
* @param string $encoding target encoding
* @return string encoded string
*/
public static function __mbMimeEncode(string $string, string $encoding): string
{
// set internal encoding, so the mimeheader encode works correctly
mb_internal_encoding($encoding);
// if a subject, make a work around for the broken mb_mimencode
$pos = 0;
$split = 36; // after 36 single bytes characters, if then comes MB, it is broken
// has to 2 x 36 < 74 so the mb_encode_mimeheader 74 hardcoded split does not get triggered
$_string = '';
while ($pos < mb_strlen($string, $encoding)) {
$output = mb_strimwidth($string, $pos, $split, "", $encoding);
$pos += mb_strlen($output, $encoding);
// if the strinlen is 0 here, get out of the loop
if (!mb_strlen($output, $encoding)) {
$pos += mb_strlen($string, $encoding);
}
$_string_encoded = mb_encode_mimeheader($output, $encoding);
// only make linebreaks if we have mime encoded code inside
// the space only belongs in the second line
if ($_string && preg_match("/^=\?/", $_string_encoded)) {
$_string .= "\n ";
}
$_string .= $_string_encoded;
}
// strip out any spaces BEFORE a line break
$string = str_replace(" \n", "\n", $_string);
return $string;
}
/**
* converts bytes into formated string with KB, MB, etc
* @param string|int|float $number bytes as string int or pure int
* @param bool $space true (default) to add space between number and suffix
* @return string converted byte number (float) with suffix
*/
public static function byteStringFormat($number, bool $space = true): string
{
if (is_numeric($number) && $number > 0) {
// labels in order of size
$labels = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB');
// calc file size, round down too two digits, add label based max change
return round($number / pow(1024, ($i = floor(log((float)$number, 1024)))), 2).($space ? ' ' : '').(isset($labels[(int)$i]) ? $labels[(int)$i] : '>EB');
}
return (string)$number;
}
/**
* calculates the bytes based on a string with nnG, nnGB, nnM, etc
* if the number has a non standard thousand seperator ","" inside, the second
* flag needs to be set true (eg german style notaded numbers)
* @param string|int|float $number any string or number to convert
* @param bool $dot_thousand default is ".", set true for ","
* @return string|int|float converted value or original value
*/
public static function stringByteFormat($number, bool $dot_thousand = false)
{
// detects up to exo bytes
preg_match("/([\d.,]*)\s?(eb|pb|tb|gb|mb|kb|e|p|t|g|m|k|b)$/", strtolower($number), $matches);
if (isset($matches[1]) && isset($matches[2])) {
// $last = strtolower($number[strlen($number) - 1]);
if ($dot_thousand === false) {
$number = str_replace(',', '', $matches[1]);
} else {
$number = str_replace('.', '', $matches[1]);
}
$number = (float)trim($number);
// match string in type to calculate
switch ($matches[2]) {
// exo bytes
case 'e':
case 'eb':
$number *= 1024;
// peta bytes
case 'p':
case 'pb':
$number *= 1024;
// tera bytes
case 't':
case 'tb':
$number *= 1024;
// giga bytes
case 'g':
case 'gb':
$number *= 1024;
// mega bytes
case 'm':
case 'mb':
$number *= 1024;
// kilo bytes
case 'k':
case 'kb':
$number *= 1024;
break;
}
$number = (int)round($number, 0);
}
// if not matching return as is
return $number;
}
/**
* a simple wrapper for the date format
* @param int|float $timestamp unix timestamp
* @param bool $show_micro show the micro time (default false)
* @return string formated date+time in Y-M-D h:m:s ms
*/
public static function dateStringFormat($timestamp, bool $show_micro = false): string
{
// split up the timestamp, assume . in timestamp
// array pad $ms if no microtime
list ($timestamp, $ms) = array_pad(explode('.', (string)round($timestamp, 4)), 2, null);
$string = date("Y-m-d H:i:s", (int)$timestamp);
if ($show_micro && $ms) {
$string .= ' '.$ms.'ms';
}
return $string;
}
/**
* formats a timestamp into interval, not into a date
* @param string|int|float $timestamp interval in seconds and optional float micro seconds
* @param bool $show_micro show micro seconds, default true
* @return string interval formatted string or string as is
*/
public static function timeStringFormat($timestamp, bool $show_micro = true): string
{
// check if the timestamp has any h/m/s/ms inside, if yes skip
if (!preg_match("/(h|m|s|ms)/", (string)$timestamp)) {
$ms = 0;
list ($timestamp, $ms) = explode('.', (string)round($timestamp, 4));
$timegroups = array(86400, 3600, 60, 1);
$labels = array('d', 'h', 'm', 's');
$time_string = '';
for ($i = 0, $iMax = count($timegroups); $i < $iMax; $i ++) {
$output = floor((float)$timestamp / $timegroups[$i]);
$timestamp = (float)$timestamp % $timegroups[$i];
// output has days|hours|min|sec
if ($output || $time_string) {
$time_string .= $output.$labels[$i].(($i + 1) != count($timegroups) ? ' ' : '');
}
}
// if we have ms and it has leading zeros, remove them
$ms = preg_replace("/^0+/", '', $ms);
// add ms if there
if ($show_micro) {
$time_string .= ' '.(!$ms ? 0 : $ms).'ms';
} elseif (!$time_string) {
$time_string .= (!$ms ? 0 : $ms).'ms';
}
} else {
$time_string = $timestamp;
}
return $time_string;
}
/**
* does a reverse of the TimeStringFormat and converts the string from
* xd xh xm xs xms to a timestamp.microtime format
* @param string|int|float $timestring formatted interval
* @return string|int|float converted float interval, or string as is
*/
public static function stringToTime($timestring)
{
$timestamp = 0;
if (preg_match("/(d|h|m|s|ms)/", $timestring)) {
// pos for preg match read + multiply factor
$timegroups = array(2 => 86400, 4 => 3600, 6 => 60, 8 => 1);
$matches = array();
// preg match: 0: full strsing
// 2, 4, 6, 8 are the to need values
preg_match("/^((\d+)d ?)?((\d+)h ?)?((\d+)m ?)?((\d+)s ?)?((\d+)ms)?$/", $timestring, $matches);
// multiply the returned matches and sum them up. the last one (ms) is added with .
foreach ($timegroups as $i => $time_multiply) {
if (is_numeric($matches[$i])) {
$timestamp += (float)$matches[$i] * $time_multiply;
}
}
if (is_numeric($matches[10])) {
$timestamp .= '.'.$matches[10];
}
return $timestamp;
} else {
return $timestring;
}
}
/**
* splits & checks date, wrap around for check_date function
* @param string $date a date string in the format YYYY-MM-DD
* @return bool true if valid date, false if date not valid
*/
public static function checkDate($date): bool
{
if (!$date) {
return false;
}
list ($year, $month, $day) = preg_split("/[\/-]/", $date);
if (!$year || !$month || !$day) {
return false;
}
if (!checkdate((int)$month, (int)$day, (int)$year)) {
return false;
}
return true;
}
/**
* splits & checks date, wrap around for check_date function
* @param string $datetime date (YYYY-MM-DD) + time (HH:MM:SS), SS can be dropped
* @return bool true if valid date, false if date not valid
*/
public static function checkDateTime($datetime): bool
{
if (!$datetime) {
return false;
}
list ($year, $month, $day, $hour, $min, $sec) = preg_split("/[\/\- :]/", $datetime);
if (!$year || !$month || !$day) {
return false;
}
if (!checkdate((int)$month, (int)$day, (int)$year)) {
return false;
}
if (!is_numeric($hour) || !is_numeric($min)) {
return false;
}
if (($hour < 0 || $hour > 24) ||
($min < 0 || $min > 60) ||
(is_numeric($sec) && ($sec < 0 || $sec > 60))
) {
return false;
}
return true;
}
/**
* plits & checks date, wrap around for check_date function
* returns int in:
* -1 if the first date is smaller the last
* 0 if both are equal
* 1 if the first date is bigger than the last
* false (bool): error
* @param string $start_date start date string in YYYY-MM-DD
* @param string $end_date end date string in YYYY-MM-DD
* @return int|bool false on error, or int -1/0/1 as difference
*/
public static function compareDate($start_date, $end_date)
{
// pre check for empty or wrong
if ($start_date == '--' || $end_date == '--' || !$start_date || !$end_date) {
return false;
}
// splits the data up with / or -
list ($start_year, $start_month, $start_day) = preg_split('/[\/-]/', $start_date);
list ($end_year, $end_month, $end_day) = preg_split('/[\/-]/', $end_date);
// check that month & day are two digits and then combine
foreach (array('start', 'end') as $prefix) {
foreach (array('month', 'day') as $date_part) {
$_date = $prefix.'_'.$date_part;
if ($$_date < 10 && !preg_match("/^0/", $$_date)) {
$$_date = '0'.$$_date;
}
}
$_date = $prefix.'_date';
$$_date = '';
foreach (array('year', 'month', 'day') as $date_part) {
$_sub_date = $prefix.'_'.$date_part;
$$_date .= $$_sub_date;
}
}
// now do the compare
if ($start_date < $end_date) {
return -1;
} elseif ($start_date == $end_date) {
return 0;
} elseif ($start_date > $end_date) {
return 1;
}
}
/**
* compares the two dates + times. if seconds missing in one set, add :00, converts / to -
* returns int/bool in:
* -1 if the first date is smaller the last
* 0 if both are equal
* 1 if the end date is bigger than the last
* false if no valid date/times chould be found
* @param string $start_datetime start date/time in YYYY-MM-DD HH:mm:ss
* @param string $end_datetime end date/time in YYYY-MM-DD HH:mm:ss
* @return int|bool false for error or -1/0/1 as difference
*/
public static function compareDateTime($start_datetime, $end_datetime)
{
// pre check for empty or wrong
if ($start_datetime == '--' || $end_datetime == '--' || !$start_datetime || !$end_datetime) {
return false;
}
$start_timestamp = strtotime($start_datetime);
$end_timestamp = strtotime($end_datetime);
if ($start_timestamp < $end_timestamp) {
return -1;
} elseif ($start_timestamp == $end_timestamp) {
return 0;
} elseif ($start_timestamp > $end_timestamp) {
return 1;
}
}
/**
* calculates the days between two dates
* return: overall days, week days, weekend days as array 0...2 or named
* as overall, weekday and weekend
* @param string $start_date valid start date (y/m/d)
* @param string $end_date valid end date (y/m/d)
* @param bool $return_named return array type, false (default), true for named
* @return array 0/overall, 1/weekday, 2/weekend
*/
public static function calcDaysInterval($start_date, $end_date, bool $return_named = false): array
{
// pos 0 all, pos 1 weekday, pos 2 weekend
$days = array();
$start = new \DateTime($start_date);
$end = new \DateTime($end_date);
// so we include the last day too, we need to add +1 second in the time
$end->setTime(0, 0, 1);
$days[0] = $end->diff($start)->days;
$days[1] = 0;
$days[2] = 0;
$period = new \DatePeriod($start, new \DateInterval('P1D'), $end);
foreach ($period as $dt) {
$curr = $dt->format('D');
if ($curr == 'Sat' || $curr == 'Sun') {
$days[2] ++;
} else {
$days[1] ++;
}
}
if ($return_named === true) {
return array(
'overall' => $days[0],
'weekday' => $days[1],
'weekend' => $days[2]
);
} else {
return $days;
}
}
/**
* converts picture to a thumbnail with max x and max y size
* @param string $pic source image file with or without path
* @param int $size_x maximum size width
* @param int $size_y maximum size height
* @param string $dummy empty, or file_type to show an icon instead of nothing if file is not found
* @param string $path if source start is not ROOT path, if empty ROOT is choosen
* @param string $cache_source cache path, if not given TMP is used
* @param bool $clear_cache if set to true, will create thumb all the tame
* @return string|bool thumbnail name, or false for error
*/
public static function createThumbnail(string $pic, int $size_x, int $size_y, string $dummy = '', string $path = '', string $cache_source = '', bool $clear_cache = false)
{
// get image type flags
$image_types = array(
1 => 'gif',
2 => 'jpg',
3 => 'png'
);
$return_data = false;
if (!empty($cache_source)) {
$tmp_src = $cache_source;
} else {
$tmp_src = BASE.TMP;
}
// check if pic has a path, and override next sets
if (strstr($pic, '/') === false) {
if (empty($path)) {
$path = BASE;
}
$filename = $path.MEDIA.PICTURES.$pic;
} else {
$filename = $pic;
// and get the last part for pic (the filename)
$tmp = explode('/', $pic);
$pic = $tmp[(count($tmp) - 1)];
}
// does this picture exist and is it a picture
if (file_exists($filename) && is_file($filename)) {
list($width, $height, $type) = getimagesize($filename);
$convert_prefix = '';
$create_file = false;
$delete_filename = '';
// check if we can skip the PDF creation: if we have size, if do not have type, we assume type png
if (!$type && is_numeric($size_x) && is_numeric($size_y)) {
$check_thumb = $tmp_src.'thumb_'.$pic.'_'.$size_x.'x'.$size_y.'.'.$image_types[3];
if (!is_file($check_thumb)) {
$create_file = true;
} else {
$type = 3;
}
}
// if type is not in the list, but returns as PDF, we need to convert to JPEG before
if (!$type) {
// is this a PDF, if no, return from here with nothing
$convert_prefix = 'png:';
# TEMP convert to PNG, we then override the file name
$convert_string = CONVERT.' '.$filename.' '.$convert_prefix.$filename.'_TEMP';
$status = exec($convert_string, $output, $return);
$filename .= '_TEMP';
// for delete, in case we need to glob
$delete_filename = $filename;
// find file, if we can't find base name, use -0 as the first one (ignore other pages in multiple ones)
if (!is_file($filename)) {
$filename .= '-0';
}
list($width, $height, $type) = getimagesize($filename);
}
// if no size given, set size to original
if (!$size_x || $size_x < 1 || !is_numeric($size_x)) {
$size_x = $width;
}
if (!$size_y || $size_y < 1 || !is_numeric($size_y)) {
$size_y = $height;
}
$thumb = 'thumb_'.$pic.'_'.$size_x.'x'.$size_y.'.'.$image_types[$type];
$thumbnail = $tmp_src.$thumb;
// check if we already have this picture converted
if (!is_file($thumbnail) || $clear_cache == true) {
// convert the picture
if ($width > $size_x) {
$convert_string = CONVERT.' -geometry '.$size_x.'x '.$filename.' '.$thumbnail;
$status = exec($convert_string, $output, $return);
// get the size of the converted data, if converted
if (is_file($thumbnail)) {
list ($width, $height, $type) = getimagesize($thumbnail);
}
}
if ($height > $size_y) {
$convert_string = CONVERT.' -geometry x'.$size_y.' '.$filename.' '.$thumbnail;
$status = exec($convert_string, $output, $return);
}
}
if (!is_file($thumbnail)) {
copy($filename, $thumbnail);
}
$return_data = $thumb;
// if we have a delete filename, delete here with glob
if ($delete_filename) {
array_map('unlink', glob($delete_filename.'*'));
}
} else {
if ($dummy && strstr($dummy, '/') === false) {
// check if we have the "dummy" image flag set
$filename = PICTURES.ICONS.strtoupper($dummy).".png";
if ($dummy && file_exists($filename) && is_file($filename)) {
$return_data = $filename;
} else {
$return_data = false;
}
} else {
$filename = $dummy;
}
}
return $return_data;
}
/**
* test if a string can be safely convert between encodings. mostly utf8 to shift jis
* the default compare has a possibility of failure, especially with windows
* it is recommended to the following in the script which uses this method:
* mb_substitute_character(0x2234);
* $class->mbErrorChar = '∴';
* if check to Shift JIS
* if check to ISO-2022-JP
* if check to ISO-2022-JP-MS
* set three dots (∴) as wrong character for correct convert error detect
* (this char is used, because it is one of the least used ones)
* @param string $string string to test
* @param string $from_encoding encoding of string to test
* @param string $to_encoding target encoding
* @return bool|array false if no error or array with failed characters
*/
public function checkConvertEncoding(string $string, string $from_encoding, string $to_encoding)
{
// convert to target encoding and convert back
$temp = mb_convert_encoding($string, $to_encoding, $from_encoding);
$compare = mb_convert_encoding($temp, $from_encoding, $to_encoding);
// if string does not match anymore we have a convert problem
if ($string != $compare) {
$failed = array();
// go through each character and find the ones that do not match
for ($i = 0, $iMax = mb_strlen($string, $from_encoding); $i < $iMax; $i ++) {
$char = mb_substr($string, $i, 1, $from_encoding);
$r_char = mb_substr($compare, $i, 1, $from_encoding);
// the ord 194 is a hack to fix the IE7/IE8 bug with line break and illegal character
// $this->debug('CHECK CONVERTT', '['.$this->mbErrorChar.'] O: '.$char.', C: '.$r_char);
if ((($char != $r_char && !$this->mbErrorChar) || ($char != $r_char && $r_char == $this->mbErrorChar && $this->mbErrorChar)) && ord($char) != 194) {
$this->debug('CHARS', "'".$char."'".' == '.$r_char.' ('.ord($char).')');
$failed[] = $char;
}
}
return $failed;
} else {
return false;
}
}
/**
* detects the source encoding of the string and if doesn't match to the given target encoding it convert is
* @param string $string string to convert
* @param string $to_encoding target encoding
* @param string $source_encoding optional source encoding, will try to auto detect
* @return string encoding converted string
*/
public static function convertEncoding(string $string, string $to_encoding, string $source_encoding = ''): string
{
// set if not given
if (!$source_encoding) {
$source_encoding = mb_detect_encoding($string);
}
if ($source_encoding != $to_encoding) {
if ($source_encoding) {
$string = mb_convert_encoding($string, $to_encoding, $source_encoding);
} else {
$string = mb_convert_encoding($string, $to_encoding);
}
}
return $string;
}
/**
* checks php version and if >=5.2.7 it will flip the string
* @param string $string string to crc
* @return string crc32b hash (old type)
*/
public function __crc32b(string $string): string
{
// do normal hash crc32b
$string = hash('crc32b', $string);
// if bigger than 5.2.7, we need to "unfix" the fix
if (self::checkPHPVersion('5.2.7')) {
// flip it back to old (two char groups)
$string = preg_replace("/^([a-z0-9]{2})([a-z0-9]{2})([a-z0-9]{2})([a-z0-9]{2})$/", "$4$3$2$1", $string);
}
return $string;
}
/**
* replacement for __crc32b call
* @param string $string string to hash
* @param bool $use_sha use sha instead of crc32b (default false)
* @return string hash of the string
*/
public function __sha1Short(string $string, bool $use_sha = false): string
{
if ($use_sha) {
// return only the first 9 characters
return substr(hash('sha1', $string), 0, 9);
} else {
return $this->__crc32b($string);
}
}
/**
* replacemend for __crc32b call (alternate)
* defaults to adler 32
* allowed adler32, fnv132, fnv1a32, joaat
* all that create 8 char long hashes
* @param string $string string to hash
* @param string $hash_type hash type (default adler32)
* @return string hash of the string
*/
public function __hash(string $string, string $hash_type = 'adler32'): string
{
if (!in_array($hash_type, array('adler32', 'fnv132', 'fnv1a32', 'joaat'))) {
$hash_type = 'adler32';
}
return hash($hash_type, $string);
}
/**
* checks if running PHP version matches given PHP version (min or max)
* @param string $min_version minimum version as string (x, x.y, x.y.x)
* @param string $max_version optional maximum version as string (x, x.y, x.y.x)
* @return bool true if ok, false if not matching version
*/
public static function checkPHPVersion(string $min_version, string $max_version = ''): bool
{
// exit with false if the min/max strings are wrong
if (!preg_match("/^\d{1}(\.\d{1})?(\.\d{1,2})?$/", $min_version)) {
return false;
}
// max is only chcked if it is set
if ($max_version && !preg_match("/^\d{1}(\.\d{1})?(\.\d{1,2})?$/", $max_version)) {
return false;
}
// split up the version strings to calc the compare number
$version = explode('.', $min_version);
$min_version = (int)$version[0] * 10000 + (int)$version[1] * 100 + (int)$version[2];
if ($max_version) {
$version = explode('.', $max_version);
$max_version = (int)$version[0] * 10000 + (int)$version[1] * 100 + (int)$version[2];
// drop out if min is bigger max, equal size is okay, that would be only THIS
if ($min_version > $max_version) {
return false;
}
}
// set the php version id
if (!defined('PHP_VERSION_ID')) {
$version = explode('.', phpversion());
// creates something like 50107
define('PHP_VERSION_ID', (int)$version[0] * 10000 + (int)$version[1] * 100 + (int)$version[2]);
}
// check if matching for version
if ($min_version && !$max_version) {
if (PHP_VERSION_ID >= $min_version) {
return true;
}
} elseif ($min_version && $max_version) {
if (PHP_VERSION_ID >= $min_version && PHP_VERSION_ID <= $max_version) {
return true;
}
}
// if no previous return, fail
return false;
}
// [!!! DEPRECATED !!!]
// ALL crypt* methids are DEPRECATED and SHALL NOT BE USED
// use the new password* instead
// [!!! DEPRECATED !!!] -> passwordInit
/**
* inits crypt settings for the crypt functions
* this function NEEDS (!) to be called BEFORE any of the crypt functions is called
* there is no auto init for this at the moment
* @return void has not return
* @deprecated use passwordInit instead
*/
private function cryptInit()
{
trigger_error('Method '.__METHOD__.' is deprecated, use passwordInit', E_USER_DEPRECATED);
// SET CRYPT SALT PREFIX:
// the prefix string is defined by what the server can do
// first we check if we can do blowfish, if not we try md5 and then des
// WARNING: des is very bad, only first 6 chars get used for the password
// MD5 is a bit better but is already broken
// problem with PHP < 5.3 is that you mostly don't have access to blowfish
if (CRYPT_BLOWFISH == 1 || self::checkPHPVersion('5.3.0')) {
// blowfish salt prefix
// for < 5.3.7 use the old one for anything newer use the new version
if (self::checkPHPVersion('5.3.7')) {
$this->cryptSaltPrefix = '$2y$';
} else {
$this->cryptSaltPrefix = '$2a$';
}
// add the iteration cost prefix (currently fixed 07)
$this->cryptSaltPrefix .= chr(ord('0') + $this->cryptIterationCost / 10);
$this->cryptSaltPrefix .= chr(ord('0') + $this->cryptIterationCost % 10);
$this->cryptSaltPrefix .= '$';
$this->cryptSaltSuffix = '$';
} else {
// any version lower 5.3 we do check
if (CRYPT_MD5 == 1) {
$this->cryptSaltPrefix = '$1$';
$this->cryptSaltSize = 6;
$this->cryptSaltSuffix = '$';
} elseif (CRYPT_STD_DES == 1) {
// so I know this is standard DES, I prefix this with $ and have only one random char
$this->cryptSaltPrefix = '$';
$this->cryptSaltSize = 1;
$this->cryptSaltSuffix = '$';
} else {
// emergency fallback
$this->cryptSaltPrefix = '$0';
$this->cryptSaltSuffix = '$';
}
}
}
// [!!! DEPRECATED !!!] -> passwordInit
/**
* creates a random string from alphanumeric characters: A-Z a-z 0-9 ./
* @param integer $nSize random string length, default is 22 (for blowfish crypt)
* @return string random string
* @deprecated use passwordInit instead
*/
private function cryptSaltString(int $nSize = 22): string
{
trigger_error('Method '.__METHOD__.' is deprecated, use passwordInit', E_USER_DEPRECATED);
// A-Z is 65,90
// a-z is 97,122
// 0-9 is 48,57
// ./ is 46,47 (so first lower limit is 46)
$min = array(46, 65, 97);
$max = array(57, 90, 122);
$chars = array();
for ($i = 0, $iMax = count($min); $i < $iMax; $i ++) {
for ($j = $min[$i]; $j <= $max[$i]; $j ++) {
$chars[] = chr($j);
}
}
// max should be 63 for this case
$max_rand = count($chars) - 1;
$salt_string = '';
// create the salt part
for ($i = 1; $i <= $nSize; $i ++) {
$salt_string .= $chars[mt_rand(0, $max_rand)];
}
return $salt_string;
}
// [!!! DEPRECATED !!!] -> passwordSet
/**
* encrypts the string with blowfish and returns the full string + salt part that needs to be stored somewhere (eg DB)
* @param string $string string to be crypted (one way)
* @return string encrypted string
* @deprecated use passwordSet instead
*/
public function cryptString(string $string): string
{
trigger_error('Method '.__METHOD__.' is deprecated, use passwordSet', E_USER_DEPRECATED);
// the crypt prefix is set in the init of the class
// uses the random string method to create the salt
// suppress deprecated error, as this is an internal call
/** @phan-suppress-next-line PhanDeprecatedFunction */
return crypt($string, $this->cryptSaltPrefix.$this->cryptSaltString($this->cryptSaltSize).$this->cryptSaltSuffix);
}
// [!!! DEPRECATED !!!] -> passwordVerify
/**
* compares the string with the crypted one, is counter method to cryptString
* @param string $string plain string (eg password)
* @param string $crypt full crypted string (from cryptString
* @return bool true on matching or false for not matching
* @deprecated use passwordVerify instead
*/
public function verifyCryptString(string $string, string $crypt): bool
{
trigger_error('Method '.__METHOD__.' is deprecated, use passwordVerify', E_USER_DEPRECATED);
// the full crypted string needs to be passed on to the salt, so the init (for blowfish) and salt are passed on
if (crypt($string, $crypt) == $crypt) {
return true;
} else {
return false;
}
}
// *** BETTER PASSWORD OPTIONS, must be used ***
/**
* inits the password options set
* currently this is et empty, and the default options are used
* @return void has no reutrn
*/
private function passwordInit(): void
{
// set default password cost: use default set automatically
$this->password_options = array(
// 'cost' => PASSWORD_BCRYPT_DEFAULT_COST
);
}
// METHOD: passwordSet
// PARAMS: password
// RETURN: hashed password
// DESC : creates the password hash
/**
* creates the password hash
* @param string $password password
* @return string hashed password
*/
public function passwordSet(string $password): string
{
// always use the PHP default for the password
// password options ca be set in the password init, but should be kept as default
return password_hash($password, PASSWORD_DEFAULT, $this->password_options);
}
/**
* checks if the entered password matches the hash
* @param string $password password
* @param string $hash password hash
* @return bool true or false
*/
public function passwordVerify(string $password, string $hash): bool
{
if (password_verify($password, $hash)) {
return true;
} else {
return false;
}
// in case something strange, return false on default
return false;
}
/**
* checks if the password needs to be rehashed
* @param string $hash password hash
* @return bool true or false
*/
public function passwordRehashCheck(string $hash): bool
{
if (password_needs_rehash($hash, PASSWORD_DEFAULT, $this->password_options)) {
return true;
} else {
return false;
}
// in case of strange, force re-hash
return true;
}
// *** COLORS ***
/**
* converts a hex RGB color to the int numbers
* @param string $hexStr RGB hexstring
* @param bool $returnAsString flag to return as string
* @param string $seperator string seperator: default: ","
* @return string|array|bool false on error or array with RGB or a string with the seperator
*/
public static function hex2rgb(string $hexStr, bool $returnAsString = false, string $seperator = ',')
{
$hexStr = preg_replace("/[^0-9A-Fa-f]/", '', $hexStr); // Gets a proper hex string
$rgbArray = array();
if (strlen($hexStr) == 6) {
// If a proper hex code, convert using bitwise operation. No overhead... faster
$colorVal = hexdec($hexStr);
$rgbArray['R'] = 0xFF & ($colorVal >> 0x10);
$rgbArray['G'] = 0xFF & ($colorVal >> 0x8);
$rgbArray['B'] = 0xFF & $colorVal;
} elseif (strlen($hexStr) == 3) {
// If shorthand notation, need some string manipulations
$rgbArray['R'] = hexdec(str_repeat(substr($hexStr, 0, 1), 2));
$rgbArray['G'] = hexdec(str_repeat(substr($hexStr, 1, 1), 2));
$rgbArray['B'] = hexdec(str_repeat(substr($hexStr, 2, 1), 2));
} else {
// Invalid hex color code
return false;
}
// returns the rgb string or the associative array
return $returnAsString ? implode($seperator, $rgbArray) : $rgbArray;
}
/**
* converts the rgb values from int data to the valid rgb html hex string
* optional can turn of leading #
* @param int $red red 0-255
* @param int $green green 0-255
* @param int $blue blue 0-255
* @param bool $hex_prefix default true, prefix with "#"
* @return string rgb in hex values with leading # if set
*/
public static function rgb2hex(int $red, int $green, int $blue, bool $hex_prefix = true): string
{
$hex_color = '';
if ($hex_prefix === true) {
$hex_color = '#';
}
foreach (array('red', 'green', 'blue') as $color) {
// if not valid, set to gray
if ($$color < 0 || $$color > 255) {
$$color = 125;
}
// pad left with 0
$hex_color .= str_pad(dechex($$color), 2, '0', STR_PAD_LEFT);
}
return $hex_color;
}
/**
* converts and int RGB to the HTML color string in hex format
* @param int $red red 0-255
* @param int $green green 0-255
* @param int $blue blue 0-255
* @return string hex rgb string
* @deprecated use rgb2hex instead
*/
public static function rgb2html(int $red, int $green, int $blue): string
{
trigger_error('Method '.__METHOD__.' is deprecated, use rgb2hex', E_USER_DEPRECATED);
// check that each color is between 0 and 255
foreach (array('red', 'green', 'blue') as $color) {
if ($$color < 0 || $$color > 255) {
$$color = 125;
}
// convert to HEX value
$$color = dechex($$color);
// prefix with 0 if only one char
$$color = ((strlen($$color) < 2) ? '0' : '').$$color;
}
// prefix hex parts with 0 if they are just one char long and return the html color string
return '#'.$red.$green.$blue;
}
/**
* converts RGB to HSB/V values
* returns:
* array with hue (0-360), sat (0-100%), brightness/value (0-100%)
* @param int $r red 0-255
* @param int $g green 0-255
* @param int $b blue 0-255
* @return array Hue, Sat, Brightness/Value
*/
public static function rgb2hsb(int $r, int $g, int $b): array
{
// check that rgb is from 0 to 255
foreach (array('r', 'g', 'b') as $c) {
if ($$c < 0 || $$c > 255) {
$$c = 0;
}
$$c = $$c / 255;
}
$MAX = max($r, $g, $b);
$MIN = min($r, $g, $b);
$HUE = 0;
if ($MAX == $MIN) {
return array(0, 0, round($MAX * 100));
}
if ($r == $MAX) {
$HUE = ($g - $b) / ($MAX - $MIN);
} elseif ($g == $MAX) {
$HUE = 2 + (($b - $r) / ($MAX - $MIN));
} elseif ($b == $MAX) {
$HUE = 4 + (($r - $g) / ($MAX - $MIN));
}
$HUE *= 60;
if ($HUE < 0) {
$HUE += 360;
}
return array(round($HUE), round((($MAX - $MIN) / $MAX) * 100), round($MAX * 100));
}
/**
* converts HSB/V to RGB values RGB is full INT
* @param int $H hue 0-360
* @param float $S saturation 0-1 (float)
* @param float $V brightness/value 0-1 (float)
* @return array 0 red/1 green/2 blue array
*/
public static function hsb2rgb(int $H, float $S, float $V): array
{
// check that H is 0 to 359, 360 = 0
// and S and V are 0 to 1
if ($H < 0 || $H > 359 || $H == 360) {
$H = 0;
}
if ($S < 0 || $S > 1) {
$S = 0;
}
if ($V < 0 || $V > 1) {
$V = 0;
}
if ($S == 0) {
return array($V * 255, $V * 255, $V * 255);
}
$Hi = floor($H / 60);
$f = ($H / 60) - $Hi;
$p = $V * (1 - $S);
$q = $V * (1 - ($S * $f));
$t = $V * (1 - ($S * (1 - $f)));
switch ($Hi) {
case 0:
$red = $V;
$green = $t;
$blue = $p;
break;
case 1:
$red = $q;
$green = $V;
$blue = $p;
break;
case 2:
$red = $p;
$green = $V;
$blue = $t;
break;
case 3:
$red = $p;
$green = $q;
$blue = $V;
break;
case 4:
$red = $t;
$green = $p;
$blue = $V;
break;
case 5:
$red = $V;
$green = $p;
$blue = $q;
break;
default:
$red = 0;
$green = 0;
$blue = 0;
}
return array(round($red * 255), round($green * 255), round($blue * 255));
}
/**
* converts a RGB (0-255) to HSL
* return:
* array with hue (0-360), saturation (0-100%) and luminance (0-100%)
* @param int $r red 0-255
* @param int $g green 0-255
* @param int $b blue 0-255
* @return array hue/sat/luminance
*/
public static function rgb2hsl(int $r, int $g, int $b): array
{
// check that rgb is from 0 to 255
foreach (array('r', 'g', 'b') as $c) {
if ($$c < 0 || $$c > 255) {
$$c = 0;
}
$$c = $$c / 255;
}
$MIN = min($r, $g, $b);
$MAX = max($r, $g, $b);
$HUE = 0;
// luminance
$L = round((($MAX + $MIN) / 2) * 100);
if ($MIN == $MAX) {
// H, S, L
return array(0, 0, $L);
} else {
// HUE to 0~360
if ($r == $MAX) {
$HUE = ($g - $b) / ($MAX - $MIN);
} elseif ($g == $MAX) {
$HUE = 2 + (($b - $r) / ($MAX - $MIN));
} elseif ($b == $MAX) {
$HUE = 4 + (($r - $g) / ($MAX - $MIN));
}
$HUE *= 60;
if ($HUE < 0) {
$HUE += 360;
}
// H, S, L
// S= L <= 0.5 ? C/2L : C/2 - 2L
return array(round($HUE), round((($MAX - $MIN) / (($L <= 0.5) ? ($MAX + $MIN) : (2 - $MAX - $MIN))) * 100), $L);
}
}
/**
* converts an HSL to RGB
* @param int $h hue: 0-360 (degrees)
* @param float $s saturation: 0-1
* @param float $l luminance: 0-1
* @return array red/blue/green 0-255 each
*/
public static function hsl2rgb(int $h, float $s, float $l): array
{
$h = (1 / 360) * $h; // calc to internal convert value for hue
// if saturation is 0
if ($s == 0) {
return array($l * 255, $l * 255, $l * 255);
} else {
$m2 = ($l < 0.5) ? $l * ($s + 1) : ($l + $s) - ($l * $s);
$m1 = $l * 2 - $m2;
$hue = function ($base) use ($m1, $m2) {
// base = hue, hue > 360 (1) - 360 (1), else < 0 + 360 (1)
$base = ($base < 0) ? $base + 1 : (($base > 1) ? $base - 1 : $base);
// 6: 60, 2: 180, 3: 240
// 2/3 = 240
// 1/3 = 120 (all from 360)
if ($base * 6 < 1) {
return $m1 + ($m2 - $m1) * $base * 6;
}
if ($base * 2 < 1) {
return $m2;
}
if ($base * 3 < 2) {
return $m1 + ($m2 - $m1) * ((2 / 3) - $base) * 6;
}
return $m1;
};
return array(round(255 * $hue($h + (1 / 3))), round(255 * $hue($h)), round(255 * $hue($h - (1 / 3))));
}
}
/**
* guesses the email type (mostly for mobile) from the domain
* if second is set to true, it will return short naming scheme (only provider)
* @param string $email email string
* @param bool $short default false, if true, returns only short type (pc instead of pc_html)
* @return string|bool email type, eg "pc", "docomo", etc, false for invalid short type
*/
public function getEmailType(string $email, bool $short = false)
{
// trip if there is no email address
if (!$email) {
return 'invalid';
}
// loop until we match a mobile type, return this first found type
foreach ($this->mobile_email_type as $email_regex => $email_type) {
if (preg_match("/$email_regex/", $email)) {
if ($short) {
return $this->getShortEmailType($email_type);
} else {
return $email_type;
}
}
}
// if no previous return we assume this is a pc address
if ($short) {
return 'pc';
} else {
return 'pc_html';
}
}
/**
* gets the short email type from a long email type
* @param string $email_type email string
* @return string|bool short string or false for invalid
*/
public function getShortEmailType(string $email_type)
{
// check if the short email type exists
if (isset($this->mobile_email_type_short[$email_type])) {
return $this->mobile_email_type_short[$email_type];
} else {
// return false on not found
return false;
}
}
/**
* print the date/time drop downs, used in any queue/send/insert at date/time place
* @param int $year year YYYY
* @param int $month month m
* @param int $day day d
* @param int $hour hour H
* @param int $min min i
* @param string $suffix additional info printed after the date time variable in the drop down
* also used for ID in the on change JS call
* @param int $min_steps default is 1 (minute), can set to anything, is used as sum up from 0
* @param bool $name_pos_back default false, if set to true, the name will be printend
* after the drop down and not before the drop down
* @return string HTML formated strings for drop down lists of date and time
*/
public static function printDateTime($year, $month, $day, $hour, $min, string $suffix = '', int $min_steps = 1, bool $name_pos_back = false)
{
// if suffix given, add _ before
if ($suffix) {
$suffix = '_'.$suffix;
}
if ($min_steps < 1 || $min_steps > 59) {
$min_steps = 1;
}
$on_change_call = 'dt_list(\''.$suffix.'\');';
// always be 1h ahead (for safety)
$timestamp = time() + 3600; // in seconds
// the max year is this year + 1;
$max_year = (int)date("Y", $timestamp) + 1;
// preset year, month, ...
$year = (!$year) ? date("Y", $timestamp) : $year;
$month = (!$month) ? date("m", $timestamp) : $month;
$day = (!$day) ? date("d", $timestamp) : $day;
$hour = (!$hour) ? date("H", $timestamp) : $hour;
$min = (!$min) ? date("i", $timestamp) : $min; // add to five min?
// max days in selected month
$days_in_month = date("t", strtotime($year."-".$month."-".$day." ".$hour.":".$min.":0"));
$string = '';
// from now to ?
if ($name_pos_back === false) {
$string = 'Year ';
}
$string .= '<select id="year'.$suffix.'" name="year'.$suffix.'" onChange="'.$on_change_call.'">';
for ($i = date("Y"); $i <= $max_year; $i ++) {
$string .= '<option value="'.$i.'" '.(($year == $i) ? 'selected' : '').'>'.$i.'</option>';
}
$string .= '</select> ';
if ($name_pos_back === true) {
$string .= 'Year ';
}
if ($name_pos_back === false) {
$string .= 'Month ';
}
$string .= '<select id="month'.$suffix.'" name="month'.$suffix.'" onChange="'.$on_change_call.'">';
for ($i = 1; $i <= 12; $i ++) {
$string .= '<option value="'.(($i < 10) ? '0'.$i : $i).'" '.(($month == $i) ? 'selected' : '').'>'.$i.'</option>';
}
$string .= '</select> ';
if ($name_pos_back === true) {
$string .= 'Month ';
}
if ($name_pos_back === false) {
$string .= 'Day ';
}
$string .= '<select id="day'.$suffix.'" name="day'.$suffix.'" onChange="'.$on_change_call.'">';
for ($i = 1; $i <= $days_in_month; $i ++) {
// set weekday text based on current month ($month) and year ($year)
$string .= '<option value="'.(($i < 10) ? '0'.$i : $i).'" '.(($day == $i) ? 'selected' : '').'>'.$i.' ('.date('D', mktime(0, 0, 0, $month, $i, $year)).')</option>';
}
$string .= '</select> ';
if ($name_pos_back === true) {
$string .= 'Day ';
}
if ($name_pos_back === false) {
$string .= 'Hour ';
}
$string .= '<select id="hour'.$suffix.'" name="hour'.$suffix.'" onChange="'.$on_change_call.'">';
for ($i = 0; $i <= 23; $i += $min_steps) {
$string .= '<option value="'.(($i < 10) ? '0'.$i : $i).'" '.(($hour == $i) ? 'selected' : '').'>'.$i.'</option>';
}
$string .= '</select> ';
if ($name_pos_back === true) {
$string .= 'Hour ';
}
if ($name_pos_back === false) {
$string .= 'Minute ';
}
$string .= '<select id="min'.$suffix.'" name="min'.$suffix.'" onChange="'.$on_change_call.'">';
for ($i = 0; $i <= 59; $i ++) {
$string .= '<option value="'.(( $i < 10) ? '0'.$i : $i).'" '.(($min == $i) ? 'selected' : '').'>'.$i.'</option>';
}
$string .= '</select>';
if ($name_pos_back === true) {
$string .= ' Minute ';
}
// return the datetime select string
return $string;
}
/**
* full wrapper for html entities
* @param string $string string to html encode
* @return mixed if string, encoded, else as is
*/
public function htmlent(string $string)
{
if (is_string($string)) {
return htmlentities($string, ENT_COMPAT|ENT_HTML401, 'UTF-8', false);
} else {
return $string;
}
}
/**
* strips out all line breaks or replaced with given string
* @param string $string string
* @param string $replace replace character, default ' '
* @return string cleaned string without any line breaks
*/
public function removeLB(string $string, string $replace = ' '): string
{
return str_replace(array("\r", "\n"), $replace, $string);
}
/**
* some float numbers will be rounded up even if they have no decimal entries
* this function fixes this by pre-rounding before calling ceil
* @param float $number number to round
* @param int|integer $precision intermediat round up decimals (default 10)
* @return float correct ceil number
*/
public function fceil(float $number, int $precision = 10): float
{
return ceil(round($number, $precision));
}
/**
* round inside an a number, not the decimal part only
* eg 48767 with -2 -> 48700
* @param float $number number to round
* @param int $precision negative number for position in number (default -2)
* @return float rounded number
*/
public function floorp(float $number, int $precision = -2): float
{
$mult = pow(10, $precision); // Can be cached in lookup table
return floor($number * $mult) / $mult;
}
/**
* inits input to 0, if value is not numeric
* @param string|int|float $number string or number to check
* @return float if not number, then returns 0, else original input
*/
public function initNumeric($number): float
{
if (!is_numeric($number)) {
return 0;
} else {
return $number;
}
}
/**
* sets a form token in a session and returns form token
* @param string $name optional form name, default form_token
* @return string token name for given form id string
*/
public function setFormToken(string $name = 'form_token'): string
{
// current hard set to sha256
$token = uniqid(hash('sha256', (string)rand()));
$_SESSION[$name] = $token;
return $token;
}
/**
* checks if the form token matches the session set form token
* @param string $token token string to check
* @param string $name optional form name to check to, default form_token
* @return bool false if not set, or true/false if matching or not mtaching
*/
public function validateFormToken(string $token, string $name = 'form_token'): bool
{
if (isset($_SESSION[$name])) {
return $_SESSION[$name] === $token;
} else {
return false;
}
}
}
// __END__