Files
studiobeve.no/application/controllers/Admin.php
T
UbuntuandClaude Opus 5 5c30467fbd Add massage as a third vertical, driven by a config registry
Introduces /massage alongside barber and beauty: landing page, booking
flow, admin support, home tile and SEO entries, in all three languages.

Architecture
- application/config/verticals.php + vertical_helper.php: one registry
  entry per vertical (branding, assets, views, behaviour flags). A fourth
  vertical is a config entry plus content files.
- Strangler: barber and beauty keep pointing at their existing view files,
  so their rendered HTML is unchanged. Only massage uses the new generic
  pages/vertical-*.php and includes/vertical-*.php views, which collapse
  the four duplicated per-language nav/footer branches into one.
- Pages::vertical() + one route; booking(), booking_finished(),
  _booking_error() and manage_booking() are now registry-driven.

Worker/vertical coupling
- getActiveWorkers() derives the vertical from services.service_category_id
  instead of the workers.is_barber / is_beauty flags, which were a
  hand-maintained cache of exactly that fact. Verified against production
  data: the derived set reproduced the stored flags for every worker, in
  both verticals. No schema change was needed for massage.
- The legacy flags are now written through from the category so a rollback
  cannot strand a new worker, and the admin worker UI shows the derived
  verticals read-only instead of two dropdowns that controlled nothing.

Bug fixes found along the way (all pre-existing)
- booking_process() had no server-side category guard; cross-vertical
  mixing was prevented only by client-side JS.
- add-service-form / add-worker-form emitted `selected` on every category
  option, so the newest category silently became the default.
- update-service-form offered only barber/beauty, so editing a service of
  any other type silently rewrote it.
- getWorkerScheduleByDay ignored schedule overrides while getAvailableTimes
  honoured them, so slots could be shown and then rejected. Added an
  override-aware getWorkerScheduleForDate() and used it in both guards.
- Booking lists dereferenced a null service if one had been hard-deleted.
- main.css: .tiles was tuned for exactly two tiles, including an
  absolutely-positioned .style1 at the 1280px breakpoint.

Massage-specific behaviour, opt-in per vertical
- strip_category_prefix: grouped service lists show "50 min" under the
  treatment heading rather than repeating the full name. The full name is
  carried in data-service-name so the totals panel stays unambiguous, and
  services.service_name is untouched for emails and admin.
- single_service_booking: one treatment per booking, enforced in the UI and
  in booking_process(). Re-clicking the selection releases it.
- Displayed treatment time (50/80/110 min) is in the service name; the
  booked slot (60/90/120 min) is service_time and covers changing and
  payment. service_time is never shown to the guest.

DB migrations for dev/prod are in documents/ - additive only, no ALTER.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 16:34:47 +00:00

