From b3d783bf6333d61fe432c0e952218b2c537c3b57 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 11 Oct 2021 09:40:01 +0900 Subject: [PATCH] Add .env reading flow in config.php Will check if there is a read_env_file.php and then run it to load .env file in /configs folder This file can hold secrets that are not to be checked into git Updated edit.js file to be eslint compatible --- .../function/edit_log_partition_insert.sql | 2 +- www/admin/class_test.php | 4 + www/configs/.env.example | 8 ++ www/configs/config.db.php | 10 +- www/configs/config.master.php | 7 +- www/configs/config.php | 7 + www/configs/read_env_file.php | 84 +++++++++++ www/layout/admin/javascript/edit.jq.js | 133 ++++++++++++------ www/lib/CoreLibs/DB/IO.php | 3 +- 9 files changed, 203 insertions(+), 55 deletions(-) create mode 100644 www/configs/.env.example create mode 100644 www/configs/read_env_file.php diff --git a/4dev/database/function/edit_log_partition_insert.sql b/4dev/database/function/edit_log_partition_insert.sql index 70255f52..ea88f1c3 100644 --- a/4dev/database/function/edit_log_partition_insert.sql +++ b/4dev/database/function/edit_log_partition_insert.sql @@ -147,4 +147,4 @@ BEGIN RETURN NULL; END $$ -LANGUAGE 'plpgsql' +LANGUAGE 'plpgsql'; diff --git a/www/admin/class_test.php b/www/admin/class_test.php index 97574db4..dedb5b71 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -64,6 +64,10 @@ print '
Class Test: BACKEND ADMIN CLASS print '
Class Test: LANG/L10n
'; print '
Class Test: SMARTY
'; +print "
"; +// print all _ENV vars set +print "
READ _ENV ARRAY:
"; +print "
" . print_r(array_map('htmlentities', $_ENV), true) . "
"; // set + check edit access id $edit_access_id = 3; if (is_object($login) && isset($login->acl['unit'])) { diff --git a/www/configs/.env.example b/www/configs/.env.example new file mode 100644 index 00000000..7f0e86df --- /dev/null +++ b/www/configs/.env.example @@ -0,0 +1,8 @@ +# Master configs +BASE_NAME= +G_TITLE= +# DB configs +DB_NAME_TEST= +DB_USER_TEST= +DB_PASS_TEST= +DB_HOST_TEST= diff --git a/www/configs/config.db.php b/www/configs/config.db.php index 3df05e29..b3e094cd 100755 --- a/www/configs/config.db.php +++ b/www/configs/config.db.php @@ -13,15 +13,15 @@ declare(strict_types=1); // please be VERY carefull only to change the right side $DB_CONFIG = [ 'test' => [ - 'db_name' => 'clemens', - 'db_user' => 'clemens', - 'db_pass' => 'clemens', - 'db_host' => 'db.tokyo.tequila.jp', + 'db_name' => $_ENV['DB_NAME_TEST'] ?? '', + 'db_user' => $_ENV['DB_USER_TEST'] ?? '', + 'db_pass' => $_ENV['DB_PASS_TEST'] ?? '', + 'db_host' => $_ENV['DB_HOST_TEST'] ?? '', 'db_port' => 5432, 'db_schema' => 'public', 'db_type' => 'pgsql', 'db_encoding' => '', - 'db_ssl' => 'disable' // allow, disable, require, prefer + 'db_ssl' => 'allow' // allow, disable, require, prefer ], ]; diff --git a/www/configs/config.master.php b/www/configs/config.master.php index 2c6b07f2..f82ce964 100644 --- a/www/configs/config.master.php +++ b/www/configs/config.master.php @@ -84,7 +84,7 @@ define('DEFAULT_HASH', 'sha256'); // default acl level define('DEFAULT_ACL_LEVEL', 80); // SSL host name -// define('SSL_HOST', 'ssl.host.name'); +// define('SSL_HOST', $_ENV['SSL_HOST'] ?? ''); // error page strictness, Default is 3 // 1: only show error page as the last mesure if really no mid & aid can be loaded and found at all // 2: if template not found, do not search, show error template @@ -139,7 +139,8 @@ define('MASTER_TEMPLATE_NAME', 'main_body.tpl'); /************* OVERALL CONTROL NAMES *************/ // BELOW has HAS to be changed // base name for all session and log names -define('BASE_NAME', 'CoreLibs'); +// only alphanumeric characters, strip all others +define('BASE_NAME', preg_replace('/[^A-Za-z0-9]/', '', $_ENV['BASE_NAME'] ?? '')); /************* SESSION NAMES *************/ // server name HASH @@ -266,7 +267,7 @@ define('LOGIN_ENABLED', $SITE_CONFIG[HOST_NAME]['login_enabled']); define('SHOW_ALL_ERRORS', true); /************* GENERAL PAGE TITLE ********/ -define('G_TITLE', ''); +define('G_TITLE', $_ENV['G_TITLE'] ?? ''); /************ STYLE SHEETS / JS **********/ define('ADMIN_STYLESHEET', 'edit.css'); diff --git a/www/configs/config.php b/www/configs/config.php index bee9c16b..eb2736e5 100755 --- a/www/configs/config.php +++ b/www/configs/config.php @@ -16,6 +16,13 @@ $CONFIG_PATH_PREFIX = ''; for ($dir_pos = 0, $dir_max = count(explode(DIRECTORY_SEPARATOR, __DIR__)); $dir_pos <= $dir_max; $dir_pos++) { $CONFIG_PATH_PREFIX .= '..' . DIRECTORY_SEPARATOR; if (file_exists($CONFIG_PATH_PREFIX . CONFIG_PATH . 'config.master.php')) { + // check if there is an read env file, load it + if (file_exists($CONFIG_PATH_PREFIX . CONFIG_PATH . 'read_env_file.php')) { + require $CONFIG_PATH_PREFIX . CONFIG_PATH . 'read_env_file.php'; + // load env variables first + readEnvFile($CONFIG_PATH_PREFIX . CONFIG_PATH); + } + // then load master config file that loads all other config files require $CONFIG_PATH_PREFIX . CONFIG_PATH . 'config.master.php'; break; } diff --git a/www/configs/read_env_file.php b/www/configs/read_env_file.php new file mode 100644 index 00000000..400ef7eb --- /dev/null +++ b/www/configs/read_env_file.php @@ -0,0 +1,84 @@ + abort + if (!is_file($env_file_target)) { + $status = 3; + return $status; + } + // cannot open file -> abort + if (($fp = fopen($env_file_target, 'r')) === false) { + $status = 2; + return $status; + } + // set to readable but not yet any data loaded + $status = 1; + $block = false; + $var = ''; + while ($line = fgets($fp)) { + // main match for variable = value part + if (preg_match("/^\s*([\w_]+)\s*=\s*((\"?).*)/", $line, $matches)) { + $var = $matches[1]; + $value = $matches[2]; + $quotes = $matches[3]; + // wirte only if env is not set yet, and write only the first time + if (empty($_ENV[$var])) { + if (!empty($quotes)) { + // match greedy for first to last so we move any " if there are + if (preg_match('/^"(.*[^\\\])"/U', $value, $matches)) { + $value = $matches[1]; + } else { + // this is a multi line + $block = true; + // first " in string remove + // add removed new line back because this is a multi line + $value = ltrim($value, '"') . PHP_EOL; + } + } + // if block is set, we strip line of slashes + $_ENV[$var] = $block === true ? stripslashes($value) : $value; + // set successful load + $status = 0; + } + } elseif ($block === true) { + // read line until there is a unescaped " + // this also strips everything after the last " + if (preg_match("/(.*[^\\\])\"/", $line, $matches)) { + $block = false; + // strip ending " and EVERYTHING that follows after that + $line = $matches[1]; + } + // strip line of slashes + $_ENV[$var] .= stripslashes($line); + } + } + fclose($fp); + return $status; +} + +// __END__ diff --git a/www/layout/admin/javascript/edit.jq.js b/www/layout/admin/javascript/edit.jq.js index 682af82e..10d4e503 100644 --- a/www/layout/admin/javascript/edit.jq.js +++ b/www/layout/admin/javascript/edit.jq.js @@ -3,6 +3,8 @@ /* jshint esversion: 6 */ +/* global i18n */ + // debug set /*var FRONTEND_DEBUG = false; var DEBUG = true; @@ -22,7 +24,8 @@ var GL_OB_BASE = 30; * @param {String} winName window name * @param {Object} features popup features */ -function pop(theURL, winName, features) { +function pop(theURL, winName, features) // eslint-disable-line no-unused-vars +{ winName = window.open(theURL, winName, features); winName.focus(); } @@ -31,7 +34,8 @@ function pop(theURL, winName, features) { * automatically resize a text area based on the amount of lines in it * @param {[string} ta_id element id */ -function expandTA(ta_id) { +function expandTA(ta_id) // eslint-disable-line no-unused-vars +{ var ta; // if a string comes, its a get by id, else use it as an element pass on if (!ta_id.length) { @@ -124,7 +128,7 @@ function setCenter(id, left, top) * @param {Number} [duration=500] animation time, default 500ms * @param {String} [base='body,html'] base element for offset scroll */ -function goToPos(element, offset = 0, duration = 500, base = 'body,html') +function goToPos(element, offset = 0, duration = 500, base = 'body,html') // eslint-disable-line no-unused-vars { try { if ($('#' + element).length) { @@ -190,7 +194,7 @@ if (Number.prototype.round) { * @param {Number} x number to be formated * @return {String} formatted with , in thousands */ -function numberWithCommas(x) +function numberWithCommas(x) // eslint-disable-line no-unused-vars { var parts = x.toString().split('.'); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); @@ -202,7 +206,7 @@ function numberWithCommas(x) * @param {String} string any string * @return {String} string with
*/ -function convertLBtoBR(string) +function convertLBtoBR(string) // eslint-disable-line no-unused-vars { return string.replace(/(?:\r\n|\r|\n)/g, '
'); } @@ -255,7 +259,7 @@ if (!String.prototype.unescapeHTML) { * returns current timestamp (unix timestamp) * @return {Number} timestamp (in milliseconds) */ -function getTimestamp() +function getTimestamp() // eslint-disable-line no-unused-vars { var date = new Date(); return date.getTime(); @@ -278,7 +282,7 @@ function dec2hex(dec) * @param {Number} len length of unique id string * @return {String} random string in length of len */ -function generateId(len) +function generateId(len) // eslint-disable-line no-unused-vars { var arr = new Uint8Array((len || 40) / 2); (window.crypto || window.msCrypto).getRandomValues(arr); @@ -291,7 +295,7 @@ function generateId(len) * after many runs it will create duplicates * @return {String} not true random string */ -function randomIdF() +function randomIdF() // eslint-disable-line no-unused-vars { return Math.random().toString(36).substring(2); } @@ -301,7 +305,7 @@ function randomIdF() * @param {string} name Name of function to check if exists * @return {Boolean} true/false */ -function isFunction(name) +function isFunction(name) // eslint-disable-line no-unused-vars { if (typeof window[name] !== 'undefined' && typeof window[name] === 'function') { @@ -320,7 +324,7 @@ function isFunction(name) * hidden next are all the arguments * @return {mixed} Return values from functon */ -function executeFunctionByName(functionName, context /*, args */) +function executeFunctionByName(functionName, context /*, args */) // eslint-disable-line no-unused-vars { var args = Array.prototype.slice.call(arguments, 2); var namespaces = functionName.split('.'); @@ -362,7 +366,7 @@ function getObjectCount(object) */ function keyInObject(key, object) { - return (Object.prototype.hasOwnProperty.call(object, key)) ? true : false; + return Object.prototype.hasOwnProperty.call(object, key) ? true : false; } /** @@ -371,7 +375,7 @@ function keyInObject(key, object) * @param {Mixed} value any value (String, Number, etc) * @return {String} the key found for the first matching value */ -function getKeyByValue(object, value) +function getKeyByValue(object, value) // eslint-disable-line no-unused-vars { return Object.keys(object).find(key => object[key] === value); // return Object.keys(object).find(function (key) { @@ -385,9 +389,9 @@ function getKeyByValue(object, value) * @param {Mixed} value any value (String, Number, etc) * @return {Boolean} true on value found, false on not found */ -function valueInObject(object, value) +function valueInObject(object, value) // eslint-disable-line no-unused-vars { - return (Object.keys(object).find(key => object[key] === value)) ? true : false; + return Object.keys(object).find(key => object[key] === value) ? true : false; // return Object.keys(object).find(function (key) { // return object[key] === value; // }) ? true : false; @@ -434,7 +438,7 @@ function exists(id) * @param {Number} bytes bytes in int * @return {String} string in GB/MB/KB */ -function formatBytes(bytes) +function formatBytes(bytes) // eslint-disable-line no-unused-vars { var i = -1; do { @@ -450,7 +454,7 @@ function formatBytes(bytes) * @param {Number} bytes bytes in int * @return {String} string in GB/MB/KB */ -function formatBytesLong(bytes) +function formatBytesLong(bytes) // eslint-disable-line no-unused-vars { var i = Math.floor(Math.log(bytes) / Math.log(1024)); var sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; @@ -462,7 +466,7 @@ function formatBytesLong(bytes) * @param {String|Number} bytes Any string with B/K/M/etc * @return {String|Number} A byte number, or original string as is */ -function stringByteFormat(bytes) +function stringByteFormat(bytes) // eslint-disable-line no-unused-vars { // if anything not string return if (!(typeof bytes === 'string' || bytes instanceof String)) { @@ -537,7 +541,7 @@ function errorCatch(err) * default empty. for console.log * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block */ -function actionIndicator(loc, overlay = true) +function actionIndicator(loc, overlay = true) // eslint-disable-line no-unused-vars { if ($('#indicator').is(':visible')) { actionIndicatorHide(loc, overlay); @@ -614,7 +618,7 @@ function overlayBoxHide() /** * position the overlay block box and shows it */ -function setOverlayBox() +function setOverlayBox() // eslint-disable-line no-unused-vars { if (!$('#overlayBox').is(':visible')) { $('#overlayBox').show(); @@ -624,7 +628,7 @@ function setOverlayBox() /** * opposite of set, always hides overlay box */ -function hideOverlayBox() +function hideOverlayBox() // eslint-disable-line no-unused-vars { if ($('#overlayBox').is(':visible')) { $('#overlayBox').hide(); @@ -634,7 +638,7 @@ function hideOverlayBox() /** * the abort call, clears the action box and hides it and the overlay box */ -function ClearCall() +function ClearCall() // eslint-disable-line no-unused-vars { $('#actionBox').html(''); $('#actionBox').hide(); @@ -655,7 +659,7 @@ function ClearCall() * - indicator is page centered * @param {String} loc ID string, only used for console log */ -function showActionIndicator(loc) +function showActionIndicator(loc) // eslint-disable-line no-unused-vars { // console.log('Indicator: SHOW [%s]', loc); // check if indicator element exists @@ -693,7 +697,7 @@ function showActionIndicator(loc) * is set to this value * @param {String} loc ID string, only used for console log */ -function hideActionIndicator(loc) +function hideActionIndicator(loc) // eslint-disable-line no-unused-vars { // console.log('Indicator: HIDE [%s]', loc); // check if indicator is visible @@ -733,7 +737,7 @@ function checkOverlayExists() * if visible, add +1 to the GL_OB_S variable and * up zIndex by this value */ -function showOverlayBoxLayers(el_id) +function showOverlayBoxLayers(el_id) // eslint-disable-line no-unused-vars { // console.log('SHOW overlaybox: %s', GL_OB_S); // if overlay box is not visible show and set zIndex to 0 @@ -790,7 +794,7 @@ function hideOverlayBoxLayers(el_id) /** * only for single action box */ -function clearCallActionBox() +function clearCallActionBox() // eslint-disable-line no-unused-vars { $('#actionBox').html(''); $('#actionBox').hide(); @@ -873,7 +877,7 @@ function aelx(base, ...attach) * @param {Array} attach array of objects to attach * @return {Object} "none", technically there is no return needed, global attach */ -function aelxar(base, attach) +function aelxar(base, attach) // eslint-disable-line no-unused-vars { for (var i = 0; i < attach.length; i ++) { // base.sub.push(Object.assign({}, attach[i])); @@ -887,7 +891,7 @@ function aelxar(base, attach) * @param {Object} base cel created element * @return {Object} returns reset base element */ -function rel(base) +function rel(base) // eslint-disable-line no-unused-vars { base.sub = []; return base; @@ -931,7 +935,7 @@ function acssel(_element, css) * @param {String} acss style to add (name) * @return {Object} returns full element */ -function scssel(_element, rcss, acss) +function scssel(_element, rcss, acss) // eslint-disable-line no-unused-vars { rcssel(_element, rcss); acssel(_element, acss); @@ -1009,7 +1013,7 @@ function phfo(tree) * @param {Array} list Array of cel created objects * @return {String} HTML String */ -function phfa(list) +function phfa(list) // eslint-disable-line no-unused-vars { var content = []; for (var i = 0; i < list.length; i ++) { @@ -1035,7 +1039,7 @@ function phfa(list) * 'values' all others are ignored * @return {String} html with build options block */ -function html_options(name, data, selected = '', options_only = false, return_string = false, sort = '') +function html_options(name, data, selected = '', options_only = false, return_string = false, sort = '') // eslint-disable-line no-unused-vars { // wrapper to new call return html_options_block(name, data, selected, false, options_only, return_string, sort); @@ -1141,7 +1145,7 @@ function html_options_block(name, data, selected = '', multiple = 0, options_onl * @param {String} [sort=''] if empty as is, else allowed 'keys', 'values' * all others are ignored */ -function html_options_refill(name, data, sort = '') +function html_options_refill(name, data, sort = '') // eslint-disable-line no-unused-vars { var element_option; var option_selected; @@ -1188,7 +1192,8 @@ function html_options_refill(name, data, sort = '') * or empty for none * @return {Object|String} parameter entry list */ -function parseQueryString(query = '', return_key = '') { +function parseQueryString(query = '', return_key = '') // eslint-disable-line no-unused-vars +{ if (!query) { query = window.location.search.substring(1); } @@ -1198,6 +1203,10 @@ function parseQueryString(query = '', return_key = '') { var pair = vars[i].split('='); var key = decodeURIComponent(pair[0]); var value = decodeURIComponent(pair[1]); + // skip over run if there is nothing + if (!key || value === 'undefined') { + continue; + } // If first entry with this name if (typeof query_string[key] === 'undefined') { query_string[key] = decodeURIComponent(value); @@ -1222,26 +1231,59 @@ function parseQueryString(query = '', return_key = '') { } /** - * searchs the current url for a parameter - * @param {String} key uid key to get data for - * @return {String} value for the key or '' for not found + * searches query parameters for entry and returns data either as string or array + * if no search is given the whole parameters are returned as an object + * if a parameter is set several times it will be returned as an array + * if search parameter set and nothing found and empty string is returned + * if no parametes exist and no serach is set and empty object is returned + * @param {String} [search=''] if set searches for this entry, if empty + * all parameters are returned + * @param {String} [query=''] different query string to parse, if not + * set (default) the current window href is used + * @param {Bool} [single=false] if set to true then only the first found + * will be returned + * @return {Object|Array|String} if search is empty, object, if search is set + * and only one entry, then string, else array + * unless single is true */ -function getQueryStringParam(key) +function getQueryStringParam(search = '', query = '', single = false) // eslint-disable-line no-unused-vars { - var url = new URL(window.location.href); - var param = url.searchParams.get(key); - if (param) { - return param; - } else { - return ''; + if (!query) { + query = window.location.href; } + const url = new URL(query); + let param = ''; + if (search) { + let _params = url.searchParams.getAll(search); + if (_params.length == 1 || single === true) { + param = _params[0]; + } else if (_params.length > 1) { + param = _params; + } + } else { + // will be object, so declare it one + param = {}; + // loop over paramenters + for (const [key] of url.searchParams.entries()) { + // check if not yet set + if (typeof param[key] === 'undefined') { + // get the parameters multiple + let _params = url.searchParams.getAll(key); + // if 1 set as string, else attach array as is + param[key] = _params.length < 2 || single === true ? + _params[0] : + _params; + } + } + } + return param; } // *** MASTER logout call /** * submits basic data for form logout */ -function loginLogout() +function loginLogout() // eslint-disable-line no-unused-vars { const form = document.createElement('form'); form.method = 'post'; @@ -1261,7 +1303,7 @@ function loginLogout() * if not set mainHeader is assumed * this is the target div for the "loginRow" */ -function createLoginRow(login_string, header_id = 'mainHeader') +function createLoginRow(login_string, header_id = 'mainHeader') // eslint-disable-line no-unused-vars { // if header does not exist, we do nothing if (exists(header_id)) { @@ -1295,7 +1337,8 @@ function createLoginRow(login_string, header_id = 'mainHeader') * if not set mainHeader is assumed * this is the target div for the "menuRow" */ -function createNavMenu(nav_menu, header_id = 'mainHeader') { +function createNavMenu(nav_menu, header_id = 'mainHeader') // eslint-disable-line no-unused-vars +{ // must be an object if (isObject(nav_menu) && getObjectCount(nav_menu) > 1) { // do we have more than one entry, if not, do not show (single page) diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index d4bc4ee4..14ed4260 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -722,7 +722,8 @@ class IO extends \CoreLibs\Basic $this->__dbError(); return false; } - // if we do have an insert, check if there is no RETURNING pk_id, add it if I can get the PK id + // if we do have an insert, check if there is no RETURNING pk_id, + // add it if I can get the PK id if ($this->__checkQueryForInsert($this->query, true)) { $this->pk_name = $pk_name; if ($this->pk_name != 'NULL') {