From 661783270ac551eb0cba982d8c235e618691cc18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CVeLiTi=E2=80=9D?= <“info@veliti.nl”> Date: Thu, 23 Jan 2025 12:54:16 +0100 Subject: [PATCH] CMXX - Pricelists --- api.php | 28 ++++ api/v2/get/products.php | 6 +- api/v2/get/products_configurations.php | 142 +++++++++++++++++ api/v2/post/pricelists.php | 2 +- api/v2/post/products_configurations.php | 107 +++++++++++++ assets/functions.php | 132 ++++++++-------- pricelists.php | 6 +- pricelists_manage.php | 145 ++++++++++++----- product.php | 46 ++++++ product_manage.php | 14 +- products_configurations.php | 197 ++++++++++++++++++++++++ products_versions.php | 91 +++-------- security.php | 65 ++++++++ settings/settingsmenu.php | 7 + settings/settingsprofiles.php | 2 +- settings/settingsviews.php | 3 +- 16 files changed, 811 insertions(+), 182 deletions(-) create mode 100644 api/v2/get/products_configurations.php create mode 100644 api/v2/post/products_configurations.php create mode 100644 products_configurations.php create mode 100644 security.php diff --git a/api.php b/api.php index 61ffda2..a1991f9 100644 --- a/api.php +++ b/api.php @@ -16,6 +16,34 @@ require_once './assets/functions.php'; include './settings/settings.php'; include './settings/config.php'; +//------------------------------------------ +// Header security - enabled via config +//------------------------------------------ +if (header_security){ + header('Content-Type: application/json'); + + // Set strict security headers + header('X-Content-Type-Options: nosniff'); + header('X-Frame-Options: DENY'); + header('X-XSS-Protection: 1; mode=block'); + header('Content-Security-Policy: default-src \'none\''); + header('Access-Control-Allow-Origin: ' . $_ENV['ALLOWED_ORIGIN']); + header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); + header('Access-Control-Allow-Headers: Content-Type, Accept, Authorization'); + + // Validate Content-Type + if (!str_contains($_SERVER['CONTENT_TYPE'],'application/json')) { + http_response_code(400); + exit(json_encode(['error' => 'Invalid Content-Type'])); + } + + // Validate request size + if ($_SERVER['CONTENT_LENGTH'] > '5M') { + http_response_code(413); + exit(json_encode(['error' => 'Request too large'])); + } + } + //------------------------------------------ // Retrieve API version and Collection // api.php/(v)ersion/{get/post}/collection/ diff --git a/api/v2/get/products.php b/api/v2/get/products.php index 86b20cc..c195307 100644 --- a/api/v2/get/products.php +++ b/api/v2/get/products.php @@ -66,6 +66,10 @@ if(isset($criterias['totals']) && $criterias['totals'] ==''){ elseif (isset($criterias['list']) && $criterias['list'] =='') { //SQL for Paging $sql = 'SELECT * FROM products '.$whereclause.''; +} +elseif (isset($criterias['list']) && $criterias['list'] =='price'){ + //GET ALL PRODUCTS AND PRODUCT ATTRIBUTES FOR PRICING + $sql = '(SELECT rowID as product_id, productname as product_name FROM products where salesflag = 1 '.$whereclause.' ) UNION (SELECT attribute_id as product_id, item_name as product_name FROM `products_attributes_items` WHERE item_status = 1 '.$whereclause.' )'; } else { //SQL for Paging @@ -100,7 +104,7 @@ if(isset($criterias['totals']) && $criterias['totals']==''){ $messages = $stmt->fetch(); $messages = $messages[0]; } -elseif(isset($criterias['list']) && $criterias['list']==''){ +elseif(isset($criterias['list'])){ //Excute Query $stmt->execute(); //Get results diff --git a/api/v2/get/products_configurations.php b/api/v2/get/products_configurations.php new file mode 100644 index 0000000..51bf815 --- /dev/null +++ b/api/v2/get/products_configurations.php @@ -0,0 +1,142 @@ +soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';} + +//default whereclause +$whereclause = ''; + +switch ($permission) { + case '4': + $whereclause = ''; + break; + case '3': + $whereclause = ''; + break; + default: + $condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search; + $whereclause = 'WHERE accounthierarchy like "'.$condition.'"'; + break; +} + +//NEW ARRAY +$criterias = []; +$clause = ''; + +//Check for $_GET variables and build up clause +if(isset($get_content) && $get_content!=''){ + //GET VARIABLES FROM URL + $requests = explode("&", $get_content); + //Check for keys and values + foreach ($requests as $y){ + $v = explode("=", $y); + //INCLUDE VARIABLES IN ARRAY + $criterias[$v[0]] = $v[1]; + + if ($v[0] == 'page' || $v[0] =='p' || $v[0] =='totals' || $v[0] =='list' || $v[0] =='history'|| $v[0] =='success_msg'){ + //do nothing + } + elseif ($v[0] == 'search') { + //build up search + $clause .= ' AND productcode like :'.$v[0]; + } + else {//create clause + $clause .= ' AND '.$v[0].' = :'.$v[0]; + } + } + if ($whereclause == '' && $clause !=''){ + $whereclause = 'WHERE '.substr($clause, 4); + } else { + $whereclause .= $clause; + } +} + +//ENSURE PRODUCTROWID IS SEND +if (isset($criterias['productrowid']) && $criterias['productrowid'] != ''){ + + //CHECK IF ALLOWED TO CRUD VERSIONS + $sql = "SELECT * FROM products WHERE rowID = ? '.$whereclause.'"; + $stmt = $pdo->prepare($sql); + $stmt->execute([$criterias['productrowid']]); + $product_data = $stmt->fetch(); + $product_owner = ($product_data['rowID'])? 1 : 0; + + //IF PRODUCT IS OWNED THEN CRUD is ALLOWED + if ($product_owner === 1 ){ + + //Define Query + if(isset($criterias['totals']) && $criterias['totals'] ==''){ + //Request for total rows + $sql = 'SELECT count(*) as count FROM products_configurations '.$whereclause.''; + } + elseif (isset($criterias['list']) && $criterias['list'] =='') { + //SQL for Paging + $sql = 'SELECT * FROM products_configurations '.$whereclause.''; + } + else { + //SQL for Paging + $sql = 'SELECT * FROM products_configurations '.$whereclause.''; + } + + $stmt = $pdo->prepare($sql); + + //Bind to query + if (str_contains($whereclause, ':condition')){ + $stmt->bindValue('condition', $condition, PDO::PARAM_STR); + } + + if (!empty($criterias)){ + foreach ($criterias as $key => $value){ + $key_condition = ':'.$key; + if (str_contains($whereclause, $key_condition)){ + if ($key == 'search'){ + $search_value = '%'.$value.'%'; + $stmt->bindValue($key, $search_value, PDO::PARAM_STR); + } + else { + $stmt->bindValue($key, $value, PDO::PARAM_STR); + } + } + } + } + + //Add paging details + if(isset($criterias['totals']) && $criterias['totals']==''){ + $stmt->execute(); + $messages = $stmt->fetch(); + $messages = $messages[0]; + } + elseif(isset($criterias['list']) && $criterias['list']==''){ + //Excute Query + $stmt->execute(); + //Get results + $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + else { + //$current_page = isset($criterias['p']) && is_numeric($criterias['p']) ? (int)$criterias['p'] : 1; + //$stmt->bindValue('page', ($current_page - 1) * $page_rows_products, PDO::PARAM_INT); + //$stmt->bindValue('num_products', $page_rows_products, PDO::PARAM_INT); + + //Excute Query + $stmt->execute(); + //Get results + $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + //------------------------------------------ + //JSON_ENCODE + //------------------------------------------ + $messages = json_encode($messages, JSON_UNESCAPED_UNICODE); + + //Send results + echo $messages; + } +} +?> \ No newline at end of file diff --git a/api/v2/post/pricelists.php b/api/v2/post/pricelists.php index 93cf997..2bae067 100644 --- a/api/v2/post/pricelists.php +++ b/api/v2/post/pricelists.php @@ -85,7 +85,7 @@ if ($command == 'update' && isAllowed('pricelists_manage',$profile,$permission,' $stmt->execute($execute_input); } elseif ($command == 'insert' && isAllowed('pricelists_manage',$profile,$permission,'C') === 1){ - $sql = 'INSERT INTO pricelists('.$clause_insert.') VALUES ('.$input_insert.')'; + $sql = 'INSERT INTO pricelists ('.$clause_insert.') VALUES ('.$input_insert.')'; $stmt = $pdo->prepare($sql); $stmt->execute($execute_input); // Return ID diff --git a/api/v2/post/products_configurations.php b/api/v2/post/products_configurations.php new file mode 100644 index 0000000..b50bb20 --- /dev/null +++ b/api/v2/post/products_configurations.php @@ -0,0 +1,107 @@ +soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';} + +//default whereclause +$whereclause = ''; + +switch ($permission) { + case '4': + $whereclause = ''; + break; + case '3': + $whereclause = ''; + break; + default: + $condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search; + $whereclause = ' AND accounthierarchy like "'.$condition.'"'; + break; +} + +//ENSURE PRODUCTROWID IS SEND +if (isset($post_content['productrowid']) && $post_content['productrowid'] != ''){ + + //CHECK IF ALLOWED TO CRUD VERSIONS + $sql = "SELECT * FROM products WHERE rowID = ? '.$whereclause.'"; + $stmt = $pdo->prepare($sql); + $stmt->execute([$post_content['productrowid']]); + $product_data = $stmt->fetch(); + $product_owner = ($product_data['rowID'])? 1 : 0; + + //IF PRODUCT IS OWNED THEN CRUD is ALLOWED + if ($product_owner === 1 ){ + //SET PARAMETERS FOR QUERY + $id = $post_content['rowID'] ?? ''; //check for rowID + $command = ($id == '')? 'insert' : 'update'; //IF rowID = empty then INSERT + if (isset($post_content['delete'])){$command = 'delete';} //change command to delete + $date = date('Y-m-d H:i:s'); + + //CREATE EMPTY STRINGS + $clause = ''; + $clause_insert =''; + $input_insert = ''; + + if ($command == 'insert'){ + $post_content['createdby'] = $username; + } + if ($command == 'update'){ + $post_content['updatedby'] = $username; + } + + //CREAT NEW ARRAY AND MAP TO CLAUSE + if(isset($post_content) && $post_content!=''){ + foreach ($post_content as $key => $var){ + if ($key == 'submit' || $key == 'rowID'){ + //do nothing + } + else { + $criterias[$key] = $var; + $clause .= ' , '.$key.' = ?'; + $clause_insert .= ' , '.$key.''; + $input_insert .= ', ?'; // ? for each insert item + $execute_input[]= $var; // Build array for input + } + } + } + + //CLEAN UP INPUT + $clause = substr($clause, 2); //Clean clause - remove first comma + $clause_insert = substr($clause_insert, 2); //Clean clause - remove first comma + $input_insert = substr($input_insert, 1); //Clean clause - remove first comma + + //QUERY AND VERIFY ALLOWED + if ($command == 'update' && isAllowed('products_configurations',$profile,$permission,'U') === 1){ + $sql = 'UPDATE products_configurations SET '.$clause.' WHERE rowID = ? '.$whereclause.''; + $execute_input[] = $id; + $stmt = $pdo->prepare($sql); + $stmt->execute($execute_input); + } + elseif ($command == 'insert' && isAllowed('products_configurations',$profile,$permission,'C') === 1){ + $sql = 'INSERT INTO products_configurations ('.$clause_insert.') VALUES ('.$input_insert.')'; + $stmt = $pdo->prepare($sql); + $stmt->execute($execute_input); + } + elseif ($command == 'delete' && isAllowed('products_configurations',$profile,$permission,'D') === 1){ + $stmt = $pdo->prepare('DELETE FROM products_configurations WHERE rowID = ? '.$whereclause.''); + $stmt->execute([ $id ]); + + //Add deletion to changelog + changelog($dbname,'products_configurations',$id,'Delete','Delete',$username); + } else + { + //do nothing + } + } +} +?> \ No newline at end of file diff --git a/assets/functions.php b/assets/functions.php index 43fe851..0b99d13 100644 --- a/assets/functions.php +++ b/assets/functions.php @@ -2846,72 +2846,74 @@ function getLatestVersion($productcode,$token){ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ function getRelativeTime($timestamp) { - //GET TRANSLATION FILE - if(isset($_SESSION['country_code'])){ - $api_file_language = dirname(__FILE__,2).'/settings/translations/translations_'.strtoupper($_SESSION['country_code']).'.php'; - if (file_exists($api_file_language)){ - include $api_file_language; //Include the code - } - else { - include dirname(__FILE__,2).'/settings/translations/translations_US.php'; - } - } - else { - include dirname(__FILE__,2).'/settings/translations/translations_US.php'; - } - - // Ensure the timestamp is a valid integer - $timestamp = is_numeric($timestamp) ? $timestamp : strtotime($timestamp); - - // Get current timestamp and calculate difference - $now = time(); - $diff = $now - $timestamp; - - // Define time periods - $minute = 60; - $hour = $minute * 60; - $day = $hour * 24; - $week = $day * 7; - $month = $day * 30; - $year = $day * 365; - - // Handle future timestamps - if ($diff < 0) { - $diff = abs($diff); - $suffix = $time_from_now; - } else { - $suffix = $time_ago; - } - - // Determine the appropriate time description - if ($diff < $minute) { - return $time_just_now; - } elseif ($diff < $hour) { - $minutes = floor($diff / $minute); - return $minutes.(($minutes != 1) ? $time_minutes : $time_minute) . " $suffix"; - } elseif ($diff < $day) { - $hours = floor($diff / $hour); - return $hours.(($hours != 1) ? $time_hours : $time_hour) . " $suffix"; - } elseif ($diff < $week) { - $days = floor($diff / $day); - - // Special handling for today and yesterday - if ($days == 0) { - return $time_today; - } elseif ($days == 1) { - return $time_yesterday; + if (!empty($timestamp) || $timestamp != ""){ + //GET TRANSLATION FILE + if(isset($_SESSION['country_code'])){ + $api_file_language = dirname(__FILE__,2).'/settings/translations/translations_'.strtoupper($_SESSION['country_code']).'.php'; + if (file_exists($api_file_language)){ + include $api_file_language; //Include the code } - - return $days.(($days != 1)?$time_days:$time_day) . " $suffix"; - } elseif ($diff < $month) { - $weeks = floor($diff / $week); - return $weeks.(($weeks != 1)?$time_weeks:$time_week) . " $suffix"; - } elseif ($diff < $year) { - $months = floor($diff / $month); - return $months.(($months != 1)?$time_months:$time_month) . " $suffix"; - } else { - $years = floor($diff / $year); - return $years.(($years != 1)?$time_years:$time_year) . " $suffix"; + else { + include dirname(__FILE__,2).'/settings/translations/translations_US.php'; + } + } + else { + include dirname(__FILE__,2).'/settings/translations/translations_US.php'; + } + + // Ensure the timestamp is a valid integer + $timestamp = is_numeric($timestamp) ? $timestamp : strtotime($timestamp); + + // Get current timestamp and calculate difference + $now = time(); + $diff = $now - $timestamp; + + // Define time periods + $minute = 60; + $hour = $minute * 60; + $day = $hour * 24; + $week = $day * 7; + $month = $day * 30; + $year = $day * 365; + + // Handle future timestamps + if ($diff < 0) { + $diff = abs($diff); + $suffix = $time_from_now; + } else { + $suffix = $time_ago; + } + + // Determine the appropriate time description + if ($diff < $minute) { + return $time_just_now; + } elseif ($diff < $hour) { + $minutes = floor($diff / $minute); + return $minutes.(($minutes != 1) ? $time_minutes : $time_minute) . " $suffix"; + } elseif ($diff < $day) { + $hours = floor($diff / $hour); + return $hours.(($hours != 1) ? $time_hours : $time_hour) . " $suffix"; + } elseif ($diff < $week) { + $days = floor($diff / $day); + + // Special handling for today and yesterday + if ($days == 0) { + return $time_today; + } elseif ($days == 1) { + return $time_yesterday; + } + + return $days.(($days != 1)?$time_days:$time_day) . " $suffix"; + } elseif ($diff < $month) { + $weeks = floor($diff / $week); + return $weeks.(($weeks != 1)?$time_weeks:$time_week) . " $suffix"; + } elseif ($diff < $year) { + $months = floor($diff / $month); + return $months.(($months != 1)?$time_months:$time_month) . " $suffix"; + } else { + $years = floor($diff / $year); + return $years.(($years != 1)?$time_years:$time_year) . " $suffix"; + } } } diff --git a/pricelists.php b/pricelists.php index efe6b1c..26d68ec 100644 --- a/pricelists.php +++ b/pricelists.php @@ -60,8 +60,8 @@ $view = '
'.($pricelists_p ?? '').'
+'.($pricelists_p ?? 'Manage pricelists').'
| '.($product_configurable ?? 'Configurable').' | +'.(($responses->configurable == 1)? $enabled : $disabled).' | +
| '.$product_serialized.' | '.(($responses->sn == 1)? $enabled : $disabled).' | diff --git a/product_manage.php b/product_manage.php index 0360bfc..974a711 100644 --- a/product_manage.php +++ b/product_manage.php @@ -38,7 +38,8 @@ $product = [ 'build' => 1, 'partnerhierarchy' => '', 'sn' =>'', - 'healthindex' =>'' + 'healthindex' =>'', + 'configurable' =>'' ]; if (isset($_GET['id'])) { @@ -56,8 +57,10 @@ if (isset($_GET['id'])) { uploadProduct($_POST['productcode']); } if (isset($_POST['submit'])) { + //GET ALL POST DATA $data = json_encode($_POST, JSON_UNESCAPED_UNICODE); + var_dump($data); //Secure data $payload = generate_payload($data); //API call @@ -65,8 +68,8 @@ if (isset($_GET['id'])) { if ($responses === 'NOK'){ } else { - header('Location: index.php?page=product&rowID='.$_GET['id'].'&success_msg=2'); - exit; + header('Location: index.php?page=product&rowID='.$_GET['id'].'&success_msg=2'); + exit; } } @@ -171,6 +174,11 @@ $view .= '
| Test | +N | +Average | +Median | +STdev | +
| '.$name.' | +'.$measurement['n'].' | +'.$measurement['average'].' | +'.$measurement['median'].' | +'.$measurement['stdev'].' | +