diff --git a/4dev/database/ORDER b/4dev/database/ORDER index eb373885..c9b5c0a6 100644 --- a/4dev/database/ORDER +++ b/4dev/database/ORDER @@ -16,6 +16,7 @@ table/edit_scheme.sql table/edit_language.sql table/edit_group.sql table/edit_page_access.sql +table/edit_page_content.sql table/edit_user.sql table/edit_log.sql table/edit_access.sql @@ -31,6 +32,7 @@ trigger/trg_edit_group.sql trigger/trg_edit_language.sql trigger/trg_edit_log.sql trigger/trg_edit_page_access.sql +trigger/trg_edit_page_content.sql trigger/trg_edit_page.sql trigger/trg_edit_query_string.sql trigger/trg_edit_scheme.sql diff --git a/www/admin/class_test.php b/www/admin/class_test.php index c76ab10b..e1cb12c4 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -131,20 +131,20 @@ print "UPDATE STATUS: $status | RETURNING EXT: ".print_r($basic->insert_id_ext, $table = 'foo'; print "TABLE META DATA: ".$basic->printAr($basic->dbShowTableMetaData($table))."
"; $primary_key = ''; # unset -$db_write_table = array ('test', 'string_a', 'number_a', 'some_bool'); -// $db_write_table = array ('test'); -$object_fields_not_touch = array (); -$object_fields_not_update = array (); -$data = array ('test' => 'BOOL TEST SOMETHING '.time(), 'string_a' => 'SOME TEXT', 'number_a' => 5); +$db_write_table = array('test', 'string_a', 'number_a', 'some_bool'); +// $db_write_table = array('test'); +$object_fields_not_touch = array(); +$object_fields_not_update = array(); +$data = array('test' => 'BOOL TEST SOMETHING '.time(), 'string_a' => 'SOME TEXT', 'number_a' => 5); $primary_key = $basic->dbWriteDataExt($db_write_table, $primary_key, $table, $object_fields_not_touch, $object_fields_not_update, $data); print "Wrote to DB tabel $table and got primary key $primary_key
"; -$data = array ('test' => 'BOOL TEST ON '.time(), 'string_a' => '', 'number_a' => 0, 'some_bool' => 1); +$data = array('test' => 'BOOL TEST ON '.time(), 'string_a' => '', 'number_a' => 0, 'some_bool' => 1); $primary_key = $basic->dbWriteDataExt($db_write_table, $primary_key, $table, $object_fields_not_touch, $object_fields_not_update, $data); print "Wrote to DB tabel $table and got primary key $primary_key
"; -$data = array ('test' => 'BOOL TEST OFF '.time(), 'string_a' => null, 'number_a' => null, 'some_bool' => 0); +$data = array('test' => 'BOOL TEST OFF '.time(), 'string_a' => null, 'number_a' => null, 'some_bool' => 0); $primary_key = $basic->dbWriteDataExt($db_write_table, $primary_key, $table, $object_fields_not_touch, $object_fields_not_update, $data); print "Wrote to DB tabel $table and got primary key $primary_key
"; -$data = array ('test' => 'BOOL TEST UNSET '.time()); +$data = array('test' => 'BOOL TEST UNSET '.time()); $primary_key = $basic->dbWriteDataExt($db_write_table, $primary_key, $table, $object_fields_not_touch, $object_fields_not_update, $data); print "Wrote to DB tabel $table and got primary key $primary_key
"; @@ -236,8 +236,52 @@ $date_1 = '2017/1/5'; $date_2 = '2017-01-05'; print "COMPARE DATE: ".$basic->compareDate($date_1, $date_2)."
"; +// recursive array search +$test_array = array( + 'foo' => 'bar', + 'input' => array( + 'element_a' => array( + 'type' => 'text' + ), + 'element_b' => array( + 'type' => 'email' + ), + 'element_c' => array( + 'type' => 'email' + ) + ) +); -// array re +echo "SOURCE ARRAY: ".$basic->printAr($test_array)."
"; +echo "FOUND ELEMENTS [base]: ".$basic->printAr($basic->arraySearchRecursive('email', $test_array, 'type'))."
"; +echo "FOUND ELEMENTS [input]: ".$basic->printAr($basic->arraySearchRecursive('email', $test_array['input'], 'type'))."
"; + +// image thumbnail +$images = array( + // height bigger + // BASE.LAYOUT.CONTENT_PATH.IMAGES.'no_picture.jpg', + // BASE.LAYOUT.CONTENT_PATH.IMAGES.'no_picture.png', + // width bigger + // BASE.LAYOUT.CONTENT_PATH.IMAGES.'no_picture_width_bigger.jpg', + // BASE.LAYOUT.CONTENT_PATH.IMAGES.'no_picture_width_bigger.png', + // square + // BASE.LAYOUT.CONTENT_PATH.IMAGES.'no_picture_square.jpg', + // BASE.LAYOUT.CONTENT_PATH.IMAGES.'no_picture_square.png', + // other sample images + BASE.LAYOUT.CONTENT_PATH.IMAGES.'5c501af48da6c.jpg' +); +echo "
"; +$thumb_width = 250; +$thumb_height = 250; +foreach ($images as $image) { + // rotate image first + $basic->correctImageOrientation($image); + // thumbnail tests + echo "
".basename($image).": WIDTH: $thumb_width
"; + echo "
".basename($image).": HEIGHT: $thumb_height
"; + echo "
".basename($image).": WIDTH/HEIGHT: $thumb_width x $thumb_height
"; + echo "
"; +} // print error messages // print $login->printErrorMsg(); diff --git a/www/configs/config.master.php b/www/configs/config.master.php index 1db3e888..8fbc1ad4 100644 --- a/www/configs/config.master.php +++ b/www/configs/config.master.php @@ -124,18 +124,21 @@ DEFINE('PAGE_WIDTH', 800); DEFINE('MASTER_TEMPLATE_NAME', 'main_body.tpl'); /************* SESSION NAMES *************/ +// base name +DEFINE('BASE_NAME', 'CoreLibs'); // server name HASH DEFINE('SERVER_NAME_HASH', hash('crc32b', $_SERVER['HTTP_HOST'])); +DEFINE('SERVER_PATH_HASH', hash('crc32b', BASE)); // backend -DEFINE('EDIT_SESSION_NAME', 'ADMIN_SESSION_NAME'.SERVER_NAME_HASH); +DEFINE('EDIT_SESSION_NAME', BASE_NAME.'Admin'.SERVER_NAME_HASH.SERVER_PATH_HASH); // frontend -DEFINE('SESSION_NAME', 'SESSION_NAME'.SERVER_NAME_HASH); +DEFINE('SESSION_NAME', BASE_NAME.SERVER_NAME_HASH.SERVER_PATH_HASH); // SET_SESSION_NAME should be set in the header if a special session name is needed DEFINE('SET_SESSION_NAME', SESSION_NAME); /************* CACHE/COMPILE IDS *************/ -DEFINE('CACHE_ID', 'CACHE_'.SERVER_NAME_HASH); -DEFINE('COMPILE_ID', 'COMPILE_'.SERVER_NAME_HASH); +DEFINE('CACHE_ID', 'CACHE_'.BASE_NAME.'_'.SERVER_NAME_HASH); +DEFINE('COMPILE_ID', 'COMPILE_'.BASE_NAME.'_'.SERVER_NAME_HASH); /************* LANGUAGE / ENCODING *******/ DEFINE('DEFAULT_LANG', 'en_utf8'); diff --git a/www/layout/admin/cache/.gitignore b/www/layout/admin/cache/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/www/layout/admin/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/www/layout/admin/cache/images/.gitignore b/www/layout/admin/cache/images/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/www/layout/admin/cache/images/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/www/layout/admin/images/5c4eb2329ca79.jpg b/www/layout/admin/images/5c4eb2329ca79.jpg new file mode 100755 index 00000000..3fc33484 Binary files /dev/null and b/www/layout/admin/images/5c4eb2329ca79.jpg differ diff --git a/www/layout/admin/images/5c4ed67057854.jpg b/www/layout/admin/images/5c4ed67057854.jpg new file mode 100755 index 00000000..3ae52299 Binary files /dev/null and b/www/layout/admin/images/5c4ed67057854.jpg differ diff --git a/www/layout/admin/images/5c501af48da6c.jpg b/www/layout/admin/images/5c501af48da6c.jpg new file mode 100755 index 00000000..8f6e28b7 Binary files /dev/null and b/www/layout/admin/images/5c501af48da6c.jpg differ diff --git a/www/layout/admin/images/no_picture.jpg b/www/layout/admin/images/no_picture.jpg new file mode 100755 index 00000000..bb45c64a Binary files /dev/null and b/www/layout/admin/images/no_picture.jpg differ diff --git a/www/layout/admin/images/no_picture_square.jpg b/www/layout/admin/images/no_picture_square.jpg new file mode 100755 index 00000000..7f0c5c73 Binary files /dev/null and b/www/layout/admin/images/no_picture_square.jpg differ diff --git a/www/layout/admin/images/no_picture_square.png b/www/layout/admin/images/no_picture_square.png new file mode 100755 index 00000000..5b597199 Binary files /dev/null and b/www/layout/admin/images/no_picture_square.png differ diff --git a/www/layout/admin/images/no_picture_width_bigger.jpg b/www/layout/admin/images/no_picture_width_bigger.jpg new file mode 100755 index 00000000..973a7c34 Binary files /dev/null and b/www/layout/admin/images/no_picture_width_bigger.jpg differ diff --git a/www/layout/admin/images/no_picture_width_bigger.png b/www/layout/admin/images/no_picture_width_bigger.png new file mode 100755 index 00000000..b5072527 Binary files /dev/null and b/www/layout/admin/images/no_picture_width_bigger.png differ diff --git a/www/layout/admin/javascript/edit.jq.js b/www/layout/admin/javascript/edit.jq.js index b632be95..ec2c2144 100644 --- a/www/layout/admin/javascript/edit.jq.js +++ b/www/layout/admin/javascript/edit.jq.js @@ -295,11 +295,9 @@ function isObject(val) { * @param {Object} object object to search key in * @return {Boolean} true/false if key exists in object */ -// const keyInObject = (key, object) => (key in object) ? true : false; const keyInObject = (key, object) => (Object.prototype.hasOwnProperty.call(object, key)) ? true : false; /*function keyInObject(key, object) { - // return (key in object) ? true : false; return (Object.prototype.hasOwnProperty.call(object, key)) ? true : false; }*/ @@ -803,6 +801,66 @@ function html_options_refill(name, data, sort = '') } } +/** + * parses a query string from window.location.search.substring(1) + * ALTERNATIVE CODE + * var url = new URL(window.location.href); + * param_uid = url.searchParams.get('uid'); + * @param {String} [query=''] the query string to parse + * if not set will auto fill + * @param {String} [return_key=''] if set only returns this key entry + * or empty for none + * @return {Object|String} parameter entry list + */ +function parseQueryString(query = '', return_key = '') { + if (!query) { + query = window.location.search.substring(1); + } + var vars = query.split("&"); + var query_string = {}; + for (var i = 0; i < vars.length; i++) { + var pair = vars[i].split("="); + var key = decodeURIComponent(pair[0]); + var value = decodeURIComponent(pair[1]); + // If first entry with this name + if (typeof query_string[key] === "undefined") { + query_string[key] = decodeURIComponent(value); + // If second entry with this name + } else if (typeof query_string[key] === "string") { + var arr = [query_string[key], decodeURIComponent(value)]; + query_string[key] = arr; + // If third or later entry with this name + } else { + query_string[key].push(decodeURIComponent(value)); + } + } + if (return_key) { + if (keyInObject(return_key, query_string)) { + return query_string[return_key]; + } else { + return ''; + } + } else { + return query_string; + } +} + +/** + * 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 + */ +function getQueryStringParam(key) +{ + var url = new URL(window.location.href); + var param = url.searchParams.get(key); + if (param) { + return param; + } else { + return ''; + } +} + // *** MASTER logout call /** * submits basic data for form logout diff --git a/www/layout/admin/javascript/edit.pt.js b/www/layout/admin/javascript/edit.pt.js index c4418f26..4fb9b1e8 100644 --- a/www/layout/admin/javascript/edit.pt.js +++ b/www/layout/admin/javascript/edit.pt.js @@ -382,10 +382,10 @@ function isObject(val) { * @param {Object} object object to search key in * @return {Boolean} true/false if key exists in object */ -const keyInObject = (key, object) => (key in object) ? true : false; +const keyInObject = (key, object) => (Object.prototype.hasOwnProperty.call(object, key)) ? true : false; /*function keyInObject(key, object) { - return (key in object) ? true : false; + return (Object.prototype.hasOwnProperty.call(object, key)) ? true : false; }*/ /** @@ -888,6 +888,66 @@ function html_options_refill(name, data, sort = '') } } +/** + * parses a query string from window.location.search.substring(1) + * ALTERNATIVE CODE + * var url = new URL(window.location.href); + * param_uid = url.searchParams.get('uid'); + * @param {String} [query=''] the query string to parse + * if not set will auto fill + * @param {String} [return_key=''] if set only returns this key entry + * or empty for none + * @return {Object|String} parameter entry list + */ +function parseQueryString(query = '', return_key = '') { + if (!query) { + query = window.location.search.substring(1); + } + var vars = query.split("&"); + var query_string = {}; + for (var i = 0; i < vars.length; i++) { + var pair = vars[i].split("="); + var key = decodeURIComponent(pair[0]); + var value = decodeURIComponent(pair[1]); + // If first entry with this name + if (typeof query_string[key] === "undefined") { + query_string[key] = decodeURIComponent(value); + // If second entry with this name + } else if (typeof query_string[key] === "string") { + var arr = [query_string[key], decodeURIComponent(value)]; + query_string[key] = arr; + // If third or later entry with this name + } else { + query_string[key].push(decodeURIComponent(value)); + } + } + if (return_key) { + if (keyInObject(return_key, query_string)) { + return query_string[return_key]; + } else { + return ''; + } + } else { + return query_string; + } +} + +/** + * 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 + */ +function getQueryStringParam(key) +{ + var url = new URL(window.location.href); + var param = url.searchParams.get(key); + if (param) { + return param; + } else { + return ''; + } +} + // *** MASTER logout call /** * submits basic data for form logout diff --git a/www/layout/frontend/cache/.gitignore b/www/layout/frontend/cache/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/www/layout/frontend/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/www/layout/frontend/cache/images/.gitignore b/www/layout/frontend/cache/images/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/www/layout/frontend/cache/images/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/www/lib/CoreLibs/Basic.php b/www/lib/CoreLibs/Basic.php index da732eae..a444fa83 100644 --- a/www/lib/CoreLibs/Basic.php +++ b/www/lib/CoreLibs/Basic.php @@ -1528,7 +1528,7 @@ class Basic // 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 round((float)$number / pow(1024, ($i = floor(log((float)$number, 1024)))), 2).($space ? ' ' : '').(isset($labels[(int)$i]) ? $labels[(int)$i] : '>EB'); } return (string)$number; } @@ -1956,16 +1956,169 @@ class Basic return $return_data; } + /** + * simple thumbnail creation for jpeg, png only + * TODO: add other types like gif, etc + * - bails with false on failed create + * - if either size_x or size_y are empty (0) + * the resize is to max of one size + * if both are set, those are the max sizes (aspect ration is always ekpt) + * - if path is not given will cache folder for current path set + * @param string $filename source file name with full path + * @param int $thumb_width thumbnail width + * @param int $thumb_height thumbnail height + * @param string|null $thumbnail_path altnerative path for thumbnails + * @param bool $use_cache default to true, set to false to skip + * creating new image if exists + * @param bool $high_quality default to true, uses sample version, set to false + * to use quick but less nice version + * @param int $jpeg_quality default 80, set image quality for jpeg only + * @return string|bool thumbnail with path + */ + public function createThumbnailSimple( + string $filename, + int $thumb_width = 0, + int $thumb_height = 0, + ?string $thumbnail_path = null, + bool $use_cache = true, + bool $high_quality = true, + int $jpeg_quality = 80 + ) { + $thumbnail = false; + // $this->debug('IMAGE PREPARE', "FILE: $filename (exists ".(string)file_exists($filename)."), WIDTH: $thumb_width, HEIGHT: $thumb_height"); + // check that input image exists and is either jpeg or png + // also fail if the basic CACHE folder does not exist at all + if (file_exists($filename) && + is_dir(BASE.LAYOUT.CONTENT_PATH.CACHE) && + is_writable(BASE.LAYOUT.CONTENT_PATH.CACHE) + ) { + // $this->debug('IMAGE PREPARE', "FILENAME OK, THUMB WIDTH/HEIGHT OK"); + list($inc_width, $inc_height, $img_type) = getimagesize($filename); + if ($img_type == IMG_JPG || + $img_type == IMG_PNG + ) { + // $this->debug('IMAGE PREPARE', "IMAGE TYPE OK: ".$inc_width.'x'.$inc_height); + // set thumbnail paths + $thumbnail_write_path = BASE.LAYOUT.CONTENT_PATH.CACHE.IMAGES; + $thumbnail_web_path = LAYOUT.CACHE.IMAGES; + // if images folder in cache does not exist create it, if failed, fall back to base cache folder + if (!is_dir($thumbnail_write_path)) { + if (false === mkdir($thumbnail_write_path)) { + $thumbnail_write_path = BASE.LAYOUT.CONTENT_PATH.CACHE; + $thumbnail_web_path = LAYOUT.CACHE; + } + } + // if missing width or height in thumb, use the set one + if ($thumb_width == 0) { + $thumb_width = $inc_width; + } + if ($thumb_height == 0) { + $thumb_height = $inc_height; + } + // check resize parameters + if ($inc_width > $thumb_width || $inc_height > $thumb_height) { + $thumb_width_r = 0; + $thumb_height_r = 0; + // we need to keep the aspect ration on longest side + if (($inc_height > $inc_width && + // and the height is bigger than thumb set + $inc_height > $thumb_height) || + // or the height is smaller or equal width + // but the width for the thumb is equal to the image height + ($inc_height <= $inc_width && + $inc_width == $thumb_width + ) + ) { + // $this->debug('IMAGE PREPARE', 'HEIGHT > WIDTH'); + $ratio = $inc_height / $thumb_height; + $thumb_width_r = (int)ceil($inc_width / $ratio); + $thumb_height_r = $thumb_height; + } else { + // $this->debug('IMAGE PREPARE', 'WIDTH > HEIGHT'); + $ratio = $inc_width / $thumb_width; + $thumb_width_r = $thumb_width; + $thumb_height_r = (int)ceil($inc_height / $ratio); + } + // $this->debug('IMAGE PREPARE', "Ratio: $ratio, Target size $thumb_width_r x $thumb_height_r"); + // set output thumbnail name + $thumbnail = 'thumb-'.pathinfo($filename)['filename'].'-'.$thumb_width_r.'x'.$thumb_height_r; + if ($use_cache === false || + !file_exists($thumbnail_write_path.$thumbnail) + ) { + // image, copy source image, offset in image, source x/y, new size, source image size + $thumb = imagecreatetruecolor($thumb_width_r, $thumb_height_r); + if ($img_type == IMG_PNG) { + // preservere transaprency + imagecolortransparent( + $thumb, + imagecolorallocatealpha($thumb, 0, 0, 0, 127) + ); + imagealphablending($thumb, false); + imagesavealpha($thumb, true); + } + $source = null; + switch ($img_type) { + case IMG_JPG: + $source = imagecreatefromjpeg($filename); + break; + case IMG_PNG: + $source = imagecreatefrompng($filename); + break; + } + // check that we have a source image resource + if ($source !== null) { + // resize no shift + if ($high_quality === true) { + imagecopyresized($thumb, $source, 0, 0, 0, 0, $thumb_width_r, $thumb_height_r, $inc_width, $inc_height); + } else { + imagecopyresampled($thumb, $source, 0, 0, 0, 0, $thumb_width_r, $thumb_height_r, $inc_width, $inc_height); + } + // write file + switch ($img_type) { + case IMG_JPG: + imagejpeg($thumb, $thumbnail_write_path.$thumbnail, $jpeg_quality); + break; + case IMG_PNG: + imagepng($thumb, $thumbnail_write_path.$thumbnail); + break; + } + // free up resources (in case we are called in a loop) + imagedestroy($source); + imagedestroy($thumb); + } else { + $thumbnail = false; + } + } + } else { + // we just copy over the image as is, we never upscale + $thumbnail = 'thumb-'.pathinfo($filename)['filename'].'-'.$inc_width.'x'.$inc_height; + if ($use_cache === false || + !file_exists($thumbnail_write_path.$thumbnail) + ) { + copy($filename, $thumbnail_write_path.$thumbnail); + } + } + // add output path + if ($thumbnail !== false) { + $thumbnail = $thumbnail_web_path.$thumbnail; + } + } + } + // either return false or the thumbnail name + output path web + return $thumbnail; + } + /** * reads the rotation info of an file and rotates it to be correctly upright * this is done because not all software honers the exit Orientation flag + * only works with jpg or png * @param string $filename path + filename to rotate. This file must be writeable * @return void */ public function correctImageOrientation($filename): void { if (function_exists('exif_read_data') && is_writeable($filename)) { - list($inc_width, $inc_height, $type) = getimagesize($filename); + list($inc_width, $inc_height, $img_type) = getimagesize($filename); $exif = exif_read_data($filename); $orientation = null; $img = null; @@ -1974,11 +2127,13 @@ class Basic } if ($orientation != 1) { $this->debug('IMAGE FILE ROTATE', 'Need to rotate image ['.$filename.'] from: '.$orientation); - if ($type == 2) { - // load image to include - $img = imagecreatefromjpeg($filename); - } elseif ($type == 3) { - $img = imagecreatefrompng($filename); + switch ($img_type) { + case IMG_JPG: + $img = imagecreatefromjpeg($filename); + break; + case IMG_PNG: + $img = imagecreatefrompng($filename); + break; } $deg = 0; // 1 top, 6: left, 8: right, 3: bottom @@ -1993,14 +2148,21 @@ class Basic $deg = 90; break; } - if ($deg && $img !== null) { - $img = imagerotate($img, $deg, 0); - } - // then rewrite the rotated image back to the disk as $filename - if ($type == 2 && $img !== null) { - imagejpeg($img, $filename); - } elseif ($type == 3 && $img !== null) { - imagepng($img, $filename); + if ($img !== null) { + if ($deg) { + $img = imagerotate($img, $deg, 0); + } + // then rewrite the rotated image back to the disk as $filename + switch ($img_type) { + case IMG_JPG: + imagejpeg($img, $filename); + break; + case IMG_PNG: + imagepng($img, $filename); + break; + } + // clean up image if we have an image + imagedestroy($img); } } // only if we need to rotate } // function exists & file is writeable, else do nothing