Administrer bestilling ';
$content .= ' Dersom noe skulle dukke opp og du ikke kan komme til avtalen din, vennligst gi oss beskjed minst 24 timer i forveien, slik at vi kan gi timen videre til en annen kunde og våre kollegers tid ikke blir stående ubrukt; avbestillinger innen 24 timer, eller manglende oppmøte, vil bli belastet med full pris for behandlingen; avbestilling kan gjøres via e-post til info@studiobeve.no eller ved å sende oss en melding på Instagram eller Facebook. Takk for forståelsen og samarbeidet!';
$content .= ' ';
$content .= ' STUDIOBEVE';
break;
case 'en':
$content = 'Dear Guest, ';
$content .= 'Thank you for your booking! ';
$content .= ' ';
$content .= '
Manage booking ';
$content .= ' If something comes up and you can’t make it to your booked appointment, please let us know at least 24 hours in advance so we can offer the time slot to another guest and our colleagues’ time won’t remain unused; cancellations within 24 hours or no-shows will be charged the full service fee; you can cancel by emailing info@studiobeve.no, or by sending us a message on Instagram or Facebook. Thank you for your understanding and cooperation!';
$content .= ' ';
$content .= ' STUDIOBEVE';
break;
case 'hu':
$content = 'Kedves Vendégünk, ';
$content .= 'Köszönettel fogadtuk a foglalását! ';
$content .= ' ';
$content .= '
Foglalás kezelése ';
$content .= ' Kérünk, ha valami közbejön, és mégsem tudsz eljönni a lefoglalt időpontodra, jelezd nekünk legalább 24 órával előre, így más vendégnek is tudunk szabad időpontot biztosítani, és kollégáink ideje sem marad kihasználatlanul; 24 órán belüli lemondás, illetve meg nem jelenés esetén a szolgáltatás teljes díja felszámításra kerül; a lemondást az info@studiobeve.no
e-mail címen, vagy Instagram- és Facebook-üzenetben tudod megtenni. Köszönjük a megértést és az együttműködést!';
$content .= ' ';
$content .= ' STUDIOBEVE';
break;
}
$data['selectedLang'] = $_POST['lang'];
$emailItem = array(
'addressee_email' => $bookingArray['guest_email'],
'addressee_name' => $bookingArray['guest_name'],
'subject' => '[studiobeve] Your booking has arrived',
'content' => $content,
'attachments' => array()
);
$emailArray[] = $emailItem;
header('Location:'.SITEURL.$_POST['lang'].'/booking-finished/'.$_POST['subpage']);
}
$this->User_model->sendEmail($emailArray, $_POST['lang'], $_POST['subpage']);
$this->load->view('pages/booking-process', $data);
}
public function profile(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Log_model');
$this->load->model('Module_model');
$data['message'] = '';
$data['message_class'] = '';
$data['fullname'] = '';
$data['password'] = '';
$data['profile_img_url'] = '';
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['message'] = '';
$data['message_class'] = '';
$data['pageTitle'] = 'Profil szerkesztése';
if($this->User_model->is_user_admin($_SESSION['username'])){
$data['activeModules'] = $this->Module_model->getAllModule();
}
else{
$data['activeModules'] = $this->Module_model->getUserModules($data['currentUser']->user_id);
}
if(isset($_POST["storeProfile"])) {
if($_POST['fullname'] != $data['currentUser']->fullname){
$userArray = array(
'fullname' => $_POST['fullname']
);
$this->User_model->updateUser($data['currentUser']->user_id, $userArray);
$this->Log_model->addLog('profile', 'A felhasználó módosította a teljes nevét: '.$data['currentUser']->fullname.' => '.$_POST['fullname'],$_SESSION['username']);
}
if($_POST['password'] != ''){
$userArray = array(
'password' => hash('sha256', $_POST['password'])
);
$this->User_model->updateUser($data['currentUser']->user_id, $userArray);
$this->Log_model->addLog('profile', 'A felhasználó módosította a jelszavát',$_SESSION['username']);
}
//upload profile image
if($_FILES["profile_img"]["tmp_name"] != ''){
$target_dir = getcwd()."/assets/img/profiles/";
$target_file = $target_dir . str_replace('.','_', str_replace('@','_',$_SESSION['username']));
$imageFileType = strtolower(pathinfo(basename($_FILES["profile_img"]["name"]),PATHINFO_EXTENSION));
$allowedTypes = array('jpg', 'jpeg', 'png', 'gif', 'webp');
if (!in_array($imageFileType, $allowedTypes)) {
$data['message'] = 'Csak képfájlok engedélyezettek (jpg, png, gif, webp)!';
$data['message_class'] = 'errorMessage';
} elseif (move_uploaded_file($_FILES["profile_img"]["tmp_name"], $target_file.'.'.$imageFileType)) {
$data['message'] = 'A profilkép sikeresen feltöltve!';
$data['message_class'] = 'successMessage';
$data['profile_img_url'] = 'assets/img/profiles/'.str_replace('.','_', str_replace('@','_',$_SESSION['username'])).'.'.$imageFileType;
$userArray = array(
'profile_img_url' => $data['profile_img_url']
);
$this->User_model->updateUser($data['currentUser']->user_id, $userArray);
$this->Log_model->addLog('profile', 'A felhasználó lecserélte a profilképét ',$_SESSION['username']);
} else {
$data['message'] = 'Hiba a feltöltés közben!';
$data['message_class'] = 'errorMessage';
}
}
$data['message'] = 'A profil sikeresen módosítva!';
$data['message_class'] = 'successMessage';
}
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$this->load->view('pages/profile', $data);
}
else{
header("Location:".SITEURL."login");
}
}
public function log_monitor(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Log_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['message'] = '';
$data['message_class'] = '';
$data['pageTitle'] = 'Rendszermonitor';
$data['logInfoLink'] = '';
if($this->User_model->is_user_admin($_SESSION['username'])){
$data['activeModules'] = $this->Module_model->getAllModule();
}
else{
$data['activeModules'] = $this->Module_model->getUserModules($data['currentUser']->user_id);
}
$selectedModule = $this->Module_model->getModuleBySlug('log-monitor');
if($this->Module_model->is_module_available($selectedModule->module_id, $data['currentUser']->user_id)){
if(isset($_POST['exportToFile'])){
$logInfoArray = $this->Log_model->getAllLoginfo();
$logrow = '';
$logTxt = fopen(getcwd()."/documents/loginfo.csv", "w") or die("Hiba a fájl mentésekor!");
$row = '"Esemény ideje";'.'"Esemény típusa";'.'"Felhasználónév";"Bejegyzés tartalma";'.PHP_EOL;
$string_encoded = iconv( mb_detect_encoding( $row ), 'ISO-8859-2', $row );
fwrite($logTxt, $string_encoded);
foreach($logInfoArray as $logInfoItem){
$logDateTime = new datetime($logInfoItem->event_datetime);
$row = date_format($logDateTime,"Y-m-d H:i").';"'.$logInfoItem->event_type.'";"'.$logInfoItem->username.'";"'.$logInfoItem->event_content.'";'.PHP_EOL;
$string_encoded = iconv( mb_detect_encoding( $row ), 'ISO-8859-2', $row );
fwrite($logTxt, $string_encoded);
}
fclose($logTxt);
$data['logInfoLink'] = 'A loginfo.csv fájl letöltése';
}
$this->load->view('log-monitor', $data);
}
else{
header("Location:".SITEURL."home");
}
}
else{
header("Location:".SITEURL."login");
}
}
public function manage_booking($lang, $token){
$this->load->helper('url');
$this->load->model('Service_model');
$booking = $this->Service_model->getBookingByToken($token);
if(!$booking){
show_404();
return;
}
// Determine booking type from services. Scan until one resolves rather
// than trusting services[0]: if that single service has since been
// hard-deleted, the guest's manage page would silently render with the
// wrong vertical's branding.
$serviceIds = unserialize($booking->service_ids);
$subpage = 'barber';
$firstService = NULL;
if(is_array($serviceIds)){
foreach($serviceIds as $serviceIdItem){
$resolvedService = $this->Service_model->getServiceById($serviceIdItem);
if($resolvedService && $resolvedService->service_type !== ''){
// Kept for the worker filter below: the category (and therefore the
// capability check) is derived from this same resolved service.
$firstService = $resolvedService;
$subpage = $resolvedService->service_type;
break;
}
}
}
$now = new DateTime('now', new DateTimeZone('Europe/Oslo'));
$appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo'));
// A booking's vertical is derived from its services, so an unregistered
// or stale service_type must still render something: fall back to barber.
$vertical = vertical_get($subpage);
if(!$vertical){
$vertical = vertical_get('barber');
}
$data['token'] = $token;
$data['booking'] = $booking;
$data['selectedLang'] = $lang;
$data['subpage'] = $vertical['slug'];
$data['vertical'] = $vertical;
$data['pageTitle'] = '';
$data['status'] = 'ok';
if($appointmentDT < $now){
$data['status'] = 'expired';
} elseif(($appointmentDT->getTimestamp() - $now->getTimestamp()) < 86400){
$data['status'] = 'cutoff';
}
$data['services'] = $this->Service_model->getAllServiceByServiceType($subpage, $lang);
// Filter workers to those qualified for the booking's service category,
// so the guest can't switch to a worker who doesn't perform these services.
$categorySlug = '';
if($firstService){
$category = $this->Service_model->getServiceCategoryById($firstService->service_category_id);
if($category){
$categorySlug = $category->serv_cat_slug;
}
}
if($categorySlug !== ''){
$data['workers'] = $this->Service_model->getWorkersByCategorySlug($categorySlug, is_array($serviceIds) ? $serviceIds : array());
} else {
$data['workers'] = $this->Service_model->getActiveWorkers($subpage);
}
$data['selectedServiceIds'] = is_array($serviceIds) ? $serviceIds : array();
$data['categorySlug'] = $categorySlug;
$data['worker'] = $this->Service_model->getWorkerById($booking->worker_id);
$this->load->view('pages/manage-booking', $data);
}
public function manage_booking_process(){
$this->load->helper('url');
$this->load->model('Service_model');
if(!isset($_POST['token']) || !isset($_POST['sendBooking'])){
header('Location:'.SITEURL);
return;
}
$token = $_POST['token'];
$booking = $this->Service_model->getBookingByToken($token);
if(!$booking){
show_404();
return;
}
// This form is a normal POST, not AJAX, so every rejection below is rendered
// as the shared booking error page. Returning raw JSON showed the guest an
// unstyled {"error":...} blob after they had filled the whole form in.
// Use the stored token, not the posted one: it is echoed straight into the
// error page's back link.
$manageBackUrl = SITEURL.vertical_lang(isset($_POST['lang']) ? $_POST['lang'] : '').'/manage-booking/'.$booking->manage_token;
$now = new DateTime('now', new DateTimeZone('Europe/Oslo'));
$appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo'));
if(($appointmentDT->getTimestamp() - $now->getTimestamp()) < 86400){
$this->_booking_error('cutoff_passed', 403, $manageBackUrl);
return;
}
// Build selected service array and total length
$selectedServiceArray = array();
$totalLengthSeconds = 0;
foreach($_POST as $key => $value){
if(strpos($key, 'service_') === 0 && $value == '1'){
$serviceId = str_replace('service_', '', $key);
$selectedServiceArray[] = $serviceId;
$service = $this->Service_model->getServiceById($serviceId);
if($service){
$parts = explode(':', $service->service_time);
$totalLengthSeconds += ($parts[0] * 3600) + ($parts[1] * 60);
}
}
}
$servicelength = gmdate('H:i:s', $totalLengthSeconds);
$newBookingDate = $_POST['booking_date'];
$newStartTime = $_POST['booking_start_time'];
$newWorkerId = $_POST['worker_id'];
$lang = $_POST['lang'];
$subpage = $_POST['subpage'];
// Category match: worker must perform the selected services' category.
$submittedWorker = $this->Service_model->getWorkerById($newWorkerId);
if(!empty($selectedServiceArray) && $submittedWorker){
$firstSelectedService = $this->Service_model->getServiceById($selectedServiceArray[0]);
if($firstSelectedService && $firstSelectedService->service_category_id != $submittedWorker->service_category_id){
$this->_booking_error('category_mismatch', 403, $manageBackUrl);
return;
}
}
// Per-worker capability, same reasoning as booking_process(): the
// category check proves only that the services belong to the worker's
// category, not that this worker performs them.
if(!$this->Service_model->workerCanPerformServices($newWorkerId, $selectedServiceArray)){
$this->_booking_error('service_not_offered', 403, $manageBackUrl);
return;
}
// Schedule check
$schedule = $this->Service_model->getWorkerScheduleForDate($newWorkerId, $newBookingDate);
if(!$schedule){
$this->_booking_error('worker_unavailable', 403, $manageBackUrl);
return;
}
if($newStartTime < $schedule->start_time || $newStartTime >= $schedule->end_time){
$this->_booking_error('outside_schedule', 403, $manageBackUrl);
return;
}
if(!$this->Service_model->isWorkerAvailableThisWeek($newWorkerId, $newBookingDate)){
$this->_booking_error('alternate_week', 403, $manageBackUrl);
return;
}
// Slot availability (exclude current booking from conflict check)
$available = $this->Service_model->getAvailableTimes($newWorkerId, $newBookingDate, $servicelength, $booking->booking_id);
if(!is_array($available) || !in_array($newStartTime, $available)){
$this->_booking_error('slot_taken', 409, $manageBackUrl);
return;
}
// 3-month limit
$today = new DateTime();
$maxDate = (clone $today)->modify('+3 months');
$selectedDate = new DateTime($newBookingDate);
if($selectedDate > $maxDate){
$this->_booking_error('too_far', 403, $manageBackUrl);
return;
}
// Calculate finish time
$bookingStart = new DateTime($newStartTime);
$bookingLength = new DateTime($servicelength);
$bookingEnd = clone $bookingStart;
$bookingEnd->add(new DateInterval('PT'.intval($bookingLength->format('H')).'H'));
$bookingEnd->add(new DateInterval('PT'.intval($bookingLength->format('i')).'M'));
$closingTime = new DateTime('18:00');
if($bookingEnd > $closingTime){
$this->_booking_error('after_hours', 403, $manageBackUrl);
return;
}
$finishTime = $bookingEnd->format('H:i:s');
// Update booking
$this->Service_model->updateBooking($booking->booking_id, array(
'worker_id' => $newWorkerId,
'booking_date' => $newBookingDate,
'booking_start_time' => $newStartTime,
'booking_finish_time' => $finishTime,
'service_ids' => serialize($selectedServiceArray),
));
$worker = $this->Service_model->getWorkerById($newWorkerId);
$serviceArray = array();
foreach($selectedServiceArray as $serviceItem){
$sel = $this->Service_model->getServiceById($serviceItem);
if(is_object($sel)) $serviceArray[] = $sel;
}
// Google Calendar: delete old events, create new ones
$this->load->library('GoogleCalendar');
$this->config->load('google_calendar');
$evelinCalId = $this->config->item('gcal_evelin_calendar_id');
$oldWorker = $this->Service_model->getWorkerById($booking->worker_id);
if(!empty($booking->gcal_event_id_worker) && !empty($oldWorker->google_calendar_id)){
$this->googlecalendar->deleteEvent($oldWorker->google_calendar_id, $booking->gcal_event_id_worker);
}
if(!empty($booking->gcal_event_id_owner) && !empty($evelinCalId)){
$this->googlecalendar->deleteEvent($evelinCalId, $booking->gcal_event_id_owner);
}
$gcalServiceNames = implode(', ', array_map(function($s){ return $s->service_name_no; }, $serviceArray));
$gcalTitle = $worker->worker_name . ' — ' . $gcalServiceNames . ' — ' . $booking->guest_name;
$gcalDescription = 'Guest: ' . $booking->guest_name . "\nPhone: " . $booking->guest_phone . "\nEmail: " . $booking->guest_email;
$gcalStart = $newBookingDate . 'T' . $newStartTime;
$gcalEnd = $newBookingDate . 'T' . $finishTime;
$newWorkerEventId = null;
$newOwnerEventId = null;
if(!empty($worker->google_calendar_id)){
$newWorkerEventId = $this->googlecalendar->createEvent($worker->google_calendar_id, $gcalTitle, $gcalDescription, $gcalStart, $gcalEnd);
}
if(!empty($evelinCalId)){
$newOwnerEventId = $this->googlecalendar->createEvent($evelinCalId, $gcalTitle, $gcalDescription, $gcalStart, $gcalEnd);
}
$this->Service_model->updateBookingCalEvents($booking->booking_id, $newWorkerEventId, $newOwnerEventId);
// Sync lunch break calendar event (new date, and old date if it changed)
$this->_syncLunchCalendarEvent($worker, $newBookingDate);
if ($booking->booking_date !== $newBookingDate) {
$this->_syncLunchCalendarEvent($worker, $booking->booking_date);
}
// ntfy push notification
$this->_ntfy(
'Modified booking: ' . $booking->guest_name,
'Worker: ' . $worker->worker_name . "\n" .
'Date: ' . $newBookingDate . ' ' . $newStartTime . ' - ' . $finishTime . "\n" .
'Services: ' . $gcalServiceNames,
isset($worker->ntfy_topic) ? $worker->ntfy_topic : null
);
$manageLink = SITEURL.$lang.'/manage-booking/'.$token;
$totalServicePrice = 0;
foreach($serviceArray as $s){ $totalServicePrice += $s->service_price; }
switch($lang){
case 'no':
$subject = '[studiobeve] Din bestilling er oppdatert';
$content = 'Kjære gjest, Din bestilling er oppdatert.
';
$content .= '
';
$content .= '
Navn:
'.$booking->guest_name.'
';
$content .= '
E-post:
'.$booking->guest_email.'
';
$content .= '
Telefon:
'.$booking->guest_phone.'
';
$content .= '
Arbeider:
'.$worker->worker_name.'
';
$content .= '
Dato:
'.$newBookingDate.'
';
$content .= '
Tid:
'.$newStartTime.' - '.$finishTime.'
';
$content .= '
Tjenester:
';
foreach($serviceArray as $s){ $content .= '
'.$s->service_name_no.'
'.$s->service_price.' kr
'; }
$content .= '
';
$content .= '
Totalpris:
'.$totalServicePrice.' kr
';
$content .= '
';
$content .= '
Administrer bestilling ';
$content .= ' Dersom noe skulle dukke opp og du ikke kan komme til avtalen din, vennligst gi oss beskjed minst 24 timer i forveien, slik at vi kan gi timen videre til en annen kunde og våre kollegers tid ikke blir stående ubrukt; avbestillinger innen 24 timer, eller manglende oppmøte, vil bli belastet med full pris for behandlingen; avbestilling kan gjøres via e-post til info@studiobeve.no eller ved å sende oss en melding på Instagram eller Facebook. Takk for forståelsen og samarbeidet!';
$content .= ' ';
$content .= ' STUDIOBEVE';
break;
case 'hu':
$subject = '[studiobeve] A foglalásod módosítva lett';
$content = 'Kedves Vendégünk, A foglalásod módosítva lett.
';
$content .= '
';
$content .= '
Név:
'.$booking->guest_name.'
';
$content .= '
Email:
'.$booking->guest_email.'
';
$content .= '
Telefon:
'.$booking->guest_phone.'
';
$content .= '
Dolgozó:
'.$worker->worker_name.'
';
$content .= '
Dátum:
'.$newBookingDate.'
';
$content .= '
Időpont:
'.$newStartTime.' - '.$finishTime.'
';
$content .= '
Szolgáltatás(ok):
';
foreach($serviceArray as $s){ $content .= '
'.$s->service_name_hu.'
'.$s->service_price.' kr
'; }
$content .= '
';
$content .= '
Összesen fizetendő:
'.$totalServicePrice.' kr
';
$content .= '
';
$content .= '
Foglalás kezelése ';
$content .= ' Kérünk, ha valami közbejön, és mégsem tudsz eljönni a lefoglalt időpontodra, jelezd nekünk legalább 24 órával előre, így más vendégnek is tudunk szabad időpontot biztosítani, és kollégáink ideje sem marad kihasználatlanul; 24 órán belüli lemondás, illetve meg nem jelenés esetén a szolgáltatás teljes díja felszámításra kerül; a lemondást az info@studiobeve.no e-mail címen, vagy Instagram- és Facebook-üzenetben tudod megtenni. Köszönjük a megértést és az együttműködést!';
$content .= ' ';
$content .= ' STUDIOBEVE';
break;
default:
$subject = '[studiobeve] Your booking has been updated';
$content = 'Dear Guest, Your booking has been updated.
';
$content .= '
';
$content .= '
Name:
'.$booking->guest_name.'
';
$content .= '
Email:
'.$booking->guest_email.'
';
$content .= '
Phone:
'.$booking->guest_phone.'
';
$content .= '
Worker:
'.$worker->worker_name.'
';
$content .= '
Date:
'.$newBookingDate.'
';
$content .= '
Time:
'.$newStartTime.' - '.$finishTime.'
';
$content .= '
Services:
';
foreach($serviceArray as $s){ $content .= '
'.$s->service_name_en.'
'.$s->service_price.' kr
'; }
$content .= '
';
$content .= '
Total price:
'.$totalServicePrice.' kr
';
$content .= '
';
$content .= '
Manage booking ';
$content .= ' If something comes up and you can’t make it to your booked appointment, please let us know at least 24 hours in advance so we can offer the time slot to another guest and our colleagues’ time won’t remain unused; cancellations within 24 hours or no-shows will be charged the full service fee; you can cancel by emailing info@studiobeve.no, or by sending us a message on Instagram or Facebook. Thank you for your understanding and cooperation!';
$content .= ' ';
$content .= ' STUDIOBEVE';
}
$emailArray = array(array(
'addressee_email' => $booking->guest_email,
'addressee_name' => $booking->guest_name,
'subject' => $subject,
'content' => $content,
'attachments' => array()
));
$this->load->model('User_model');
$this->User_model->sendEmail($emailArray, $lang, '');
header('Location:'.SITEURL.$lang.'/booking-finished/'.$subpage);
}
public function manage_booking_cancel(){
$this->load->helper('url');
$this->load->model('Service_model');
$this->load->model('User_model');
if(!isset($_POST['token'])){
header('Location:'.SITEURL);
return;
}
$token = $_POST['token'];
$lang = isset($_POST['lang']) ? $_POST['lang'] : 'en';
$booking = $this->Service_model->getBookingByToken($token);
if(!$booking){
show_404();
return;
}
$now = new DateTime('now', new DateTimeZone('Europe/Oslo'));
$appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo'));
if(($appointmentDT->getTimestamp() - $now->getTimestamp()) < 86400){
$this->_booking_error('cutoff_passed', 403, SITEURL.vertical_lang($lang).'/manage-booking/'.$booking->manage_token);
return;
}
$worker = $this->Service_model->getWorkerById($booking->worker_id);
switch($lang){
case 'no':
$subject = '[studiobeve] Din bestilling er avbestilt';
$content = 'Kjære gjest, Din reservasjon den '.$booking->booking_date.' kl. '.date('H:i', strtotime($booking->booking_start_time)).' med '.$worker->worker_name.' er avbestilt.
STUDIOBEVE';
break;
case 'hu':
$subject = '[studiobeve] A foglalásod törölve lett';
$content = 'Kedves Vendégünk, A '.$booking->booking_date.' '.$booking->booking_start_time.'-os foglalásod '.$worker->worker_name.'-nál törölve lett.
STUDIOBEVE';
break;
default:
$subject = '[studiobeve] Your booking has been cancelled';
$content = 'Dear Guest, Your booking on '.$booking->booking_date.' at '.date('H:i', strtotime($booking->booking_start_time)).' with '.$worker->worker_name.' has been cancelled.