diff --git a/.DS_Store b/.DS_Store index 15607a8..cc08f68 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/api.php b/api.php index 027a5a0..3034fa5 100644 --- a/api.php +++ b/api.php @@ -149,6 +149,25 @@ if($is_jwt_valid && str_contains($version, 'v')) { //------------------------------------------ //CHECK IF USER IS ALLOWED TO CALL SPECIFIC API //------------------------------------------ + + //------------------------------------------ + // First check if endPoint is fileUpload + //------------------------------------------ + $fileUploadEndpoints = [ + 'media_upload' + ]; + + $isFileUploadEndpoint = in_array($collection, $fileUploadEndpoints); + $hasValidFileData = !empty($_FILES) && $_SERVER['REQUEST_METHOD'] ==='POST'; + + if ($isFileUploadEndpoint && $hasValidFileData) { + $input = $_POST; + } + + //------------------------------------------ + // END check if endPoint is fileUpload + //------------------------------------------ + if (isAllowed($collection,$profile,$permission,'R') === 1 && empty($input) && file_exists($api_file)){ include_once $api_file; diff --git a/api/.DS_Store b/api/.DS_Store index 25bea50..362cceb 100644 Binary files a/api/.DS_Store and b/api/.DS_Store differ diff --git a/api/v1/.DS_Store b/api/v1/.DS_Store index cb2f10e..ad7d9dd 100644 Binary files a/api/v1/.DS_Store and b/api/v1/.DS_Store differ diff --git a/api/v2/.DS_Store b/api/v2/.DS_Store index c28ab40..16d8e39 100644 Binary files a/api/v2/.DS_Store and b/api/v2/.DS_Store differ diff --git a/api/v2/get/appointment.php b/api/v2/get/appointment.php new file mode 100644 index 0000000..ac1b602 --- /dev/null +++ b/api/v2/get/appointment.php @@ -0,0 +1,150 @@ +prepare($sql_opening_hours); + $stmt->execute([$dealer_id]); + $opening_hours = $stmt->fetch(PDO::FETCH_ASSOC); + + $opening_hours = json_decode($opening_hours['opening_hours'],true); + + if (empty($opening_hours)){ + // Define opening hours + $opening_hours = [ + 1 => ['start' => '09:00', 'end' => '17:00'], // Monday + 2 => ['start' => '09:00', 'end' => '17:00'], // Tuesday + 3 => ['start' => '09:00', 'end' => '17:00'], // Wednesday + 4 => ['start' => '09:00', 'end' => '17:00'], // Thursday + 5 => ['start' => '09:00', 'end' => '17:00'], // Friday + 6 => ['start' => '09:00', 'end' => '17:00'], // Saturday + 7 => null // Sunday - Closed + ]; + } else { + // Convert all string "null" values to actual null + foreach ($opening_hours as $day => $hours) { + if ($hours === "null") { + $opening_hours[$day] = null; + } + } + } + + // Initialize an array to store the available slots for the entire month + $all_available_slots = []; + + // Initialize a counter for the slot IDs + $id_counter = 1; + + // Iterate over each day in the month + for ($day = 1; $day <= $num_days; $day++) { + // Create a full date string + $full_date = sprintf("%04d-%02d-%02d", $year, $month, $day); + + // Determine the day of the week (1 = Monday, 7 = Sunday) + $day_of_week = date('N', strtotime($full_date)); + + // Check if the day is open + if ($opening_hours[$day_of_week] === null) { + continue; // Skip closed days (Sunday) + } + + // Get existing booked/unavailable slots for the day + $booked_slots_sql = "SELECT start_time, end_time FROM appointment_slots + WHERE DATE(start_time) = ? AND dealer_id = ? AND is_available = false"; + $stmt = $pdo->prepare($booked_slots_sql); + $stmt->execute([$full_date, $dealer_id]); + $booked_result = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // Store booked slots for the day + $booked_slots = []; + + foreach ($booked_result as $row){ + $booked_slots[] = [ + 'start' => substr($row['start_time'], 11, 5), + 'end' => substr($row['end_time'], 11, 5) + ]; + } + + // Generate all possible slots for the day + $start_time = new DateTime($full_date . ' ' . $opening_hours[$day_of_week]['start']); + $end_time = new DateTime($full_date . ' ' . $opening_hours[$day_of_week]['end']); + $interval = new DateInterval('PT1H'); + + // Iterate through each hour and check availability + while ($start_time < $end_time) { + $slot_end = clone $start_time; + $slot_end->add($interval); + + // Check if this slot is booked + $is_available = true; + foreach ($booked_slots as $booked) { + $booked_start = new DateTime($full_date . ' ' . $booked['start']); + $booked_end = new DateTime($full_date . ' ' . $booked['end']); + + if (($start_time >= $booked_start && $start_time < $booked_end) || + ($slot_end > $booked_start && $slot_end <= $booked_end)) { + $is_available = false; + break; + } + } + + // If the slot is available, add it to the list + if ($is_available) { + $all_available_slots[] = [ + 'id' => $id_counter++, + 'start_time' => $start_time->format('Y-m-d H:i:s'), + 'end_time' => $slot_end->format('Y-m-d H:i:s') + ]; + } + + $start_time->add($interval); + } + } + + $messages = $all_available_slots; +} else { + $messages =['success' => false, 'slots' => '']; +} + +//------------------------------------------ +//JSON_ENCODE +//------------------------------------------ +$messages = json_encode($messages, JSON_UNESCAPED_UNICODE); + +//Send results +echo $messages; + +?> \ No newline at end of file diff --git a/api/v2/get/dealers.php b/api/v2/get/dealers.php index bbc5328..56a5ad2 100644 --- a/api/v2/get/dealers.php +++ b/api/v2/get/dealers.php @@ -55,7 +55,8 @@ if(isset($get_content) && $get_content!=''){ //Define Query if(isset($criterias['totals']) && $criterias['totals'] ==''){ //Request for total rows - $sql = 'SELECT count(*) as count FROM dealers d '.$whereclause.''; + $sql = 'SELECT count(*) as count FROM dealers d '.$whereclause; + } elseif (isset($criterias['list']) && $criterias['list'] ==''){ $sql = 'SELECT d.* FROM dealers d '.$whereclause; @@ -64,6 +65,7 @@ else { //SQL for Paging $sql = 'SELECT d.*, m.full_path FROM dealers d LEFT JOIN media m ON d.dealer_media = m.rowID '.$whereclause.' LIMIT :page,:num_products'; } + $stmt = $pdo->prepare($sql); //Bind to query @@ -95,8 +97,18 @@ if (!empty($criterias)){ //Add paging details if(isset($criterias['totals']) && $criterias['totals']==''){ $stmt->execute(); - $messages = $stmt->fetch(); + $messages = $stmt->fetch(); $messages = $messages[0]; + + //No further data transformation need + $messages = json_encode($messages, JSON_UNESCAPED_UNICODE); + + //Send results + echo $messages; + + //exit + exit(); + } elseif (isset($criterias['list']) && $criterias['list']==''){ //Excute Query @@ -116,10 +128,20 @@ else { } +//------------------------------------------ +//CHANGE ROWID INTO UUID +//------------------------------------------ +function updateRowID($row) { + $row['rowID'] = encodeUuid($row['rowID']); + return $row; +} + +$updatedData = array_map('updateRowID', $messages); + //------------------------------------------ //JSON_ENCODE //------------------------------------------ -$messages = json_encode($messages, JSON_UNESCAPED_UNICODE); +$messages = json_encode($updatedData, JSON_UNESCAPED_UNICODE); //Send results echo $messages; diff --git a/api/v2/get/identity.php b/api/v2/get/identity.php index e9f25bc..a5e3b37 100644 --- a/api/v2/get/identity.php +++ b/api/v2/get/identity.php @@ -50,6 +50,11 @@ if(isset($get_content) && $get_content!=''){ } } +//ASSIGN DEALER ID TO IDENTITY +if(isset($criterias['userkey'])){ + checkAndInsertIdentityDealer($pdo, $criterias['userkey']); +} + if(isset($criterias['totals']) && $criterias['totals'] ==''){ //Request for total rows $sql = 'SELECT count(*) as count from identity '.$whereclause.''; diff --git a/api/v2/get/identity_dealers.php b/api/v2/get/identity_dealers.php new file mode 100644 index 0000000..272a972 --- /dev/null +++ b/api/v2/get/identity_dealers.php @@ -0,0 +1,59 @@ +prepare($sql); + //Excute Query + $stmt->execute(); + //Get results + $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); + + //------------------------------------------ + //CHANGE ROWID INTO UUID + //------------------------------------------ + function updateRowID($row) { + $row['rowID'] = encodeUuid($row['rowID']); + return $row; + } + + $updatedData = array_map('updateRowID', $messages); + + //------------------------------------------ + //JSON_ENCODE + //------------------------------------------ + $messages = json_encode($updatedData, JSON_UNESCAPED_UNICODE); +} + +//Send results +echo $messages; + +?> \ No newline at end of file diff --git a/api/v2/post/appointment.php b/api/v2/post/appointment.php new file mode 100644 index 0000000..3b68967 --- /dev/null +++ b/api/v2/post/appointment.php @@ -0,0 +1,137 @@ +prepare($sql); + $stmt->execute([$starttime,$endtime,1,$dealer_id]); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + + if (isset($result['id'])){ + $messages = ['success' => false, 'message' => 'This slot is no longer available']; + } + else { + try { + //INSERT TIMESLOT + $sql = "INSERT INTO appointment_slots (dealer_id, start_time, end_time, is_available,createdby) VALUES (?,?, ?, ?, ?)"; + $stmt = $pdo->prepare($sql); + $stmt->execute([$dealer_id,$starttime,$endtime,0,'system']); + + $appointment_id = $pdo->lastInsertId(); + + //INSERT APPOINTMENT + $sql = "INSERT INTO appointments (appointment_slot_id, client_name, client_email, client_phone, appointment_status,createdby, dealer_id) VALUES (?,?,?,?,?,?,?)"; + $stmt = $pdo->prepare($sql); + $stmt->execute([$appointment_id,$name, $email, $phone,0,'system', $dealer_id]); + + $messages = [ + 'success' => true, + 'message' => 'Appointment requested successfully', + 'appointment_id' => $appointment_id + ]; + } catch (Exception $e) { + // Roll back transaction on error + $pdo->rollback(); + $messages = ['success' => false, 'message' => 'Error: ' . $e->getMessage()]; + } + } + + //------------------------------------------ + //JSON_ENCODE + //------------------------------------------ + $messages = json_encode($messages, JSON_UNESCAPED_UNICODE); + + //Send results + echo $messages; +} +else { + + //ADD STANDARD PARAMETERS TO ARRAY BASED ON INSERT OR UPDATE + if ($command == 'update'){ + $post_content['updatedby'] = $username ; + + } + elseif ($command == 'insert'){ + $post_content['createdby'] = $username; + } + else { + //do nothing + } + + //CREAT NEW ARRAY AND MAP TO CLAUSE + if(isset($post_content) && $post_content!=''){ + foreach ($post_content as $key => $var){ + if ($key == 'submit' || $key == 'id'){ + //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('appointment',$profile,$permission,'U') === 1){ + $sql = 'UPDATE appointment SET '.$clause.' WHERE rowID = ? '.$whereclause.''; + $execute_input[] = $id; + $stmt = $pdo->prepare($sql); + $stmt->execute($execute_input); + } + elseif ($command == 'insert' && isAllowed('appointment',$profile,$permission,'C') === 1){ + $sql = 'INSERT INTO appointment ('.$clause_insert.') VALUES ('.$input_insert.')'; + $stmt = $pdo->prepare($sql); + $stmt->execute($execute_input); + // Return ID + echo json_encode(array('rowID'=> $pdo->lastInsertId())); + } + elseif ($command == 'delete' && isAllowed('appointment',$profile,$permission,'D') === 1){ + $stmt = $pdo->prepare('DELETE FROM appointment WHERE rowID = ? '.$whereclause.''); + $stmt->execute([ $id ]); + + //Add deletion to changelog + changelog($dbname,'appointment',$id,'Delete','Delete',$username); + } else + { + //do nothing + } +} +?> \ No newline at end of file diff --git a/api/v2/post/dealers.php b/api/v2/post/dealers.php index e59a1a1..51f517e 100644 --- a/api/v2/post/dealers.php +++ b/api/v2/post/dealers.php @@ -1,9 +1,6 @@ 'send')); break; + case 'register_identity': + + //SEND MAIL + send_mail($mail_to,$mail_subject,$mail_content,'',''); + echo json_encode(array('status' => 'send')); + break; + case 'reset': //GET TEMPLATE @@ -43,6 +52,20 @@ if (isset($post_content['type']) && $post_content['type'] !=''){ //SEND MAIL send_mail($mail_to,$subject,$message,'',''); break; + + case 'sendInvite': + + // Get appointment data from mail_content + $appointment = $post_content['content']; + + if ($appointment && isset($appointment['starttime']) && isset($appointment['endtime'])) { + sendIcsCalendar($appointment, $post_content['to'], $post_content['subject']); + echo json_encode(array('status' => 'send')); + } else { + echo json_encode(array('status' => 'error', 'message' => 'Invalid appointment data')); + } + + break; } } diff --git a/api/v2/post/media_upload.php b/api/v2/post/media_upload.php new file mode 100644 index 0000000..bffc2e3 --- /dev/null +++ b/api/v2/post/media_upload.php @@ -0,0 +1,60 @@ + 'No file uploaded']); + exit; +} + +$file = $_FILES['image']; + +// Validate file type +$allowedTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp']; +$filename = $file['name']; +$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); + +if (!in_array($ext, $allowedTypes)) { + http_response_code(400); + echo json_encode(['error' => 'Invalid file type. Only JPEG, PNG, GIF, and WebP allowed.']); + exit; +} + +$target_dir = dirname(__FILE__, 4)."/assets/images/media/"; + +$title = uniqid().'_'.time().'_'.$input['title']; +$full_path = $target_dir . $title; +$logical_dir = "assets/images/media/".$title; + +if (move_uploaded_file($file['tmp_name'], $full_path)) { + //BUILD UP PARTNERHIERARCHY FROM USER + $partner_product = json_encode(array("salesid"=>$partner->salesid,"soldto"=>$partner->soldto), JSON_UNESCAPED_UNICODE); + + //If succesfull recvieved store in DB + $insert_media_sql = 'INSERT INTO `media`(`title`, `full_path`, `createdby`,`accounthierarchy`) VALUES (?,?,?,?)'; + $stmt = $pdo->prepare( $insert_media_sql); + $stmt->execute([$title,$logical_dir,$username,$partner_product]); + // Return ID + $media_rowID = $pdo->lastInsertId(); + + //assign picture to dealer + if(isset($input['dealer_id']) && !empty($input['dealer_id'])){ + $dealer_id = decodeUuid($input['dealer_id']); + $update_dealer = 'UPDATE dealers SET dealer_media = ? , updatedby = ? WHERE rowID = ?'; + $stmt = $pdo->prepare( $update_dealer); + $stmt->execute([$media_rowID,$username,$dealer_id]); + } + + echo json_encode(['success' => true, 'path' => $logical_dir]); + +} +else { + echo json_encode(['error' => 'Failed to move file']); +} + +?> \ No newline at end of file diff --git a/assets/functions.php b/assets/functions.php index 0de277b..a11319a 100644 --- a/assets/functions.php +++ b/assets/functions.php @@ -93,6 +93,77 @@ function send_mail($to, $subject, $message, $attachment, $attachment_name){ } +function sendIcsCalendar($appointment, $to, $subject = 'Appointment Confirmation') { + + include_once dirname(__FILE__,2).'/settings/config_redirector.php'; + + // Create unique identifier for the event + $uid = md5(uniqid(rand(), true)) . '@' . $_SERVER['HTTP_HOST']; + + // Format times for ICS + $start_time = date('Ymd\THis\Z', strtotime($appointment['starttime'])); + $end_time = date('Ymd\THis\Z', strtotime($appointment['endtime'])); + $now = date('Ymd\THis\Z'); + + // Get appointment details with defaults + $description = isset($appointment['description']) ? $appointment['description'] : 'Your appointment has been confirmed.'; + $location = isset($appointment['location']) ? $appointment['location'] : 'TBD'; + $summary = isset($appointment['title']) ? $appointment['title'] : $subject; + + // Create ICS content + $ics_content = "BEGIN:VCALENDAR\r\n"; + $ics_content .= "VERSION:2.0\r\n"; + $ics_content .= "PRODID:-//AssetMgt//AppointmentSystem//EN\r\n"; + $ics_content .= "CALSCALE:GREGORIAN\r\n"; + $ics_content .= "METHOD:REQUEST\r\n"; + $ics_content .= "BEGIN:VEVENT\r\n"; + $ics_content .= "DTSTART:" . $start_time . "\r\n"; + $ics_content .= "DTEND:" . $end_time . "\r\n"; + $ics_content .= "DTSTAMP:" . $now . "\r\n"; + $ics_content .= "UID:" . $uid . "\r\n"; + $ics_content .= "CREATED:" . $now . "\r\n"; + $ics_content .= "DESCRIPTION:" . $description . "\r\n"; + $ics_content .= "LAST-MODIFIED:" . $now . "\r\n"; + $ics_content .= "LOCATION:" . $location . "\r\n"; + $ics_content .= "SEQUENCE:0\r\n"; + $ics_content .= "STATUS:CONFIRMED\r\n"; + $ics_content .= "SUMMARY:" . $summary . "\r\n"; + $ics_content .= "TRANSP:OPAQUE\r\n"; + $ics_content .= "END:VEVENT\r\n"; + $ics_content .= "END:VCALENDAR\r\n"; + + // Use PHPMailer for ICS calendar invitation + $mail = new PHPMailer(); + $mail->CharSet = 'UTF-8'; + $mail->isSMTP(); + $mail->Host = email_host_name; + $mail->SMTPAuth = true; + $mail->Username = email; + $mail->Password = email_outgoing_pw; + $mail->SMTPSecure = email_outgoing_security; + $mail->Port = email_outgoing_port; + $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; + $mail->setFrom(email, mail_from); + $mail->addAddress($to); + $mail->addReplyTo(email_reply_to); + + // PRIMARY METHOD: Set as content type (preferred) + $mail->ContentType = 'text/calendar; method=REQUEST; charset=UTF-8'; + $mail->Subject = $subject; + $mail->Body = $ics_content; + + // ALTERNATIVE: Also add as attachment for fallback + $mail->addStringAttachment($ics_content, 'appointment.ics', 'base64', 'text/calendar'); + + if (!$mail->send()) { + $tab = array('error' => 'Mailer Error: ' . $mail->ErrorInfo); + debuglog(json_encode($tab)); + return false; + } else { + return true; + } +} + //------------------------------------------ // Global functions //------------------------------------------ @@ -4372,7 +4443,7 @@ function generateDealerInformation($token){ ); if (isset($response[$key]) && (empty($response[$key]) || $response[$key] == '')) { - $new_content['rowID'] = encodeUuid($response['rowID']); + $new_content['rowID'] = $response['rowID']; $new_content[$key] = $generated_content[$key]; } } @@ -4494,4 +4565,39 @@ function processPostContent(array $post_content): array } return $post_content; +} + +function checkAndInsertIdentityDealer($pdo, $identityUserkey) { + try { + $stmt = $pdo->prepare('SELECT id, email FROM identity WHERE userkey = ?'); + $stmt->execute([$identityUserkey]); + $identity = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$identity) { + return false; + } + + $stmt = $pdo->prepare('SELECT rowID FROM dealers WHERE email = ?'); + $stmt->execute([$identity['email']]); + $dealer = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$dealer) { + return false; + } + + $checkStmt = $pdo->prepare('SELECT COUNT(*) as count FROM identity_dealers WHERE dealer_id = ? AND identity_id = ?'); + $checkStmt->execute([$dealer['rowID'], $identity['id']]); + $result = $checkStmt->fetch(PDO::FETCH_ASSOC); + + if ($result['count'] == 0) { + $insertStmt = $pdo->prepare('INSERT INTO identity_dealers (dealer_id, identity_id) VALUES (?, ?)'); + return $insertStmt->execute([$dealer['rowID'], $identity['id']]); + } + + return false; + + } catch (PDOException $e) { + error_log('Database error in checkAndInsertIdentityDealer: ' . $e->getMessage()); + return false; + } } \ No newline at end of file diff --git a/custom/bewellwell/settings/settingsprofiles.php b/custom/bewellwell/settings/settingsprofiles.php index e38240a..cb4f277 100644 --- a/custom/bewellwell/settings/settingsprofiles.php +++ b/custom/bewellwell/settings/settingsprofiles.php @@ -1,16 +1,16 @@ \ No newline at end of file diff --git a/dealer.php b/dealer.php index c0caf14..708ea34 100644 --- a/dealer.php +++ b/dealer.php @@ -54,7 +54,7 @@ if (isset($_GET['success_msg'])) { template_header('Dealer', 'dealer', 'view'); $view = '