1452 lines
61 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Admin extends CI_Controller {
/**
* Index Page for this controller.
*
* Maps to the following URL
* http://example.com/index.php/welcome
* - or -
* http://example.com/index.php/welcome/index
* - or -
* Since this controller is set as the default controller in
* config/routes.php, it's displayed at http://example.com/
*
* So any other public methods not prefixed with an underscore will
* map to /index.php/welcome/<method_name>
* @see https://codeigniter.com/userguide3/general/urls.html
*/
public function dashboard(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = 'Vezérlőpult';
$data['message'] = '';
$data['message_class'] = '';
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);
}
$this->load->view('admin/dashboard', $data);
}
else{
header("Location:".SITEURL."login");
}
}
public function services(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = 'Szolgáltatások';
$data['message'] = '';
$data['message_class'] = '';
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);
}
$data['results'] = $this->Service_model->getAllServices();
$this->load->view('admin/services', $data);
}
else{
header("Location:".SITEURL."login");
}
}
public function add_service(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = 'Szolgáltatás hozzáadás';
$data['message'] = '';
$data['message_class'] = '';
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);
}
$data['serviceCategories'] = $this->Service_model->getAllServiceCategories();
$this->load->view('admin/add-service', $data);
}
else{
header("Location:".SITEURL."login");
}
}
public function worker_schedule() {
$this->load->helper('url');
$this->load->model('Service_model');
$this->load->model('User_model');
$this->load->model('Module_model');
if (!isset($_SESSION['username'])) {
header("Location:".SITEURL."login");
exit;
}
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
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);
}
$data['workers'] = $this->Service_model->getAllWorkers();
$worker_id = $this->input->get('worker_id');
if ($worker_id) {
$data['selectedWorkerId'] = $worker_id;
$data['schedule'] = $this->Service_model->getWorkerSchedule($worker_id);
} else {
$data['selectedWorkerId'] = null;
$data['schedule'] = [];
}
$this->load->view('admin/worker_schedule', $data);
}
public function worker_schedule_overrides() {
$this->load->model('Service_model');
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['workerSchedules'] = $this->Service_model->getWorkerScheduleOverrides();
$data['workers'] = $this->Service_model->getAllWorkers();
$data['pageTitle'] = 'Dolgozói egyedi munkaidők';
$this->load->view('admin/worker_schedule_overrides', $data);
}
public function admin_worker_schedule() {
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$data['pageTitle'] = 'Munkarend beállítása';
$data['workers'] = $this->Service_model->getAllWorkers();
if ($this->input->post('storeSchedule')) {
$this->load->database();
$this->db->where('worker_id', $_POST['worker_id']);
$this->db->delete('worker_schedule');
foreach ($_POST['weekday'] as $i => $day) {
$insert = [
'worker_id' => $_POST['worker_id'],
'weekday' => $day,
'start_time' => $_POST['start_time'][$i],
'end_time' => $_POST['end_time'][$i],
'is_alternate_week' => isset($_POST['is_alternate_week'][$i]) ? 1 : 0
];
$this->db->insert('worker_schedule', $insert);
}
}
if (!empty($_GET['worker_id'])) {
$this->load->database();
$worker_id = $_GET['worker_id'];
$data['selectedWorkerId'] = $worker_id;
$data['schedule'] = $this->db->query("SELECT * FROM worker_schedule WHERE worker_id = ?", array($worker_id))->result();
}
$this->load->view('admin/worker_schedule', $data);
}
public function store_worker_schedule() {
$this->load->model('Service_model');
$this->load->helper('url');
$worker_id = $this->input->post('worker_id');
if (!$worker_id) {
show_error('Dolgozó nincs kiválasztva.');
return;
}
// Clear existing schedule
$this->Service_model->clearWorkerSchedule($worker_id);
// Loop through submitted times
for ($i = 0; $i <= 6; $i++) {
$start = $this->input->post("start_time[$i]");
$end = $this->input->post("end_time[$i]");
$alt = $this->input->post("alternate_week[$i]") ? 1 : 0;
if (!empty($start) && !empty($end)) {
$this->Service_model->addWorkerSchedule([
'worker_id' => $worker_id,
'weekday' => $i,
'start_time' => $start,
'end_time' => $end,
'is_alternate_week' => $alt
]);
}
}
redirect('admin/worker_schedule');
}
public function manage_worker_schedule($worker_id = null) {
$this->load->model('Service_model');
$data['workers'] = $this->Service_model->getAllWorkers();
$data['selectedWorker'] = null;
$data['schedule'] = [];
if ($worker_id) {
$data['selectedWorker'] = $this->Service_model->getWorkerById($worker_id);
$data['schedule'] = $this->Service_model->getWorkerSchedule($worker_id);
}
$this->load->view('admin/manage_worker_schedule', $data);
}
public function save_worker_schedule() {
$this->load->model('Service_model');
$worker_id = $this->input->post('worker_id');
$this->Service_model->clearWorkerSchedule($worker_id);
foreach ($_POST['weekday'] as $index => $day) {
if (!empty($_POST['start_time'][$index]) && !empty($_POST['end_time'][$index])) {
$this->Service_model->addWorkerSchedule([
'worker_id' => $worker_id,
'weekday' => $day,
'start_time' => $_POST['start_time'][$index],
'end_time' => $_POST['end_time'][$index],
'is_alternate_week' => isset($_POST['is_alternate_week'][$index]) ? 1 : 0
]);
}
}
redirect('admin/manage_worker_schedule/' . $worker_id);
}
public function update_service($service_id){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = 'Szolgáltatás módosítása (#'.$service_id.')';
$data['message'] = '';
$data['message_class'] = '';
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);
}
$data['selectedItem'] = $this->Service_model->getServiceById($service_id);
$data['serviceCategories'] = $this->Service_model->getAllServiceCategories();
$this->load->view('admin/update-service', $data);
}
else{
header("Location:".SITEURL."login");
}
}
public function service_process(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Log_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = '';
$data['message'] = '';
$data['message_class'] = '';
if(isset($_GET['delete-service'])){
$selected_service = $this->Service_model->getServiceById($_GET['delete-service']);
$serviceArray = array(
'is_deleted' => '1'
);
$this->Service_model->updateService($_GET['delete-service'], $serviceArray);
$this->Log_model->addLog('services', $data['currentUser']->username.' törölte a(z) '.$_GET['delete-service'].' számú szolgáltatást ('.$selected_service->service_name_hu.')', $data['currentUser']->username);
}
if(isset($_POST['addNewService'])){
$allowedServiceTypes = array('barber', 'beauty', 'massage');
$serviceArray = array(
'service_type' => (isset($_POST['service_type']) && in_array($_POST['service_type'], $allowedServiceTypes, true)) ? $_POST['service_type'] : 'beauty',
'service_category_no' => $_POST['service_category_no'],
'service_category_en' => $_POST['service_category_en'],
'service_category_hu' => $_POST['service_category_hu'],
'service_name_no' => $_POST['service_name_no'],
'service_name_en' => $_POST['service_name_en'],
'service_name_hu' => $_POST['service_name_hu'],
'service_description_no' => $_POST['service_description_no'],
'service_description_en' => $_POST['service_description_en'],
'service_description_hu' => $_POST['service_description_hu'],
'service_price' => $_POST['service_price'],
'service_time' => $_POST['service_time'],
'service_category_id' => $_POST['service_category_id'],
'is_enabled' => $_POST['is_enabled']
);
$this->Log_model->addLog('services', $data['currentUser']->username.' hozzáadta a(z) '.$_POST['service_name_hu'].' szolgáltatást ('.implode(',',$serviceArray).')', $data['currentUser']->username);
$this->Service_model->createService($serviceArray);
}
if(isset($_POST['updateService'])){
$allowedServiceTypes = array('barber', 'beauty', 'massage');
$existingService = $this->Service_model->getServiceById($_POST['service_id']);
$serviceArray = array(
'service_type' => (isset($_POST['service_type']) && in_array($_POST['service_type'], $allowedServiceTypes, true)) ? $_POST['service_type'] : ($existingService ? $existingService->service_type : 'beauty'),
'service_category_no' => $_POST['service_category_no'],
'service_category_en' => $_POST['service_category_en'],
'service_category_hu' => $_POST['service_category_hu'],
'service_name_no' => $_POST['service_name_no'],
'service_name_en' => $_POST['service_name_en'],
'service_name_hu' => $_POST['service_name_hu'],
'service_description_no' => $_POST['service_description_no'],
'service_description_en' => $_POST['service_description_en'],
'service_description_hu' => $_POST['service_description_hu'],
'service_price' => $_POST['service_price'],
'service_time' => $_POST['service_time'],
'service_category_id' => $_POST['service_category_id'],
'is_enabled' => $_POST['is_enabled']
);
$this->Log_model->addLog('services', $data['currentUser']->username.' módosította a(z) '.$_POST['service_name_hu'].' szolgáltatást ('.implode(',',$serviceArray).')', $data['currentUser']->username);
$this->Service_model->updateService($_POST['service_id'], $serviceArray);
}
header('Location:'.SITEURL.'services');
$this->load->view('admin/service-process', $data);
}
}
public function service_categories(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = 'Szolgáltatás kategóriák';
$data['message'] = '';
$data['message_class'] = '';
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);
}
$data['results'] = $this->Service_model->getAllServiceCategories();
$this->load->view('admin/service-categories', $data);
}
else{
header("Location:".SITEURL."login");
}
}
public function add_service_category(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = 'Szolgáltatás kategória hozzáadása';
$data['message'] = '';
$data['message_class'] = '';
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);
}
$this->load->view('admin/add-service-category', $data);
}
else{
header("Location:".SITEURL."login");
}
}
public function update_service_category($service_category_id){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = 'Szolgáltatás módosítása (#'.$service_category_id.')';
$data['message'] = '';
$data['message_class'] = '';
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);
}
$data['selectedItem'] = $this->Service_model->getServiceCategoryById($service_category_id);
$this->load->view('admin/update-service-category', $data);
}
else{
header("Location:".SITEURL."login");
}
}
public function service_category_process(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Log_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = '';
$data['message'] = '';
$data['message_class'] = '';
if(isset($_GET['delete-service-category'])){
$selected_service = $this->Service_model->getServiceCategoryById($_GET['delete-service-category']);
$serviceArray = array(
'is_deleted' => '1'
);
$this->Service_model->updateServiceCategory($_GET['delete-service-category'], $serviceArray);
}
if(isset($_POST['addNewServiceCategory'])){
$serviceCategoryArray = array(
'serv_cat_slug' => $_POST['serv_cat_slug'],
'serv_cat_name' => $_POST['serv_cat_name']
);
$this->Service_model->createServiceCategory($serviceCategoryArray);
}
if(isset($_POST['updateServiceCategory'])){
$serviceCategoryArray = array(
'serv_cat_slug' => $_POST['serv_cat_slug'],
'serv_cat_name' => $_POST['serv_cat_name']
);
$this->Service_model->updateServiceCategory($_POST['service_category_id'], $serviceCategoryArray);
}
header('Location:'.SITEURL.'service-categories');
$this->load->view('admin/service-category-process', $data);
}
}
public function workers(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = 'Dolgozók';
$data['message'] = '';
$data['message_class'] = '';
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);
}
$data['results'] = $this->Service_model->getAllWorkers();
$this->load->view('admin/workers', $data);
}
else{
header("Location:".SITEURL."login");
}
}
public function add_worker(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = 'Dolgozó hozzáadása';
$data['message'] = '';
$data['message_class'] = '';
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);
}
$data['serviceCategories'] = $this->Service_model->getAllServiceCategories();
$this->load->view('admin/add-worker', $data);
}
else{
header("Location:".SITEURL."login");
}
}
public function update_worker($worker_id){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = 'Dolgozó módosítása (#'.$worker_id.')';
$data['message'] = '';
$data['message_class'] = '';
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);
}
$data['serviceCategories'] = $this->Service_model->getAllServiceCategories();
$data['selectedItem'] = $this->Service_model->getWorkerById($worker_id);
// Verticals are derived from the worker's service category, not stored.
$data['workerVerticals'] = $this->Service_model->getVerticalsForWorker($worker_id);
$this->load->view('admin/update-worker', $data);
}
else{
header("Location:".SITEURL."login");
}
}
public function worker_process(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Log_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = '';
$data['message'] = '';
$data['message_class'] = '';
if(isset($_GET['delete-worker'])){
$selected_worker = $this->Service_model->getWorkerById($_GET['delete-worker']);
$workerArray = array(
'is_deleted' => '1'
);
$this->Service_model->updateWorker($_GET['delete-worker'], $workerArray);
$this->Log_model->addLog('workers', $data['currentUser']->username.' törölte a(z) '.$_GET['delete-worker'].' számú dolgozót ('.$selected_worker->worker_name.')', $data['currentUser']->username);
}
if(isset($_POST['addNewWorker'])){
$data['worker_img'] = '';
if($_FILES["worker_img"]["tmp_name"] != ''){
$target_dir = getcwd()."/assets/img/";
$target_file = $target_dir . 'worker_'.str_replace(' ','_', $_POST['worker_name']);
$imageFileType = strtolower(pathinfo(basename($_FILES["worker_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["worker_img"]["tmp_name"], $target_file.'.'.$imageFileType)) {
$data['message'] = 'A profilkép sikeresen feltöltve!';
$data['message_class'] = 'successMessage';
$data['worker_img'] = 'worker_'.str_replace(' ','_', $_POST['worker_name']).'.'.$imageFileType;
} else {
$data['message'] = 'Hiba a feltöltés közben!';
$data['message_class'] = 'errorMessage';
}
}
// is_beauty / is_barber are no longer submitted by the form - the
// vertical is derived from service_category_id. Service_model
// keeps the legacy columns consistent on write.
$workerArray = array(
'worker_name' => $_POST['worker_name'],
'worker_info' => $_POST['worker_info'],
'worker_profile_img' => $data['worker_img'],
'service_category_id' => $_POST['service_category_id'],
'is_active' => $_POST['is_active']
);
$this->Log_model->addLog('workers', $data['currentUser']->username.' létrehozta a(z) '.$_POST['worker_name'].' nevű dolgozót ('.implode(',',$workerArray).')', $data['currentUser']->username);
$this->Service_model->createWorker($workerArray);
}
if(isset($_POST['updateWorker'])){
$workerArray = array(
'worker_name' => $_POST['worker_name'],
'worker_info' => $_POST['worker_info'],
'service_category_id' => $_POST['service_category_id'],
'is_active' => $_POST['is_active'],
'google_calendar_id' => isset($_POST['google_calendar_id']) ? $_POST['google_calendar_id'] : '',
'ntfy_topic' => isset($_POST['ntfy_topic']) ? $_POST['ntfy_topic'] : '',
'lunch_window_start' => !empty($_POST['lunch_window_start']) ? $_POST['lunch_window_start'] : null,
'lunch_window_end' => !empty($_POST['lunch_window_end']) ? $_POST['lunch_window_end'] : null,
'lunch_preferred_time' => !empty($_POST['lunch_preferred_time']) ? $_POST['lunch_preferred_time'] : null,
);
$this->Service_model->updateWorker($_POST['worker_id'], $workerArray);
$this->Log_model->addLog('workers', $data['currentUser']->username.' módosította a(z) '.$_POST['worker_name'].' nevű dolgozót ('.implode(',',$workerArray).')', $data['currentUser']->username);
if($_FILES["worker_img"]["tmp_name"] != ''){
$target_dir = getcwd()."/assets/img/";
$target_file = $target_dir . 'worker_'.str_replace(' ','_', $_POST['worker_name']);
$imageFileType = strtolower(pathinfo(basename($_FILES["worker_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["worker_img"]["tmp_name"], $target_file.'.'.$imageFileType)) {
$data['message'] = 'A profilkép sikeresen feltöltve!';
$data['message_class'] = 'successMessage';
$data['worker_img'] = 'worker_'.str_replace(' ','_', $_POST['worker_name']).'.'.$imageFileType;
$workerArray = array(
'worker_profile_img' => $data['worker_img']
);
$this->Log_model->addLog('workers', $data['currentUser']->username.' profilképet állított be a(z) '.$_POST['worker_name'].' nevű dolgozóhoz', $data['currentUser']->username);
$this->Service_model->updateWorker($_POST['worker_id'], $workerArray);
} else {
$data['message'] = 'Hiba a feltöltés közben!';
$data['message_class'] = 'errorMessage';
}
}
}
header("Location:".SITEURL."workers");
$this->load->view('admin/worker-process', $data);
}
}
public function bookings(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = 'Foglalások';
$data['message'] = '';
$data['message_class'] = '';
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);
}
$data['workers'] = $this->Service_model->getActiveWorkers();
if(isset($_GET['worker'])){
$data['results'] = $this->Service_model->getActualBookingsByWorkerID($_GET['worker']);
}
else{
$data['results'] = $this->Service_model->getActualBookings();
}
$this->load->view('admin/bookings', $data);
}
else{
header("Location:".SITEURL."login");
}
}
public function booking_calendar(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = 'Foglalási naptár';
$data['message'] = '';
$data['message_class'] = '';
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);
}
// Compute week bounds
$weekParam = isset($_GET['week']) ? $_GET['week'] : date('Y-m-d');
$monday = new DateTime($weekParam);
$dow = (int)$monday->format('N'); // 1=Mon..7=Sun
if ($dow > 1) $monday->modify('-'.($dow - 1).' days');
$sunday = clone $monday;
$sunday->modify('+6 days');
$data['weekStart'] = $monday->format('Y-m-d');
$data['weekEnd'] = $sunday->format('Y-m-d');
$data['workers'] = $this->Service_model->getActiveWorkers();
$workerId = isset($_GET['worker_id']) ? $_GET['worker_id'] : null;
$data['bookings'] = $this->Service_model->getBookingsByDateRange($data['weekStart'], $data['weekEnd'], $workerId);
$this->load->view('admin/booking-calendar', $data);
} else {
header("Location:".SITEURL."login");
}
}
public function booking_calendar_data(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
if(!isset($_SESSION['username'])){
header('Content-Type: application/json');
echo json_encode(array('error' => 'unauthorized'));
return;
}
$weekParam = isset($_GET['week']) ? $_GET['week'] : date('Y-m-d');
$monday = new DateTime($weekParam);
$dow = (int)$monday->format('N');
if ($dow > 1) $monday->modify('-'.($dow - 1).' days');
$sunday = clone $monday;
$sunday->modify('+6 days');
$workerId = isset($_GET['worker_id']) && $_GET['worker_id'] !== '' ? $_GET['worker_id'] : null;
$bookings = $this->Service_model->getBookingsByDateRange($monday->format('Y-m-d'), $sunday->format('Y-m-d'), $workerId);
$result = array();
foreach ($bookings as $b) {
$services = array();
if (is_array($b->services)) {
foreach ($b->services as $s) {
$services[] = array(
'service_name' => $s->service_name,
'service_price' => $s->service_price,
'service_time' => $s->service_time
);
}
}
$result[] = array(
'booking_id' => $b->booking_id,
'guest_name' => $b->guest_name,
'guest_email' => $b->guest_email,
'guest_phone' => $b->guest_phone,
'worker_id' => $b->worker_id,
'worker_name' => $b->worker_name,
'booking_date' => $b->booking_date,
'booking_start_time' => $b->booking_start_time,
'booking_finish_time' => $b->booking_finish_time,
'services' => $services,
'service_names_joined' => $b->service_names_joined
);
}
header('Content-Type: application/json');
echo json_encode($result);
}
public function add_booking(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = 'Foglalás hozzáadása';
$data['message'] = '';
$data['message_class'] = '';
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);
}
$available_services = $this->Service_model->getAvailableServices();
$serviceFile = fopen("load_services.json", "w") or die("Unable to open file!");
fwrite($serviceFile, json_encode($available_services));
fclose($serviceFile);
$data['workers'] = $this->Service_model->getAllWorkers();
$this->load->view('admin/add-booking', $data);
}
else{
header("Location:".SITEURL."login");
}
}
public function update_booking($booking_id){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Module_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = 'Foglalás módosítása (#'.$booking_id.')';
$data['message'] = '';
$data['message_class'] = '';
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);
}
$available_services = $this->Service_model->getAvailableServices();
$serviceFile = fopen("load_services.json", "w") or die("Unable to open file!");
fwrite($serviceFile, json_encode($available_services));
fclose($serviceFile);
$data['workers'] = $this->Service_model->getAllWorkers();
$data['selectedItem'] = $this->Service_model->getBookingById($booking_id);
$this->load->view('admin/update-booking', $data);
}
else{
header("Location:".SITEURL."login");
}
}
public function booking_process(){
$this->load->helper('url');
$this->load->model('User_model');
$this->load->model('Service_model');
$this->load->model('Log_model');
if(isset($_SESSION['username'])){
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
$data['pageTitle'] = '';
$data['message'] = '';
$data['message_class'] = '';
if(isset($_GET['delete-booking'])){
$selectedBooking = $this->Service_model->getBookingById($_GET['delete-booking']);
// Google Calendar: delete events before removing booking
$this->load->library('GoogleCalendar');
$this->config->load('google_calendar');
$delWorker = $this->Service_model->getWorkerById($selectedBooking->worker_id);
if(!empty($selectedBooking->gcal_event_id_worker) && $delWorker && !empty($delWorker->google_calendar_id)){
$this->googlecalendar->deleteEvent($delWorker->google_calendar_id, $selectedBooking->gcal_event_id_worker);
}
$evelinCalIdDel = $this->config->item('gcal_evelin_calendar_id');
if(!empty($selectedBooking->gcal_event_id_owner) && !empty($evelinCalIdDel)){
$this->googlecalendar->deleteEvent($evelinCalIdDel, $selectedBooking->gcal_event_id_owner);
}
// ntfy push notification
$this->_ntfy(
'Admin deleted booking: ' . $selectedBooking->guest_name,
'Worker: ' . ($delWorker ? $delWorker->worker_name : '?') . "\n" .
'Date: ' . $selectedBooking->booking_date . ' ' . $selectedBooking->booking_start_time,
($delWorker && isset($delWorker->ntfy_topic)) ? $delWorker->ntfy_topic : null
);
$this->Service_model->deleteBooking($_GET['delete-booking']);
$this->Log_model->addLog('bookings', $data['currentUser']->username.' törölte a(z) '.$_GET['delete-booking'].' számú foglalást ('.$selectedBooking->guest_name.', '.$selectedBooking->guest_email.', '.$selectedBooking->guest_phone.', '.$selectedBooking->booking_date.', '.$selectedBooking->booking_start_time.', '.$selectedBooking->booking_finish_time.', '.$selectedBooking->service_ids.')', $data['currentUser']->username);
header('Location:'.SITEURL.'bookings');
}
if(isset($_POST['addNewBookingManual'])){
$booking_date = new DateTime(date('Y-m-d', strtotime($_POST['booking_date'])));
$end_booking_date = new DateTime(date('Y-m-d', strtotime($_POST['end_booking_date'])));
$diff = $booking_date->diff($end_booking_date);
$selectedServiceArray = array();
if($_POST['service_ids'] != ''){
$selectedServiceArray = explode(',', $_POST['service_ids']);
}
if($_POST['booking_date'] != $_POST['end_booking_date'] && $booking_date <= $end_booking_date){
for($index = 0; $index <= $diff->days; $index++){
$selectedDate = date('Y-m-d', strtotime($_POST['booking_date']. ' + '.$index.' days'));
$manageToken = bin2hex(random_bytes(16));
$bookingArray = array(
'guest_name' => $_POST['guest_name'],
'guest_email' => $_POST['guest_email'],
'guest_phone' => $_POST['guest_phone'],
'worker_id' => $_POST['worker_id'],
'booking_date' => $selectedDate,
'booking_start_time' => $_POST['booking_start_time'],
'booking_finish_time' => $_POST['booking_finish_time'],
'service_ids' => serialize($selectedServiceArray),
'guest_confirmed' => '0',
'guest_confirm_code' => '1234',
'manage_token' => $manageToken,
);
$this->Service_model->createBooking($bookingArray);
}
}
else{
$manageToken = bin2hex(random_bytes(16));
$bookingArray = array(
'guest_name' => $_POST['guest_name'],
'guest_email' => $_POST['guest_email'],
'guest_phone' => $_POST['guest_phone'],
'worker_id' => $_POST['worker_id'],
'booking_date' => $_POST['booking_date'],
'booking_start_time' => $_POST['booking_start_time'],
'booking_finish_time' => $_POST['booking_finish_time'],
'service_ids' => serialize($selectedServiceArray),
'guest_confirmed' => '0',
'guest_confirm_code' => '1234',
'manage_token' => $manageToken,
);
$this->Log_model->addLog('bookings', $data['currentUser']->username.' létrehoza a(z) '.$_POST['guest_name'].' nevű vendég foglalást ('.implode(', ', $bookingArray).')', $data['currentUser']->username);
$this->Service_model->createBooking($bookingArray);
}
if($_POST['send_notification']){
$worker = $this->Service_model->getWorkerById($bookingArray['worker_id']);
$services = unserialize($bookingArray['service_ids']);
$serviceArray = array();
$emailArray = array();
if(is_array($services)){
foreach($services as $serviceItem){
$selectedService = $this->Service_model->getServiceById($serviceItem);
if(is_object($selectedService)){
$serviceArray[] = $selectedService;
}
}
}
$content = '';
switch($_POST['lang']){
case 'no':
$content = 'Kjære gjest,<br />';
$content .= 'Takk for din bestilling!<br />';
$content .= '<br />';
$content .= '<table>';
$content .= '<tr><td>Navn: </td><td>'.$bookingArray['guest_name'].'</td></tr>';
$content .= '<tr><td>E-post: </td><td>'.$bookingArray['guest_email'].'</td></tr>';
$content .= '<tr><td>Telefon: </td><td>'.$bookingArray['guest_phone'].'</td></tr>';
$content .= '<tr><td>Arbeider: </td><td>'.$worker->worker_name.'</td></tr>';
$content .= '<tr><td>Dato: </td><td>'.$bookingArray['booking_date'].'</td></tr>';
$content .= '<tr><td>Tid: </td><td>'.$bookingArray['booking_start_time'].' - '.$bookingArray['booking_finish_time'].'</td></tr>';
$content .= '<tr><td>Tjenester: </td><td><table>';
$totalServicePrice = 0;
foreach($serviceArray as $serviceArrayItem){
$totalServicePrice += $serviceArrayItem->service_price;
$content .= '<tr><td>'.$serviceArrayItem->service_name_no.'</td><td>'.$serviceArrayItem->service_price.' kr</td></tr>';
}
$content .= '</table></td>';
$content .= '<tr><td>Totalpris: </td><td><strong>'.$totalServicePrice.' kr</strong></td></tr>';
$content .= '</table>';
$content .= '<br /><br /><a href="'.SITEURL.$_POST['lang'].'/manage-booking/'.$manageToken.'">Administrer bestilling</a><br />';
$content .= '<br /> 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 <a href="mailto:info@studiobeve.no">info@studiobeve.no</a> eller ved å sende oss en melding på Instagram eller Facebook. Takk for forståelsen og samarbeidet!';
$content .= '<br />';
$content .= '<br /> STUDIOBEVE';
$this->Log_model->addLog('bookings', $data['currentUser']->username.' új foglalásról norvég nyelvű értesítést küldött a(z) '.$bookingArray['guest_email'].' email címre ('.implode(', ', $bookingArray).')', $data['currentUser']->username);
break;
case 'en':
$content = 'Dear Guest,<br />';
$content .= 'Thank you for your booking!<br />';
$content .= '<br />';
$content .= '<table>';
$content .= '<tr><td>Name: </td><td>'.$bookingArray['guest_name'].'</td></tr>';
$content .= '<tr><td>Email: </td><td>'.$bookingArray['guest_email'].'</td></tr>';
$content .= '<tr><td>Phone: </td><td>'.$bookingArray['guest_phone'].'</td></tr>';
$content .= '<tr><td>Worker: </td><td>'.$worker->worker_name.'</td></tr>';
$content .= '<tr><td>Date: </td><td>'.$bookingArray['booking_date'].'</td></tr>';
$content .= '<tr><td>Time: </td><td>'.$bookingArray['booking_start_time'].' - '.$bookingArray['booking_finish_time'].'</td></tr>';
$content .= '<tr><td>Services: </td><td><table>';
$totalServicePrice = 0;
foreach($serviceArray as $serviceArrayItem){
$totalServicePrice += $serviceArrayItem->service_price;
$content .= '<tr><td>'.$serviceArrayItem->service_name_en.'</td><td>'.$serviceArrayItem->service_price.' kr</td></tr>';
}
$content .= '</table></td>';
$content .= '<tr><td>Total price: </td><td><strong>'.$totalServicePrice.' kr</strong></td></tr>';
$content .= '</table>';
$content .= '<br /><br /><a href="'.SITEURL.$_POST['lang'].'/manage-booking/'.$manageToken.'">Manage booking</a><br />';
$content .= '<br /> 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 <a href="mailto:info@studiobeve.no">info@studiobeve.no</a>, or by sending us a message on Instagram or Facebook. Thank you for your understanding and cooperation!';
$content .= '<br />';
$content .= '<br /> STUDIOBEVE';
$this->Log_model->addLog('bookings', $data['currentUser']->username.' új foglalásról angol nyelvű értesítést küldött a(z) '.$bookingArray['guest_email'].' email címre ('.implode(', ', $bookingArray).')', $data['currentUser']->username);
break;
case 'hu':
$content = 'Kedves Vendégünk,<br />';
$content .= 'Köszönettel fogadtuk a foglalását!<br />';
$content .= '<br />';
$content .= '<table>';
$content .= '<tr><td>Név: </td><td>'.$bookingArray['guest_name'].'</td></tr>';
$content .= '<tr><td>Email: </td><td>'.$bookingArray['guest_email'].'</td></tr>';
$content .= '<tr><td>Telefon: </td><td>'.$bookingArray['guest_phone'].'</td></tr>';
$content .= '<tr><td>Dolgozó: </td><td>'.$worker->worker_name.'</td></tr>';
$content .= '<tr><td>Dátum: </td><td>'.$bookingArray['booking_date'].'</td></tr>';
$content .= '<tr><td>Időpont: </td><td>'.$bookingArray['booking_start_time'].' - '.$bookingArray['booking_finish_time'].'</td></tr>';
$content .= '<tr><td>Szolgáltatás(ok): </td><td><table>';
$totalServicePrice = 0;
foreach($serviceArray as $serviceArrayItem){
$totalServicePrice += $serviceArrayItem->service_price;
$content .= '<tr><td>'.$serviceArrayItem->service_name_hu.'</td><td>'.$serviceArrayItem->service_price.' kr</td></tr>';
}
$content .= '</table></td>';
$content .= '<tr><td>Összesen fizetendő: </td><td><strong>'.$totalServicePrice.' kr</strong></td></tr>';
$content .= '</table>';
$content .= '<br /><br /><a href="'.SITEURL.$_POST['lang'].'/manage-booking/'.$manageToken.'">Foglalás kezelése</a><br />';
$content .= '<br /> 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 <a href="mailto:info@studiobeve.no">info@studiobeve.no</a> 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 .= '<br />';
$content .= '<br /> STUDIOBEVE';
$this->Log_model->addLog('bookings', $data['currentUser']->username.' új foglalásról magyar nyelvű értesítést küldött a(z) '.$bookingArray['guest_email'].' email címre ('.implode(', ', $bookingArray).')', $data['currentUser']->username);
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;
$this->User_model->sendEmail($emailArray, $_POST['lang'], '');
}
header('Location:'.SITEURL.'bookings');
}
if(isset($_POST['updateBookingManual'])){
$selectedServiceArray = array();
if($_POST['service_ids'] != ''){
$selectedServiceArray = explode(',', $_POST['service_ids']);
}
$bookingArray = array(
'guest_name' => $_POST['guest_name'],
'guest_email' => $_POST['guest_email'],
'guest_phone' => $_POST['guest_phone'],
'worker_id' => $_POST['worker_id'],
'booking_date' => $_POST['booking_date'],
'booking_start_time' => $_POST['booking_start_time'],
'booking_finish_time' => $_POST['booking_finish_time'],
'service_ids' => serialize($selectedServiceArray),
'guest_confirmed' => '0',
'guest_confirm_code' => '1234'
);
$oldBooking = $this->Service_model->getBookingById($_POST['booking_id']);
$this->Service_model->updateBooking($_POST['booking_id'], $bookingArray);
$this->Log_model->addLog('bookings', $data['currentUser']->username.' módosította a foglalását a(z) '.$bookingArray['guest_name'].' nevű vendégnek ('.implode(', ', $bookingArray).')', $data['currentUser']->username);
// 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');
if($oldBooking){
$oldWorker = $this->Service_model->getWorkerById($oldBooking->worker_id);
if(!empty($oldBooking->gcal_event_id_worker) && $oldWorker && !empty($oldWorker->google_calendar_id)){
$this->googlecalendar->deleteEvent($oldWorker->google_calendar_id, $oldBooking->gcal_event_id_worker);
}
if(!empty($oldBooking->gcal_event_id_owner) && !empty($evelinCalId)){
$this->googlecalendar->deleteEvent($evelinCalId, $oldBooking->gcal_event_id_owner);
}
}
$gcalWorker = $this->Service_model->getWorkerById($bookingArray['worker_id']);
$gcalServices = unserialize($bookingArray['service_ids']);
$gcalServiceArray = array();
if(is_array($gcalServices)){
foreach($gcalServices as $sid){
$svc = $this->Service_model->getServiceById($sid);
if(is_object($svc)) $gcalServiceArray[] = $svc;
}
}
$gcalServiceNames = implode(', ', array_map(function($s){ return $s->service_name_no; }, $gcalServiceArray));
$gcalTitle = $gcalWorker->worker_name . ' — ' . $gcalServiceNames . ' — ' . $bookingArray['guest_name'];
$gcalDescription = 'Guest: ' . $bookingArray['guest_name'] . "\nPhone: " . $bookingArray['guest_phone'] . "\nEmail: " . $bookingArray['guest_email'];
$gcalStart = $bookingArray['booking_date'] . 'T' . $bookingArray['booking_start_time'];
$gcalEnd = $bookingArray['booking_date'] . 'T' . $bookingArray['booking_finish_time'];
$newWorkerEventId = null;
$newOwnerEventId = null;
if(!empty($gcalWorker->google_calendar_id)){
$newWorkerEventId = $this->googlecalendar->createEvent($gcalWorker->google_calendar_id, $gcalTitle, $gcalDescription, $gcalStart, $gcalEnd);
}
if(!empty($evelinCalId)){
$newOwnerEventId = $this->googlecalendar->createEvent($evelinCalId, $gcalTitle, $gcalDescription, $gcalStart, $gcalEnd);
}
$this->Service_model->updateBookingCalEvents($_POST['booking_id'], $newWorkerEventId, $newOwnerEventId);
// ntfy push notification
$this->_ntfy(
'Admin modified booking: ' . $bookingArray['guest_name'],
'Worker: ' . $gcalWorker->worker_name . "\n" .
'Date: ' . $bookingArray['booking_date'] . ' ' . $bookingArray['booking_start_time'] . ' - ' . $bookingArray['booking_finish_time'] . "\n" .
'Services: ' . $gcalServiceNames,
isset($gcalWorker->ntfy_topic) ? $gcalWorker->ntfy_topic : null
);
if($_POST['send_notification']){
$worker = $this->Service_model->getWorkerById($bookingArray['worker_id']);
$services = unserialize($bookingArray['service_ids']);
$serviceArray = array();
$emailArray = array();
if(is_array($services)){
foreach($services as $serviceItem){
$selectedService = $this->Service_model->getServiceById($serviceItem);
if(is_object($selectedService)){
$serviceArray[] = $selectedService;
}
}
}
$manageToken = (isset($oldBooking) && !empty($oldBooking->manage_token)) ? $oldBooking->manage_token : '';
if(empty($manageToken)){
$manageToken = bin2hex(random_bytes(16));
$this->Service_model->updateBooking($_POST['booking_id'], array('manage_token' => $manageToken));
}
$content = '';
$email_subject ='';
switch($_POST['lang']){
case 'no':
$email_subject ='[studiobeve] Din reservasjon er endret';
$content = 'Kjære gjest,<br />';
$content .= 'Vi vil gjerne informere deg om at din reservasjon er endret.<br />';
$content .= '<br />';
$content .= '<table>';
$content .= '<tr><td>Navn: </td><td>'.$bookingArray['guest_name'].'</td></tr>';
$content .= '<tr><td>E-post: </td><td>'.$bookingArray['guest_email'].'</td></tr>';
$content .= '<tr><td>Telefon: </td><td>'.$bookingArray['guest_phone'].'</td></tr>';
$content .= '<tr><td>Arbeider: </td><td>'.$worker->worker_name.'</td></tr>';
$content .= '<tr><td>Dato: </td><td>'.$bookingArray['booking_date'].'</td></tr>';
$content .= '<tr><td>Tid: </td><td>'.$bookingArray['booking_start_time'].' - '.$bookingArray['booking_finish_time'].'</td></tr>';
$content .= '<tr><td>Tjenester: </td><td><table>';
$totalServicePrice = 0;
foreach($serviceArray as $serviceArrayItem){
$totalServicePrice += $serviceArrayItem->service_price;
$content .= '<tr><td>'.$serviceArrayItem->service_name_no.'</td><td>'.$serviceArrayItem->service_price.' kr</td></tr>';
}
$content .= '</table></td>';
$content .= '<tr><td>Totalpris: </td><td><strong>'.$totalServicePrice.' kr</strong></td></tr>';
$content .= '</table>';
$content .= '<br /><br /><a href="'.SITEURL.$_POST['lang'].'/manage-booking/'.$manageToken.'">Administrer bestilling</a><br />';
$content .= '<br /> 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 <a href="mailto:info@studiobeve.no">info@studiobeve.no</a> eller ved å sende oss en melding på Instagram eller Facebook. Takk for forståelsen og samarbeidet!';
$content .= '<br />';
$content .= '<br /> STUDIOBEVE';
$this->Log_model->addLog('bookings', $data['currentUser']->username.' foglalás módosításról norvég nyelvű értesítést küldött a(z) '.$bookingArray['guest_email'].' email címre ('.implode(', ', $bookingArray).')', $data['currentUser']->username);
break;
case 'en':
$email_subject ='[studiobeve] Your booking has changed';
$content = 'Dear Guest,<br />';
$content .= 'We inform you that your booking has changed.<br />';
$content .= '<br />';
$content .= '<table>';
$content .= '<tr><td>Name: </td><td>'.$bookingArray['guest_name'].'</td></tr>';
$content .= '<tr><td>Email: </td><td>'.$bookingArray['guest_email'].'</td></tr>';
$content .= '<tr><td>Phone: </td><td>'.$bookingArray['guest_phone'].'</td></tr>';
$content .= '<tr><td>Worker: </td><td>'.$worker->worker_name.'</td></tr>';
$content .= '<tr><td>Date: </td><td>'.$bookingArray['booking_date'].'</td></tr>';
$content .= '<tr><td>Time: </td><td>'.$bookingArray['booking_start_time'].' - '.$bookingArray['booking_finish_time'].'</td></tr>';
$content .= '<tr><td>Services: </td><td><table>';
$totalServicePrice = 0;
foreach($serviceArray as $serviceArrayItem){
$totalServicePrice += $serviceArrayItem->service_price;
$content .= '<tr><td>'.$serviceArrayItem->service_name_en.'</td><td>'.$serviceArrayItem->service_price.' kr</td></tr>';
}
$content .= '</table></td>';
$content .= '<tr><td>Total price: </td><td><strong>'.$totalServicePrice.' kr</strong></td></tr>';
$content .= '</table>';
$content .= '<br /><br /><a href="'.SITEURL.$_POST['lang'].'/manage-booking/'.$manageToken.'">Manage booking</a><br />';
$content .= '<br /> 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 <a href="mailto:info@studiobeve.no">info@studiobeve.no</a>, or by sending us a message on Instagram or Facebook. Thank you for your understanding and cooperation!';
$content .= '<br />';
$content .= '<br /> STUDIOBEVE';
$this->Log_model->addLog('bookings', $data['currentUser']->username.' foglalás módosításról angol nyelvű értesítést küldött a(z) '.$bookingArray['guest_email'].' email címre ('.implode(', ', $bookingArray).')', $data['currentUser']->username);
break;
case 'hu':
$email_subject ='[studiobeve] A foglalása módosult';
$content = 'Kedves Vendégünk,<br />';
$content .= 'Tájékoztatjuk, hogy a foglalása módosult.<br />';
$content .= '<br />';
$content .= '<table>';
$content .= '<tr><td>Név: </td><td>'.$bookingArray['guest_name'].'</td></tr>';
$content .= '<tr><td>Email: </td><td>'.$bookingArray['guest_email'].'</td></tr>';
$content .= '<tr><td>Telefon: </td><td>'.$bookingArray['guest_phone'].'</td></tr>';
$content .= '<tr><td>Dolgozó: </td><td>'.$worker->worker_name.'</td></tr>';
$content .= '<tr><td>Dátum: </td><td>'.$bookingArray['booking_date'].'</td></tr>';
$content .= '<tr><td>Időpont: </td><td>'.$bookingArray['booking_start_time'].' - '.$bookingArray['booking_finish_time'].'</td></tr>';
$content .= '<tr><td>Szolgáltatás(ok): </td><td><table>';
$totalServicePrice = 0;
foreach($serviceArray as $serviceArrayItem){
$totalServicePrice += $serviceArrayItem->service_price;
$content .= '<tr><td>'.$serviceArrayItem->service_name_hu.'</td><td>'.$serviceArrayItem->service_price.' kr</td></tr>';
}
$content .= '</table></td>';
$content .= '<tr><td>Összesen fizetendő: </td><td><strong>'.$totalServicePrice.' kr</strong></td></tr>';
$content .= '</table>';
$content .= '<br /><br /><a href="'.SITEURL.$_POST['lang'].'/manage-booking/'.$manageToken.'">Foglalás kezelése</a><br />';
$content .= '<br /> 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 <a href="mailto:info@studiobeve.no">info@studiobeve.no</a> 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 .= '<br />';
$content .= '<br /> STUDIOBEVE';
$this->Log_model->addLog('bookings', $data['currentUser']->username.' foglalás módosításról magyar nyelvű értesítést küldött a(z) '.$bookingArray['guest_email'].' email címre ('.implode(', ', $bookingArray).')', $data['currentUser']->username);
break;
}
$data['selectedLang'] = $_POST['lang'];
$emailItem = array(
'addressee_email' => $bookingArray['guest_email'],
'addressee_name' => $bookingArray['guest_name'],
'subject' => $email_subject,
'content' => $content,
'attachments' => array()
);
$emailArray[] = $emailItem;
$this->User_model->sendEmail($emailArray, $_POST['lang'], '');
}
header('Location:'.SITEURL.'bookings');
}
$this->load->view('admin/booking-process', $data);
}
}
private function _ntfy($title, $message, $workerTopic = null){
$this->config->load('google_calendar');
$evelinTopic = $this->config->item('ntfy_topic');
$topics = [];
if(!empty($evelinTopic)) $topics[] = $evelinTopic;
if(!empty($workerTopic) && $workerTopic !== $evelinTopic) $topics[] = $workerTopic;
if(empty($topics)) return;
foreach($topics as $topic){
$ch = curl_init('https://ntfy.sh/' . $topic);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $message,
CURLOPT_HTTPHEADER => ['Title: ' . $title, 'Priority: high', 'Tags: calendar'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
]);
curl_exec($ch);
curl_close($ch);
}
}
public function worker_calendar($worker_id = null) {
$this->load->helper('url');
$this->load->model('Service_model');
$this->load->model('User_model');
$this->load->model('Module_model');
if (!isset($_SESSION['username'])) {
header("Location:".SITEURL."login");
exit;
}
$data['currentUser'] = $this->User_model->getUserByUsername($_SESSION['username']);
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);
}
$data['workers'] = $this->Service_model->getAllWorkers();
$data['selectedWorker'] = null;
$data['overridesByDate'] = [];
$data['scheduleRows'] = [];
$monthParam = $this->input->get('month');
if ($monthParam && preg_match('/^\d{4}-\d{2}$/', $monthParam)) {
list($year, $month) = explode('-', $monthParam);
$year = (int)$year;
$month = (int)$month;
} else {
$year = (int)date('Y');
$month = (int)date('m');
}
$data['year'] = $year;
$data['month'] = $month;
if ($worker_id) {
$data['selectedWorker'] = $this->Service_model->getWorkerById($worker_id);
$data['overridesByDate'] = $this->Service_model->getWorkerOverridesByMonth($worker_id, $year, $month);
$data['scheduleRows'] = $this->Service_model->getWorkerSchedule($worker_id);
}
// Compute lunch break slot for every working day of the month
$data['lunchByDate'] = [];
$sw = $data['selectedWorker'];
if ($sw && !empty($sw->lunch_window_start) && !empty($sw->lunch_window_end)) {
$monthBookings = $this->Service_model->getBookingsForWorkerMonth($worker_id, $year, $month);
$scheduleMap = [];
foreach ($data['scheduleRows'] as $row) {
$scheduleMap[$row->weekday] = $row;
}
$daysInMonth = (int)date('t', mktime(0, 0, 0, $month, 1, $year));
for ($d = 1; $d <= $daysInMonth; $d++) {
$dateStr = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT) . '-' . str_pad($d, 2, '0', STR_PAD_LEFT);
$override = isset($data['overridesByDate'][$dateStr]) ? $data['overridesByDate'][$dateStr] : null;
if ($override && $override->is_day_off) continue;
if ($override && !$override->is_day_off && $override->end_time) {
$shiftStart = new DateTime($dateStr . ' ' . $override->start_time);
$shiftEnd = new DateTime($dateStr . ' ' . $override->end_time);
} else {
$phpWeekday = (int)date('w', strtotime($dateStr));
if (!isset($scheduleMap[$phpWeekday])) continue;
$shiftStart = new DateTime($dateStr . ' ' . $scheduleMap[$phpWeekday]->start_time);
$shiftEnd = new DateTime($dateStr . ' ' . $scheduleMap[$phpWeekday]->end_time);
}
$dayBookings = isset($monthBookings[$dateStr]) ? $monthBookings[$dateStr] : [];
$lunch = $this->Service_model->computeLunchBreak(
$sw->lunch_window_start, $sw->lunch_window_end,
$dateStr, $dayBookings, $shiftEnd, $shiftStart,
isset($sw->lunch_preferred_time) ? $sw->lunch_preferred_time : null
);
if ($lunch) $data['lunchByDate'][$dateStr] = $lunch;
}
}
$data['pageTitle'] = 'Munkaidő naptár';
$this->load->view('admin/worker_calendar', $data);
}
public function store_calendar_override() {
$this->load->model('Service_model');
$this->load->helper('url');
if (!isset($_SESSION['username'])) {
header("Location:".SITEURL."login");
exit;
}
$worker_id = $this->input->post('worker_id');
$start_date = $this->input->post('start_date');
$end_date = $this->input->post('end_date') ?: $start_date;
$type = $this->input->post('override_type');
$start_time = $this->input->post('start_time');
$end_time = $this->input->post('end_time');
$note = $this->input->post('note');
$current = strtotime($start_date);
$last = strtotime($end_date);
$guard = 0;
while ($current <= $last && $guard < 366) {
$dateStr = date('Y-m-d', $current);
if ($type === 'remove') {
$this->Service_model->deleteWorkerScheduleOverrideByDate($worker_id, $dateStr);
} else {
$data = [];
if ($type === 'day_off') {
$data['is_day_off'] = 1;
$data['absence_type'] = null;
$data['start_time'] = null;
$data['end_time'] = null;
$data['note'] = null;
} elseif ($type === 'custom') {
$data['is_day_off'] = 0;
$data['absence_type'] = 'custom';
$data['start_time'] = $start_time;
$data['end_time'] = $end_time;
$data['note'] = $note;
} else {
// vacation, sick, other
$data['is_day_off'] = 1;
$data['absence_type'] = $type;
$data['start_time'] = null;
$data['end_time'] = null;
$data['note'] = $note;
}
$this->Service_model->upsertWorkerScheduleOverride($worker_id, $dateStr, $data);
}
$current = strtotime('+1 day', $current);
$guard++;
}
$monthParam = date('Y-m', strtotime($start_date));
redirect('workers/worker-calendar/' . $worker_id . '?month=' . $monthParam);
}
public function delete_calendar_override() {
$this->load->model('Service_model');
$this->load->helper('url');
if (!isset($_SESSION['username'])) {
header("Location:".SITEURL."login");
exit;
}
$worker_id = $this->input->post('worker_id');
$date = $this->input->post('date');
$this->Service_model->deleteWorkerScheduleOverrideByDate($worker_id, $date);
$monthParam = date('Y-m', strtotime($date));
redirect('workers/worker-calendar/' . $worker_id . '?month=' . $monthParam);
}
}