Plugin Directory

Changeset 3456986


Ignore:
Timestamp:
02/09/2026 11:13:25 AM (7 weeks ago)
Author:
creavi
Message:

Extended Google Calendar event details & Modernized admin styling and layout

Location:
creavi-booking-service
Files:
45 added
7 edited

Legend:

Unmodified
Added
Removed
  • creavi-booking-service/trunk/assets/css/admin.css

    r3431070 r3456986  
    7474    font-size: 14px;
    7575}
    76 .creavibc-grid-table td {
    77     cursor: pointer;
    78     border: 1px solid #ccc;
    79     height: 36px;
    80 }
    81 
    82 .creavibc-grid-table td.selected {
    83     background-color: #8bc34a !important;
     76.creavibc-grid-table td {   
     77    height: 20px;
    8478}
    8579
     
    130124.creavibc-grid-table td,
    131125.creavibc-grid-table th {
    132     border: 1px solid #ccc;
    133     padding: 8px;
     126    border: 1px solid #dfdfdf;   
    134127    text-align: center;
     128    width: 100px;
    135129}
    136130
     
    298292/* Tab buttons */
    299293.creavibc-tab-nav {
    300   display: inline-flex;
    301   background: #f3f4f6;
     294  display: inline-flex;
    302295  border-radius: 999px;
    303296  padding: 4px;
     
    384377  box-shadow: 0 0 0 3px rgba(96, 165, 250, .25);
    385378}
     379
     380
     381/* =========================================
     382   Creavi Admin Metabox UI — modern branded
     383   
     384   ========================================= */
     385
     386
     387:root{
     388  --creavibc-accent: #78029f;
     389  --creavibc-accent-2: #dd81f9;
     390
     391  --creavibc-text: #111827;
     392  --creavibc-muted: #6b7280;
     393
     394  --creavibc-radius: 16px;
     395
     396  /* glass + borders */
     397  --creavibc-glass: rgba(255,255,255,.72);
     398  --creavibc-glass-2: rgba(255,255,255,.58);
     399  --creavibc-stroke: rgba(17,24,39,.10);
     400  --creavibc-stroke-2: rgba(17,24,39,.06);
     401
     402  /* shadows */
     403  --creavibc-shadow: 0 18px 55px rgba(0,0,0,.10);
     404  --creavibc-shadow-soft: 0 10px 30px rgba(0,0,0,.07);
     405
     406  /* focus ring = subtle */
     407  --creavibc-ring: rgba(120,2,159,.16);
     408  --creavibc-ring-2: rgba(221,129,249,.18);
     409}
     410
     411/* -----------------------------------------------------
     412   TARGET: only your metaboxes via IDs
     413----------------------------------------------------- */
     414#creavibc_output_type.postbox,
     415#creavibc_available_days.postbox,
     416#creavibc_booking_form_fields.postbox,
     417#creavibc_time_slot_grid.postbox,
     418#creavibc_service_appearance.postbox,
     419#creavibc_service_notifications.postbox,
     420#creavibc_google_calendar.postbox,
     421#creavibc_booking_details.postbox{
     422  position: relative;
     423  border: 1px solid transparent !important; /* gradient border uses pseudo */
     424  border-radius: 18px !important;
     425  background: transparent !important;
     426  overflow: hidden;
     427  box-shadow: var(--creavibc-shadow-soft) !important;
     428}
     429
     430/* thin GRADIENT BORDER (1px) */
     431#creavibc_output_type.postbox::before,
     432#creavibc_available_days.postbox::before,
     433#creavibc_booking_form_fields.postbox::before,
     434#creavibc_time_slot_grid.postbox::before,
     435#creavibc_service_appearance.postbox::before,
     436#creavibc_service_notifications.postbox::before,
     437#creavibc_google_calendar.postbox::before,
     438#creavibc_booking_details.postbox::before{
     439  content:"";
     440  position:absolute;
     441  inset:0;
     442  padding: 1px;                 
     443  border-radius: 18px;
     444  background: linear-gradient(135deg,
     445      rgba(120,2,159,.55),
     446      rgba(221,129,249,.45),
     447      rgba(17,24,39,.08)
     448  );
     449  -webkit-mask:
     450    linear-gradient(#000 0 0) content-box,
     451    linear-gradient(#000 0 0);
     452  -webkit-mask-composite: xor;
     453          mask-composite: exclude;
     454  pointer-events:none;
     455}
     456
     457/* glass background layer inside */
     458#creavibc_output_type.postbox::after,
     459#creavibc_available_days.postbox::after,
     460#creavibc_booking_form_fields.postbox::after,
     461#creavibc_time_slot_grid.postbox::after,
     462#creavibc_service_appearance.postbox::after,
     463#creavibc_service_notifications.postbox::after,
     464#creavibc_google_calendar.postbox::after,
     465#creavibc_booking_details.postbox::after{
     466  position:absolute;
     467  inset:1px;                   
     468  border-radius: 17px;
     469  background:
     470    radial-gradient(1200px 220px at 20% 0%,
     471      rgba(221,129,249,.16),
     472      rgba(255,255,255,0) 60%),
     473    radial-gradient(1000px 240px at 90% 20%,
     474      rgba(120,2,159,.10),
     475      rgba(255,255,255,0) 55%),
     476    linear-gradient(180deg,
     477      var(--creavibc-glass) 0%,
     478      var(--creavibc-glass-2) 100%);
     479  backdrop-filter: blur(10px);
     480  -webkit-backdrop-filter: blur(10px);
     481  pointer-events:none;
     482}
     483
     484/* Make sure header/content sit above glass layers */
     485#creavibc_output_type.postbox > *,
     486#creavibc_available_days.postbox > *,
     487#creavibc_booking_form_fields.postbox > *,
     488#creavibc_time_slot_grid.postbox > *,
     489#creavibc_service_appearance.postbox > *,
     490#creavibc_service_notifications.postbox > *,
     491#creavibc_google_calendar.postbox > *,
     492#creavibc_booking_details.postbox > *{
     493
     494  position: relative;
     495  z-index: 1;
     496}
     497
     498/* -----------------------------------------------------
     499   Metabox HEADER: clean, premium, minimal
     500----------------------------------------------------- */
     501#creavibc_output_type .postbox-header,
     502#creavibc_available_days .postbox-header,
     503#creavibc_booking_form_fields .postbox-header,
     504#creavibc_time_slot_grid .postbox-header,
     505#creavibc_service_appearance .postbox-header,
     506#creavibc_service_notifications .postbox-header,
     507#creavibc_google_calendar .postbox-header,
     508#creavibc_booking_details .postbox-header{
     509  border-bottom: 1px solid rgba(17,24,39,.08) !important;
     510  background: transparent !important;
     511  padding: 14px 5px 12px !important;
     512}
     513
     514/* subtle top “accent hairline” */
     515#creavibc_output_type .postbox-header::before,
     516#creavibc_available_days .postbox-header::before,
     517#creavibc_booking_form_fields .postbox-header::before,
     518#creavibc_time_slot_grid .postbox-header::before,
     519#creavibc_service_appearance .postbox-header::before,
     520#creavibc_service_notifications .postbox-header::before,
     521#creavibc_google_calendar .postbox-header::before,
     522#creavibc_booking_details .postbox-header::before{
     523  content:"";
     524  position:absolute;
     525  left: 16px;
     526  top: 10px;
     527  width: 70px;
     528  height: 3px;
     529  border-radius: 999px;
     530  background: linear-gradient(90deg, var(--creavibc-accent), var(--creavibc-accent-2));
     531  opacity: .85;
     532}
     533
     534/* title typography */
     535#creavibc_output_type .postbox-header h2,
     536#creavibc_available_days .postbox-header h2,
     537#creavibc_booking_form_fields .postbox-header h2,
     538#creavibc_time_slot_grid .postbox-header h2,
     539#creavibc_service_appearance .postbox-header h2,
     540#creavibc_service_notifications .postbox-header h2,
     541#creavibc_google_calendar .postbox-header h2,
     542#creavibc_booking_details .postbox-header h2{
     543  font-weight: 800 !important;
     544  letter-spacing: -0.02em;
     545  color: var(--creavibc-text) !important;
     546}
     547
     548/* -----------------------------------------------------
     549   Add padding INSIDE metabox content
     550----------------------------------------------------- */
     551#creavibc_output_type .inside,
     552#creavibc_available_days .inside,
     553#creavibc_booking_form_fields .inside,
     554#creavibc_time_slot_grid .inside,
     555#creavibc_service_appearance .inside,
     556#creavibc_service_notifications .inside,
     557#creavibc_google_calendar .inside,
     558#creavibc_booking_details .inside{
     559  padding: 16px !important;     
     560}
     561
     562/* Make separators calmer */
     563#creavibc_output_type hr,
     564#creavibc_available_days hr,
     565#creavibc_booking_form_fields hr,
     566#creavibc_time_slot_grid hr,
     567#creavibc_service_appearance hr,
     568#creavibc_service_notifications hr,
     569#creavibc_google_calendar hr,
     570#creavibc_booking_details hr,
     571.creavibc-panel-card hr,
     572details hr{
     573
     574  border: 0;
     575  border-top: 1px solid rgba(17,24,39,.08);
     576  margin: 16px 0;
     577}
     578
     579/* -----------------------------------------------------
     580   Controls: “Apple-ish” fields (soft glass, thin stroke)
     581----------------------------------------------------- */
     582.creavibc-input,
     583.creavibc-select,
     584.creavibc-style-field input[type="color"],
     585.creavibc-style-field input[type="number"],
     586.creavibc-style-field input[type="text"],
     587.creavibc-custom-row input[type="text"],
     588.creavibc-custom-row select,
     589#creavibc_email_admin_template,
     590#creavibc_email_user_template,
     591#creavibc_email_reminder_template,
     592#creavibc_event_desc_tpl,
     593#creavibc_event_title_tpl,
     594#creavibc_meeting_location,
     595#creavibc_button_text,
     596#creavibc_thankyou_text,
     597#creavibc_gcal_admin_email,
     598#creavibc_email_reminder_subject,
     599#creavibc_reminder_offset_minutes{
     600  border-radius: 14px !important;
     601  border: 1px solid rgba(17,24,39,.10) !important;
     602  background: rgba(255,255,255,.78) !important;
     603  box-shadow:
     604    0 1px 0 rgba(255,255,255,.35) inset,
     605    0 10px 24px rgba(0,0,0,.05);
     606  transition: border-color .15s ease, box-shadow .15s ease, transform .08s ease;
     607}
     608
     609/* Focus: soft “SaaS ring”, not a heavy border */
     610.creavibc-input:focus,
     611.creavibc-select:focus,
     612.creavibc-style-field input:focus,
     613.creavibc-custom-row input[type="text"]:focus,
     614.creavibc-custom-row select:focus,
     615#creavibc_email_admin_template:focus,
     616#creavibc_email_user_template:focus,
     617#creavibc_email_reminder_template:focus,
     618#creavibc_event_desc_tpl:focus,
     619#creavibc_event_title_tpl:focus,
     620#creavibc_meeting_location:focus,
     621#creavibc_button_text:focus,
     622#creavibc_thankyou_text:focus,
     623#creavibc_gcal_admin_email:focus,
     624#creavibc_email_reminder_subject:focus,
     625#creavibc_reminder_offset_minutes:focus{
     626  outline: none !important;
     627  border-color: rgba(120,2,159,.38) !important;
     628  box-shadow:
     629    0 0 0 3px rgba(255,255,255,.9),
     630    0 0 0 7px var(--creavibc-ring-2),
     631    0 12px 28px rgba(0,0,0,.08) !important;
     632}
     633
     634/* Help text = slightly smaller / calmer */
     635.creavibc-help,
     636.creavibc-tabs-subtitle,
     637.description{
     638  color: var(--creavibc-muted) !important;
     639  font-size: 12.5px;
     640}
     641
     642/* -----------------------------------------------------
     643   Cards inside: panels, rows (thin gradient outline)
     644----------------------------------------------------- */
     645.creavibc-panel-card{
     646  position: relative;
     647  border: 1px solid rgba(17,24,39,.08) !important;
     648  border-radius: 16px !important;
     649  background: rgba(255,255,255,.70) !important;
     650  backdrop-filter: blur(8px);
     651  -webkit-backdrop-filter: blur(8px);
     652  box-shadow: 0 12px 30px rgba(0,0,0,.06);
     653}
     654
     655/* hover = subtle lift */
     656.creavibc-custom-row:hover{
     657  transform: translateY(-1px); 
     658}
     659
     660/* Thin gradient stroke on “active” elements (tabs + toggles) */
     661.creavibc-tab.is-active,
     662.creavibc-toggle-option.active{
     663  border: 1px solid transparent !important;
     664  background:
     665    linear-gradient(rgba(255,255,255,.78), rgba(255,255,255,.78)) padding-box,
     666    linear-gradient(135deg, rgba(120,2,159,.75), rgba(221,129,249,.65), rgba(17,24,39,.08)) border-box !important;
     667  box-shadow: 0 16px 40px rgba(0,0,0,.10);
     668}
     669
     670/* Toggle cards: more Apple-ish */
     671.creavibc-toggle-option{
     672  border: 1px solid rgba(17,24,39,.10) !important;
     673  border-radius: 16px !important;
     674  background: rgba(255,255,255,.70) !important;
     675  backdrop-filter: blur(8px);
     676  -webkit-backdrop-filter: blur(8px);
     677  box-shadow: 0 12px 30px rgba(0,0,0,.06);
     678}
     679.creavibc-toggle-option:hover{
     680  transform: translateY(-1px);
     681  box-shadow: 0 18px 45px rgba(0,0,0,.10);
     682}
     683.creavibc-toggle-option svg{
     684  fill: var(--creavibc-accent) !important;
     685}
     686
     687/* -----------------------------------------------------
     688   Slot Grid: clean + premium selection
     689----------------------------------------------------- */
     690#creavibc-slot-grid-container{
     691  border: 1px solid rgba(17,24,39,.08);
     692  border-radius: 16px;
     693  padding: 10px;
     694  background: rgba(255,255,255,.70);
     695  backdrop-filter: blur(8px);
     696  -webkit-backdrop-filter: blur(8px);
     697}
     698
     699.creavibc-grid-table{ 
     700  border-spacing: 0 !important; 
     701  border-radius: 14px !important;
     702  overflow: hidden;
     703  background: rgba(255,255,255,.72);
     704}
     705
     706.creavibc-grid-table th{
     707  background: rgba(255,255,255,.78) !important;
     708  border-bottom: 1px solid rgba(17,24,39,.08) !important;
     709  backdrop-filter: blur(10px);
     710  -webkit-backdrop-filter: blur(10px);
     711}
     712
     713.creavibc-grid-table td:first-child,
     714.creavibc-grid-table th:first-child{
     715  background: rgba(255,255,255,.78) !important;
     716}
     717
     718.creavibc-grid-table td.creavibc-cell:hover{
     719  background: rgba(120,2,159,.06) !important;
     720}
     721
     722/* Selection: gradient fill but still clean */
     723.creavibc-grid-table td.selected,
     724.creavibc-cell.selected{
     725  background: linear-gradient(90deg, rgba(120,2,159,.92), rgba(221,129,249,.92)) !important;
     726  color: #fff !important;
     727}
     728
     729/* Hour divider calmer */
     730.creavibc-grid-table tr.creavibc-hour-divider td{
     731  border-top: 1px solid rgba(17,24,39,.18) !important;
     732}
     733
     734/* -----------------------------------------------------
     735   Buttons: SaaS polished hover (no heavy borders)
     736----------------------------------------------------- */
     737#creavibc-add-field,
     738.creavibc-remove-field,
     739#creavibc-admin-tz-edit{
     740  border-radius: 14px !important;
     741  box-shadow: 0 10px 24px rgba(0,0,0,.08);
     742  transition: transform .08s ease, box-shadow .15s ease;
     743}
     744
     745#creavibc-add-field:hover,
     746.creavibc-remove-field:hover,
     747#creavibc-admin-tz-edit:hover{
     748  transform: translateY(-1px);
     749  box-shadow: 0 18px 40px rgba(0,0,0,.14);
     750}
     751
     752/* Focus visible polish */
     753:focus-visible{
     754  outline: 3px solid rgba(221,129,249,.35);
     755  outline-offset: 2px;
     756}
     757
     758
     759
     760/* -----------------------------------------------------
     761   LABEL → INPUT spacing (clean vertical rhythm)
     762----------------------------------------------------- */
     763.creavibc-field label,
     764.creavibc-style-field label,
     765.creavibc-default-field-row label,
     766.creavibc-custom-row label,
     767label[for]{
     768  display: inline-block;
     769  margin-bottom: 6px;  /* space between label and control */
     770  font-weight: 600;
     771}
     772
     773/* In rows where label + checkbox are inline, keep compact */
     774.creavibc-checkbox-label{
     775  margin-bottom: 0 !important;
     776}
     777
     778/* -----------------------------------------------------
     779   INPUTS & SELECTS — more inner space (NOT color)
     780----------------------------------------------------- */
     781.creavibc-input,
     782.creavibc-select,
     783.creavibc-style-field input[type="number"],
     784.creavibc-style-field input[type="text"],
     785.creavibc-custom-row input[type="text"],
     786.creavibc-custom-row select,
     787input[type="text"],
     788input[type="email"],
     789input[type="number"],
     790select{
     791  padding: 12px 14px !important; /* <- more breathing room */
     792  line-height: 1.4 !important;
     793}
     794
     795/* -----------------------------------------------------
     796   TEXTAREAS — even more comfort
     797----------------------------------------------------- */
     798textarea,
     799#creavibc_email_admin_template,
     800#creavibc_email_user_template,
     801#creavibc_email_reminder_template,
     802#creavibc_event_desc_tpl{
     803  padding: 14px 16px !important;
     804  line-height: 1.55;
     805}
     806
     807/* -----------------------------------------------------
     808   COLOR PICKER — NO inner spacing at all
     809----------------------------------------------------- */
     810input[type="color"],
     811.creavibc-style-field input[type="color"]{
     812  padding: 0 !important;
     813  height: 40px;        /* consistent square */
     814  min-height: 40px;
     815  border-radius: 10px !important;
     816  overflow: hidden;
     817}
     818
     819/* Remove browser-added inner gaps (Safari / Chrome) */
     820input[type="color"]::-webkit-color-swatch-wrapper{
     821  padding: 0;
     822}
     823input[type="color"]::-webkit-color-swatch{
     824  border: none;
     825  border-radius: 10px;
     826}
     827
     828/* -----------------------------------------------------
     829   Small polish: inputs inside rows stay aligned
     830----------------------------------------------------- */
     831.creavibc-custom-row input[type="text"],
     832.creavibc-custom-row select{
     833  margin: 0; /* avoid double spacing */
     834}
     835
     836/* -----------------------------------------------------
     837   Optional: slightly taller controls for Apple feel
     838----------------------------------------------------- */
     839.creavibc-input,
     840.creavibc-select,
     841input[type="text"],
     842input[type="email"],
     843input[type="number"],
     844select{
     845  min-height: 42px;
     846}
     847
     848textarea{
     849  min-height: 110px;
     850}
     851
     852/* =====================================================
     853   Creavi HR — clean glass separator (Apple / SaaS)
     854   ===================================================== */
     855
     856/* Default HR inside Creavi metaboxes */
     857#creavibc_output_type hr,
     858#creavibc_available_days hr,
     859#creavibc_booking_form_fields hr,
     860#creavibc_time_slot_grid hr,
     861#creavibc_service_options hr,
     862#creavibc_google_calendar hr,
     863#creavibc_booking_details hr,
     864.creavibc-panel-card hr,
     865details hr{
     866  border: none;
     867  height: 1px;
     868  margin: 20px 0;
     869  background:
     870    linear-gradient(
     871      90deg,
     872      rgba(120,2,159,0),
     873      rgba(120,2,159,.35),
     874      rgba(221,129,249,.45),
     875      rgba(120,2,159,.35),
     876      rgba(120,2,159,0)
     877    );
     878  opacity: .9;
     879}
     880
     881/* Softer variant when HR is inside glass cards */
     882.creavibc-panel-card hr{
     883  margin: 18px 0;
     884  background:
     885    linear-gradient(
     886      90deg,
     887      rgba(17,24,39,0),
     888      rgba(17,24,39,.18),
     889      rgba(17,24,39,0)
     890    );
     891}
     892
     893/* Extra spacing when HR separates big sections */
     894#creavibc_service_options hr,
     895#creavibc_google_calendar hr{
     896  margin: 24px 0;
     897}
     898
     899/* Optional: very subtle glow on high-DPI screens */
     900@media (min-resolution: 2dppx) {
     901  #creavibc_output_type hr,
     902  #creavibc_available_days hr,
     903  #creavibc_booking_form_fields hr,
     904  #creavibc_time_slot_grid hr,
     905  #creavibc_service_options hr,
     906  #creavibc_google_calendar hr,
     907  #creavibc_booking_details hr{
     908    box-shadow: 0 0 6px rgba(221,129,249,.25);
     909  }
     910}
  • creavi-booking-service/trunk/assets/css/creavibc-deactivation-feedback.css

    r3445660 r3456986  
    55
    66:root{
    7   --creavibc-accent: #7a00a0;      /* <-- change to your brand color */
     7  --creavibc-accent: #7a00a0;      /* brand color */
    88  --creavibc-accent-2: #a56db4;    /* optional second tone */
    99  --creavibc-text: #111827;
  • creavi-booking-service/trunk/creavi-booking-service.php

    r3454590 r3456986  
    55 * Text Domain: creavi-booking-service
    66 * Domain Path: /languages
    7  * Version: 1.1.7
     7 * Version: 1.1.8
    88 * Author: Creavi
    99 * License: GPL2
     
    1616define('CREAVIBC_PLUGIN_URL', plugin_dir_url(__FILE__));
    1717define('CREAVIBC_PLUGIN_PATH', plugin_dir_path(__FILE__));
    18 define('CREAVIBC_VERSION', '1.1.7');
     18define('CREAVIBC_VERSION', '1.1.8');
    1919
    2020
  • creavi-booking-service/trunk/includes/ajax-handlers.php

    r3441012 r3456986  
    1212    }
    1313}
     14
     15if ( ! function_exists('creavibc_apply_tokens') ) {
     16    function creavibc_apply_tokens( $tpl, array $vars ) {
     17        $tpl = (string) $tpl;
     18        foreach ( $vars as $k => $v ) {
     19            $tpl = str_replace('{' . $k . '}', (string) $v, $tpl);
     20        }
     21        return $tpl;
     22    }
     23}
     24
     25if ( ! function_exists('creavibc_ics_escape') ) {
     26    function creavibc_ics_escape( $v ) {
     27        $v = (string) $v;
     28        // RFC5545-ish escaping for TEXT
     29        $v = str_replace(["\\", ";", ",", "\r\n", "\n", "\r"], ["\\\\", "\;", "\,", "\\n", "\\n", ""], $v);
     30        return $v;
     31    }
     32}
     33
    1434
    1535// =====================================================
     
    114134}
    115135
    116 function creavibc_send_booking_email($user_email, $name, $date, $start_time, $duration, $service_title, $custom, $service_id) {
    117     // Check per-service override first
    118     $admin_email = get_post_meta($service_id, '_creavibc_gcal_admin_email', true);
    119     if ( ! $admin_email ) {
    120         $admin_email = get_option('admin_email'); // fallback to WP general email
    121     }
    122    
    123     $admin_tz = get_post_meta($service_id, '_creavibc_admin_timezone', true) ?: 'UTC';
    124     $timezone_mode = get_post_meta($service_id, '_creavibc_timezone_mode', true); // 'locked' or 'localized'
    125     // -> $user_tz = !empty($_POST['timezone']) ? sanitize_text_field(wp_unslash($_POST['timezone'])) : 'UTC';
    126     // =====================================================
    127     // TZ: get user timezone (insert/replace here)
    128     // =====================================================
    129     $user_tz = !empty($_POST['timezone']) ? sanitize_text_field(wp_unslash($_POST['timezone'])) : 'UTC';
    130 
    131     // Normalize + validate user_tz to avoid DateTimeZone fatal errors
    132     if ($user_tz === 'Europe/Kiev') {
    133         $user_tz = 'Europe/Kyiv';
    134     }
    135     if (!in_array($user_tz, timezone_identifiers_list(), true)) {
    136         creavibc_log('invalid user_tz; falling back to UTC', $user_tz);
    137         $user_tz = 'UTC';
    138     }
    139 
    140     // normalize timezone
    141     if ($admin_tz === 'Europe/Kiev') {
    142         $admin_tz = 'Europe/Kyiv';
    143     }
    144     if (!in_array($admin_tz, timezone_identifiers_list(), true)) {
    145         creavibc_log('invalid admin_tz; falling back to site tz', $admin_tz);
    146         $admin_tz = wp_timezone_string();
    147     }
    148    
    149 
    150     $start_dt_admin = new DateTime("$date $start_time", new DateTimeZone($admin_tz));
    151     $end_dt_admin = clone $start_dt_admin;
    152     $end_dt_admin->modify("+$duration minutes");
    153 
    154     $start_dt_user = clone $start_dt_admin;
    155     $end_dt_user = clone $end_dt_admin;
    156 
    157     if ($timezone_mode === 'localized' && $user_tz !== $admin_tz) {
    158         $start_dt_user->setTimezone(new DateTimeZone($user_tz));
    159         $end_dt_user->setTimezone(new DateTimeZone($user_tz));
    160     }   
    161 
    162     $start_utc = clone $start_dt_admin;
    163     $end_utc   = clone $end_dt_admin;
    164 
    165     $start_utc->setTimezone(new DateTimeZone('UTC'));
    166     $end_utc->setTimezone(new DateTimeZone('UTC'));
    167 
    168     $gcal_url = 'https://www.google.com/calendar/render?action=TEMPLATE';
    169     $gcal_url .= '&text=' . urlencode($service_title);
    170     $gcal_url .= '&dates=' . $start_utc->format('Ymd\THis\Z') . '/' . $end_utc->format('Ymd\THis\Z');
    171     $gcal_url .= '&details=' . urlencode("Appointment with $name");
    172     $gcal_url .= '&location=' . urlencode(home_url());
    173     $gcal_url .= '&sf=true&output=xml';
    174 
    175     $ical = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nBEGIN:VEVENT\r\n";
    176     $ical .= "SUMMARY:$service_title\r\n";
    177     $ical .= "DTSTART:" . $start_utc->format('Ymd\THis\Z') . "\r\n";
    178     $ical .= "DTEND:" . $end_utc->format('Ymd\THis\Z') . "\r\n";
    179     $ical .= "DESCRIPTION:Appointment with $name\r\n";
    180     $ical .= "LOCATION:" . home_url() . "\r\n";
    181     $ical .= "END:VEVENT\r\nEND:VCALENDAR";
    182 
    183     $tmp_file = tempnam(sys_get_temp_dir(), 'booking') . '.ics';
    184     file_put_contents($tmp_file, $ical);
    185 
    186     $admin_tpl = get_post_meta($service_id, '_creavibc_email_admin_template', true);
    187     $user_tpl  = get_post_meta($service_id, '_creavibc_email_user_template', true);
    188 
    189     $custom_lines = '';
    190     foreach ($custom as $label => $value) {
    191         $custom_lines .= "$label: $value\n";
    192     }
    193 
    194     $display_date = $start_dt_user->format('Y-m-d');   
    195 
    196     $display_time_admin = $start_dt_admin->format('H:i') . ' – ' . $end_dt_admin->format('H:i') . " ($admin_tz)";
    197     $display_time_user  = $start_dt_user->format('H:i')  . ' – ' . $end_dt_user->format('H:i')  . " ($user_tz)";
    198     $display_date       = $start_dt_user->format('Y-m-d'); // both see same date
    199    
    200 
    201     if ($timezone_mode === 'locked') {
    202         // Show admin time for both user and admin
    203         $replacements_user = [
    204             '{service}'         => $service_title,
    205             '{date}'            => $start_dt_admin->format('Y-m-d'),
    206             '{time}'            => $display_time_admin,
    207             '{name}'            => $name,
    208             '{email}'           => $user_email,
    209             '{custom}'          => $custom_lines,
    210             '{admin_link}'      => admin_url("post.php?post=$service_id&action=edit"),
    211             '{google_calendar}' => $gcal_url,
    212         ];
    213         $replacements_admin = $replacements_user;
    214     } else {
    215         // Localized: show user time in user email, admin time in admin email
    216         $replacements_user = [
    217             '{service}'         => $service_title,
    218             '{date}'            => $start_dt_user->format('Y-m-d'),
    219             '{time}'            => $display_time_user,
    220             '{name}'            => $name,
    221             '{email}'           => $user_email,
    222             '{custom}'          => $custom_lines,
    223             '{admin_link}'      => admin_url("post.php?post=$service_id&action=edit"),
    224             '{google_calendar}' => $gcal_url,
    225         ];
    226         $replacements_admin = $replacements_user;
    227         $replacements_admin['{time}'] = $display_time_admin;
    228     }
    229 
    230    
    231     $display_time_fallback = ($timezone_mode === 'locked') ? $display_time_admin : $display_time_user;
    232 
    233     $fallback = "Booking for $service_title on $display_date at $display_time_fallback.\n"
    234             . "Name: $name\nEmail: $user_email\n\n$custom_lines\nGoogle Calendar: $gcal_url";
    235    
    236    // $fallback = "Booking for $service_title on $display_date at $display_time.\nName: $name\nEmail: $user_email\n\n$custom_lines\nGoogle Calendar: $gcal_url";
    237 
    238     $final_user_tpl  = $user_tpl  ?: $fallback;
    239     $final_admin_tpl = $admin_tpl ?: $fallback;
    240 
    241     foreach ($replacements_user as $key => $value) {
    242         $final_user_tpl  = str_replace($key, $value, $final_user_tpl);
    243         $final_admin_tpl = str_replace($key, $value, $final_admin_tpl);
    244     }
    245 
    246     // translators: %1$s: Service title, %2$s: Date, %3$s: Start time
    247     $admin_subject = sprintf(__('New Booking: %1$s on %2$s at %3$s', 'creavi-booking-service'), $service_title, $display_date, $start_time);
    248 
    249     // translators: %s: Service title
    250     $user_subject  = sprintf(__('Your Booking: %s', 'creavi-booking-service'), $service_title);
    251    
    252 
    253     // =====================================================
    254     // MAIL: headers + logging + safe user send (replace here)
    255     // =====================================================
    256     $headers = [];
    257     $from_email = get_option('admin_email');
    258     $from_name  = wp_specialchars_decode(get_bloginfo('name'), ENT_QUOTES);
    259 
    260     $headers[] = 'Content-Type: text/plain; charset=UTF-8';
    261     $headers[] = 'From: ' . $from_name . ' <' . $from_email . '>';
    262 
    263     creavibc_log('sending emails', [
    264         'user_email'  => $user_email,
    265         'user_valid'  => is_email($user_email),
    266         'admin_email' => $admin_email,
    267         'admin_valid' => is_email($admin_email),
    268         'tmp_file'    => $tmp_file,
    269         'tmp_exists'  => file_exists($tmp_file),
    270     ]);
    271 
    272     $sent_admin = wp_mail($admin_email, $admin_subject, $final_admin_tpl, $headers, [$tmp_file]);
    273 
    274     $sent_user = false;
    275     if ( is_email($user_email) ) {
    276         $sent_user = wp_mail($user_email, $user_subject, $final_user_tpl, $headers, [$tmp_file]);
    277     } else {
    278         creavibc_log('skip user mail: invalid email', $user_email);
    279     }
    280 
    281     creavibc_log('mail results', [
    282         'sent_admin' => $sent_admin,
    283         'sent_user'  => $sent_user,
    284     ]);
    285 
    286    
    287 
    288     // Auto-push to Google Calendar if enabled
    289     if ( (bool) get_post_meta($service_id, '_creavibc_gcal_enable', true) ) {
    290         $attendees = [];
    291         if ( $user_email ) {
    292             $attendees[] = $user_email;
    293         }
    294 
    295         $admin_invite = get_post_meta($service_id, '_creavibc_gcal_admin_email', true);
    296         if ( $admin_invite ) {
    297             $attendees[] = sanitize_email($admin_invite);
    298         }
    299 
    300         creavibc_gcal_maybe_insert_event([
    301             'service_id' => $service_id,
    302             'date'       => $start_dt_admin->format('Y-m-d'), // always admin TZ
    303             'time'       => $start_dt_admin->format('H:i'),   // HH:mm only
    304             'name'       => $name,
    305             'email'      => $user_email, // keep for logging/backward compat
    306             'attendees'  => $attendees,  // explicit list
    307             'duration'   => (int) $duration,
    308         ]);
    309     }
    310    
    311     wp_delete_file($tmp_file);
     136function creavibc_send_booking_email( $user_email, $name, $date, $start_time, $duration, $service_title, $custom, $service_id ) {
     137
     138    // ---------------------------
     139    // Admin email (service override -> site admin)
     140    // ---------------------------
     141    $admin_email = get_post_meta( $service_id, '_creavibc_gcal_admin_email', true );
     142    $admin_email = $admin_email ? sanitize_email( $admin_email ) : get_option( 'admin_email' );
     143
     144    // ---------------------------
     145    // Timezone mode + timezones
     146    // ---------------------------
     147    $timezone_mode = get_post_meta( $service_id, '_creavibc_timezone_mode', true ); // 'locked' or 'localized'
     148    $timezone_mode = $timezone_mode ? (string) $timezone_mode : 'locked';
     149
     150    $user_tz  = ! empty( $_POST['timezone'] ) ? sanitize_text_field( wp_unslash( $_POST['timezone'] ) ) : 'UTC';
     151    $admin_tz = get_post_meta( $service_id, '_creavibc_admin_timezone', true );
     152    $admin_tz = $admin_tz ? (string) $admin_tz : 'UTC';
     153
     154    // Normalize legacy alias
     155    if ( 'Europe/Kiev' === $user_tz ) {
     156        $user_tz = 'Europe/Kyiv';
     157    }
     158    if ( 'Europe/Kiev' === $admin_tz ) {
     159        $admin_tz = 'Europe/Kyiv';
     160    }
     161
     162    // Validate timezones
     163    if ( ! in_array( $user_tz, timezone_identifiers_list(), true ) ) {
     164        if ( function_exists( 'creavibc_log' ) ) {
     165            creavibc_log( 'invalid user_tz; falling back to UTC', $user_tz );
     166        }
     167        $user_tz = 'UTC';
     168    }
     169    if ( ! in_array( $admin_tz, timezone_identifiers_list(), true ) ) {
     170        if ( function_exists( 'creavibc_log' ) ) {
     171            creavibc_log( 'invalid admin_tz; falling back to site tz', $admin_tz );
     172        }
     173        $admin_tz = wp_timezone_string();
     174        if ( ! $admin_tz || ! in_array( $admin_tz, timezone_identifiers_list(), true ) ) {
     175            $admin_tz = 'UTC';
     176        }
     177    }
     178
     179    // Duration sanity
     180    $duration = (int) $duration;
     181    if ( $duration <= 0 ) {
     182        $duration = 30;
     183    }
     184
     185    // Build start/end (admin TZ canonical)
     186    try {
     187        $start_dt_admin = new DateTimeImmutable( "$date $start_time", new DateTimeZone( $admin_tz ) );
     188        $end_dt_admin   = $start_dt_admin->modify( '+' . $duration . ' minutes' );
     189    } catch ( Exception $e ) {
     190        if ( function_exists( 'creavibc_log' ) ) {
     191            creavibc_log( 'email: invalid date/time', $e->getMessage() );
     192        }
     193        return;
     194    }
     195
     196    $start_dt_user = $start_dt_admin;
     197    $end_dt_user   = $end_dt_admin;
     198
     199    if ( 'localized' === $timezone_mode && $user_tz !== $admin_tz ) {
     200        $start_dt_user = $start_dt_admin->setTimezone( new DateTimeZone( $user_tz ) );
     201        $end_dt_user   = $end_dt_admin->setTimezone( new DateTimeZone( $user_tz ) );
     202    }
     203
     204    // UTC for ICS + Google Calendar link
     205    $start_utc = $start_dt_admin->setTimezone( new DateTimeZone( 'UTC' ) );
     206    $end_utc   = $end_dt_admin->setTimezone( new DateTimeZone( 'UTC' ) );
     207
     208    // Display strings
     209    $display_date       = $start_dt_user->format( 'Y-m-d' );
     210    $display_time_admin = $start_dt_admin->format( 'H:i' ) . ' – ' . $end_dt_admin->format( 'H:i' ) . " ($admin_tz)";
     211    $display_time_user  = $start_dt_user->format( 'H:i' ) . ' – ' . $end_dt_user->format( 'H:i' ) . " ($user_tz)";
     212    $display_time_for_tokens = ( 'locked' === $timezone_mode ) ? $display_time_admin : $display_time_user;
     213
     214    // Custom fields as lines
     215    $custom_lines = '';
     216    if ( is_array( $custom ) ) {
     217        foreach ( $custom as $label => $value ) {
     218            $custom_lines .= (string) $label . ': ' . (string) $value . "\n";
     219        }
     220    }
     221
     222    // Event templates
     223    $title_tpl = (string) get_post_meta( $service_id, '_creavibc_event_title_tpl', true );
     224    if ( '' === trim( $title_tpl ) ) {
     225        $title_tpl = __( 'Booking — {service}', 'creavi-booking-service' );
     226    }
     227
     228    $location = (string) get_post_meta( $service_id, '_creavibc_meeting_location', true );
     229    $location = trim( $location ) !== '' ? trim( $location ) : home_url();
     230
     231    $desc_tpl = (string) get_post_meta( $service_id, '_creavibc_event_desc_tpl', true );
     232    if ( '' === trim( $desc_tpl ) ) {
     233        $desc_tpl = __( "Client: {name}\nEmail: {email}\nService: {service}\nDate: {date}\nTime: {time}\n\n{custom}", 'creavi-booking-service' );
     234    }
     235
     236    $token_vars = [
     237        'name'    => (string) $name,
     238        'email'   => (string) $user_email,
     239        'service' => (string) $service_title,
     240        'date'    => (string) $display_date,
     241        'time'    => (string) $display_time_for_tokens,
     242        'custom'  => (string) $custom_lines,
     243    ];
     244
     245    $event_title = function_exists( 'creavibc_apply_tokens' ) ? creavibc_apply_tokens( $title_tpl, $token_vars ) : $service_title;
     246    $event_desc  = function_exists( 'creavibc_apply_tokens' ) ? creavibc_apply_tokens( $desc_tpl,  $token_vars ) : sprintf( __( 'Appointment with %s', 'creavi-booking-service' ), (string) $name );
     247
     248    // Google Calendar URL (UTC)
     249    $gcal_url  = 'https://www.google.com/calendar/render?action=TEMPLATE';
     250    $gcal_url .= '&text=' . rawurlencode( $event_title );
     251    $gcal_url .= '&dates=' . $start_utc->format( 'Ymd\THis\Z' ) . '/' . $end_utc->format( 'Ymd\THis\Z' );
     252    $gcal_url .= '&details=' . rawurlencode( $event_desc );
     253    $gcal_url .= '&location=' . rawurlencode( $location );
     254    $gcal_url .= '&sf=true&output=xml';
     255
     256    // ICS attachment (SAFE)
     257    $attachments = [];
     258    $tmp_file    = tempnam( sys_get_temp_dir(), 'booking' );
     259
     260    if ( $tmp_file ) {
     261        $tmp_file .= '.ics';
     262
     263        $ical  = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nBEGIN:VEVENT\r\n";
     264        $ical .= 'SUMMARY:' . creavibc_ics_escape( $event_title ) . "\r\n";
     265        $ical .= 'DTSTART:' . $start_utc->format( 'Ymd\THis\Z' ) . "\r\n";
     266        $ical .= 'DTEND:' . $end_utc->format( 'Ymd\THis\Z' ) . "\r\n";
     267        $ical .= 'LOCATION:' . creavibc_ics_escape( $location ) . "\r\n";
     268        $ical .= 'DESCRIPTION:' . creavibc_ics_escape( $event_desc ) . "\r\n";
     269        $ical .= "END:VEVENT\r\nEND:VCALENDAR\r\n";
     270
     271        $written = file_put_contents( $tmp_file, $ical );
     272        if ( false !== $written && file_exists( $tmp_file ) ) {
     273            $attachments[] = $tmp_file;
     274        } else {
     275            $tmp_file = '';
     276        }
     277    }
     278
     279    // Email templates
     280    $admin_tpl = (string) get_post_meta( $service_id, '_creavibc_email_admin_template', true );
     281    $user_tpl  = (string) get_post_meta( $service_id, '_creavibc_email_user_template', true );
     282
     283    if ( 'locked' === $timezone_mode ) {
     284        $replacements_user = [
     285            '{service}'         => $service_title,
     286            '{date}'            => $start_dt_admin->format( 'Y-m-d' ),
     287            '{time}'            => $display_time_admin,
     288            '{name}'            => $name,
     289            '{email}'           => $user_email,
     290            '{custom}'          => $custom_lines,
     291            '{admin_link}'      => admin_url( "post.php?post=$service_id&action=edit" ),
     292            '{google_calendar}' => $gcal_url,
     293        ];
     294        $replacements_admin = $replacements_user;
     295    } else {
     296        $replacements_user = [
     297            '{service}'         => $service_title,
     298            '{date}'            => $start_dt_user->format( 'Y-m-d' ),
     299            '{time}'            => $display_time_user,
     300            '{name}'            => $name,
     301            '{email}'           => $user_email,
     302            '{custom}'          => $custom_lines,
     303            '{admin_link}'      => admin_url( "post.php?post=$service_id&action=edit" ),
     304            '{google_calendar}' => $gcal_url,
     305        ];
     306        $replacements_admin = $replacements_user;
     307        $replacements_admin['{time}'] = $display_time_admin;
     308        $replacements_admin['{date}'] = $start_dt_admin->format( 'Y-m-d' );
     309    }
     310
     311    $display_time_fallback = ( 'locked' === $timezone_mode ) ? $display_time_admin : $display_time_user;
     312
     313    $fallback = "Booking for $service_title on $display_date at $display_time_fallback.\n"
     314        . "Name: $name\nEmail: $user_email\n\n$custom_lines\nGoogle Calendar: $gcal_url";
     315
     316    $final_user_tpl  = trim( $user_tpl )  !== '' ? $user_tpl  : $fallback;
     317    $final_admin_tpl = trim( $admin_tpl ) !== '' ? $admin_tpl : $fallback;
     318
     319    foreach ( $replacements_user as $key => $value ) {
     320        $final_user_tpl = str_replace( $key, (string) $value, $final_user_tpl );
     321    }
     322    foreach ( $replacements_admin as $key => $value ) {
     323        $final_admin_tpl = str_replace( $key, (string) $value, $final_admin_tpl );
     324    }
     325
     326    // Subjects (MATCH OLD BEHAVIOR)
     327    // translators: %1$s: Service title, %2$s: Date, %3$s: Start time
     328    $admin_subject = sprintf(
     329        __( 'New Booking: %1$s on %2$s at %3$s', 'creavi-booking-service' ),
     330        $service_title,
     331        $display_date,
     332        $start_time
     333    );
     334
     335    // translators: %s: Service title
     336    $user_subject = sprintf( __( 'Your Booking: %s', 'creavi-booking-service' ), $service_title );
     337
     338    // Headers + logging + send
     339    $headers    = [];
     340    $from_email = get_option( 'admin_email' );
     341    $from_name  = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
     342
     343    $headers[] = 'Content-Type: text/plain; charset=UTF-8';
     344    $headers[] = 'From: ' . $from_name . ' <' . $from_email . '>';
     345
     346    if ( function_exists( 'creavibc_log' ) ) {
     347        creavibc_log( 'sending emails', [
     348            'user_email'    => $user_email,
     349            'user_valid'    => is_email( $user_email ),
     350            'admin_email'   => $admin_email,
     351            'admin_valid'   => is_email( $admin_email ),
     352            'attachments'   => $attachments,
     353            'timezone_mode' => $timezone_mode,
     354            'admin_tz'      => $admin_tz,
     355            'user_tz'       => $user_tz,
     356        ] );
     357    }
     358
     359    $sent_admin = false;
     360    if ( is_email( $admin_email ) ) {
     361        $sent_admin = wp_mail( $admin_email, $admin_subject, $final_admin_tpl, $headers, $attachments );
     362    } else {
     363        if ( function_exists( 'creavibc_log' ) ) {
     364            creavibc_log( 'skip admin mail: invalid email', $admin_email );
     365        }
     366    }
     367
     368    $sent_user = false;
     369    if ( is_email( $user_email ) ) {
     370        $sent_user = wp_mail( $user_email, $user_subject, $final_user_tpl, $headers, $attachments );
     371    } else {
     372        if ( function_exists( 'creavibc_log' ) ) {
     373            creavibc_log( 'skip user mail: invalid email', $user_email );
     374        }
     375    }
     376
     377    if ( function_exists( 'creavibc_log' ) ) {
     378        creavibc_log( 'mail results', [
     379            'sent_admin' => $sent_admin,
     380            'sent_user'  => $sent_user,
     381        ] );
     382    }
     383
     384    // Auto-push to Google Calendar
     385    if ( (bool) get_post_meta( $service_id, '_creavibc_gcal_enable', true ) && function_exists( 'creavibc_gcal_maybe_insert_event' ) ) {
     386
     387        $attendees = [];
     388
     389        if ( is_email( $user_email ) ) {
     390            $attendees[] = $user_email;
     391        }
     392
     393        $admin_invite = get_post_meta( $service_id, '_creavibc_gcal_admin_email', true );
     394        if ( $admin_invite && is_email( $admin_invite ) ) {
     395            $attendees[] = sanitize_email( $admin_invite );
     396        }
     397
     398        creavibc_gcal_maybe_insert_event( [
     399            'service_id' => (int) $service_id,
     400            'date'       => $start_dt_admin->format( 'Y-m-d' ),
     401            'time'       => $start_dt_admin->format( 'H:i' ),
     402            'name'       => (string) $name,
     403            'email'      => (string) $user_email,
     404            'attendees'  => $attendees,
     405            'duration'   => (int) $duration,
     406        ] );
     407    }
     408
     409    // Cleanup
     410    if ( ! empty( $tmp_file ) && file_exists( $tmp_file ) ) {
     411        wp_delete_file( $tmp_file );
     412    }
    312413}
     414
     415
    313416
    314417add_action('wp_ajax_creavibc_get_booked_slots', 'creavibc_get_booked_slots');
     
    575678    }
    576679
     680    // ---------------------------
     681    // Event details templates (service-level)
     682    // ---------------------------
     683    $title_tpl = (string) get_post_meta($service_id, '_creavibc_event_title_tpl', true);
     684    if ( '' === trim($title_tpl) ) {
     685        $title_tpl = __( 'Booking — {service}', 'creavi-booking-service' );
     686    }
     687
     688    $location = (string) get_post_meta($service_id, '_creavibc_meeting_location', true);
     689    $location = trim($location) !== '' ? trim($location) : home_url();
     690
     691
     692    $desc_tpl = get_post_meta($service_id, '_creavibc_event_desc_tpl', true);
     693    if ( '' === $desc_tpl ) {
     694        $desc_tpl = "Client: {name}\nEmail: {email}\nService: {service}\nDate: {date}\nTime: {time}\n\n{custom}";
     695    }
     696
     697    // Build {custom} if you want it in pushed events too (optional)
     698    // We only have name/email/date/time here. Custom fields are in booking meta in your other flow.
     699    // For now keep it empty (or you can pass custom_lines via args later).
     700    $token_vars = [
     701        'name'    => $name,
     702        'email'   => $email,
     703        'service' => get_the_title($service_id),
     704        'date'    => $date,
     705        'time'    => $time . ' (' . $admin_tz . ')',
     706        'custom'  => '',
     707    ];
     708
     709    $event_title = creavibc_apply_tokens($title_tpl, $token_vars);
     710    $event_desc  = creavibc_apply_tokens($desc_tpl,  $token_vars);
     711
     712
     713
    577714    // 8. Attendees (client + optional admin invite)
    578715    $attendees = [];
    579     if ($email) $attendees[] = $email;
    580     $admin_invite = get_post_meta($service_id, '_creavibc_gcal_admin_email', true);
    581     if ($admin_invite && is_email($admin_invite)) {
    582         $attendees[] = $admin_invite;
    583     }
     716    if ( ! empty($args['attendees']) && is_array($args['attendees']) ) {
     717        $attendees = array_values(array_filter(array_map('sanitize_email', $args['attendees'])));
     718    } else {
     719        if ($email) { $attendees[] = $email; }
     720        $admin_invite = get_post_meta($service_id, '_creavibc_gcal_admin_email', true);
     721        if ($admin_invite && is_email($admin_invite)) {
     722            $attendees[] = $admin_invite;
     723        }
     724    }
     725
     726
     727    $invite = get_post_meta($service_id, '_creavibc_gcal_invite_attendee', true);
     728    $invite = ( '' === $invite ) ? '1' : (string) $invite; // default ON
     729    if ( $invite !== '1' ) {
     730        $attendees = [];
     731    }
     732
    584733
    585734    // 9. Target calendar
     
    592741    // 10. Log preview
    593742    $preview = [
    594         'summary'   => sprintf('Booking — %s', get_the_title($service_id)),
     743        'summary'   => $event_title,
     744        'location'  => $location,
    595745        'start'     => $start->format('c'),
    596746        'end'       => $end->format('c'),
     
    605755    // 11. Call connector
    606756    $res = creavibc_gcal_insert_event([
    607         'summary'     => sprintf('Booking — %s', get_the_title($service_id)),
    608         'description' => sprintf("Client: %s\nService: %s\nDate: %s\nTime: %s", $name ?: '—', get_the_title($service_id), $date, $time),
     757        'summary'     => $event_title,
     758        'description' => $event_desc,
     759        'location'    => $location,
    609760        'start'       => $start->format('c'),
    610761        'end'         => $end->format('c'),
     
    615766    ]);
    616767
     768
    617769    if (is_wp_error($res)) {
    618770        creavibc_log('push failed (WP_Error)', $res->get_error_message());
  • creavi-booking-service/trunk/includes/meta-boxes.php

    r3454590 r3456986  
    22if ( ! defined( 'ABSPATH' ) ) exit;
    33
     4
    45add_action('add_meta_boxes', function () {
    56
    6     add_meta_box(
    7         'creavibc_output_type',
    8         __( 'Booking Display Type', 'creavi-booking-service' ),
    9         'creavibc_render_output_type_box',
    10         'creavibc_service',
    11         'normal',
    12         'default'
    13     );
    14     add_meta_box('creavibc_available_days', __( 'Available Booking Days', 'creavi-booking-service' ), 'creavibc_render_available_days_box', 'creavibc_service', 'normal', 'high');
     7    add_meta_box('creavibc_output_type', __( 'Booking Display Type', 'creavi-booking-service' ), 'creavibc_render_output_type_box', 'creavibc_service', 'normal', 'high');
     8
     9    add_meta_box('creavibc_available_days', __( 'Available Booking Days', 'creavi-booking-service' ), 'creavibc_render_available_days_box', 'creavibc_service', 'normal', 'high');
     10
     11    add_meta_box('creavibc_time_slot_grid', __( 'Visual Time Slot Selector', 'creavi-booking-service' ), 'creavibc_render_weekday_slots_grid_box', 'creavibc_service', 'normal', 'high');
    1512
    1613    add_meta_box('creavibc_booking_form_fields', __( 'Booking Form Fields', 'creavi-booking-service' ), 'creavibc_render_form_fields_box', 'creavibc_service', 'normal', 'default');
     14
     15    add_meta_box('creavibc_service_appearance', __( 'Booking Appearance', 'creavi-booking-service' ), 'creavibc_render_service_appearance_box', 'creavibc_service', 'normal', 'default');
     16
     17    add_meta_box('creavibc_service_notifications', __( 'Notifications', 'creavi-booking-service' ), 'creavibc_render_service_notifications_box', 'creavibc_service', 'normal', 'default');
     18
     19    add_meta_box('creavibc_google_calendar', __( 'Google Calendar', 'creavi-booking-service' ), 'creavibc_render_google_calendar_box', 'creavibc_service', 'normal', 'low');
     20
    1721    add_meta_box('creavibc_booking_details', __( 'Booking Details', 'creavi-booking-service' ), 'creavibc_render_booking_meta', 'creavibc_booking', 'normal', 'default');
    18     add_meta_box(
    19         'creavibc_time_slot_grid',
    20         __( 'Visual Time Slot Selector', 'creavi-booking-service' ),
    21         'creavibc_render_weekday_slots_grid_box',
    22         'creavibc_service',
    23         'normal',
    24         'default'
    25     );   
    26 
    27     add_meta_box(
    28         'creavibc_service_options',
    29         __( 'Booking Appearance & Notifications', 'creavi-booking-service' ),
    30         'creavibc_render_service_options_box',
    31         'creavibc_service',
    32         'normal',
    33         'default'
    34     );
    35 
    36     add_meta_box(
    37         'creavibc_google_calendar',
    38         __( 'Google Calendar', 'creavi-booking-service' ),
    39         'creavibc_render_google_calendar_box',
    40         'creavibc_service',
    41         'normal',     
    42         'default'
    43     );
    44 
    4522
    4623});
     24
    4725
    4826add_action('init', function () {
     
    539517}
    540518
    541 function creavibc_render_service_options_box($post) {
     519function creavibc_render_service_appearance_box($post) {
     520
    542521    $text_color = get_post_meta($post->ID, '_creavibc_button_text_color', true) ?: '#ffffff';
    543522    $bg_color = get_post_meta($post->ID, '_creavibc_button_bg_color', true) ?: '#569FF7';
     
    559538
    560539    $color = get_post_meta($post->ID, '_creavibc_primary_color', true) ?: '#569FF7';
    561    
    562     $thankyou = get_post_meta( $post->ID, '_creavibc_thankyou_text', true );
    563     if ( empty( $thankyou ) ) {
    564         $thankyou = __( 'Thank you for booking! See you soon!', 'creavi-booking-service' );
    565     }
    566 
    567     $admin_email_tpl = get_post_meta( $post->ID, '_creavibc_email_admin_template', true );
    568     if ( empty( $admin_email_tpl ) ) {
    569         $admin_email_tpl = __( "New booking:\n\nName: {name}\nEmail: {email}\nDate: {date}\nTime: {time}\nService: {service}", 'creavi-booking-service' );
    570     }
    571 
    572     $user_email_tpl = get_post_meta( $post->ID, '_creavibc_email_user_template', true );
    573     if ( empty( $user_email_tpl ) ) {
    574         $user_email_tpl = __( "Hi {name},\n\nThanks for booking {service} on {date} at {time}.", 'creavi-booking-service' );
    575     }   
    576 
    577     wp_nonce_field('creavibc_save_service_options', 'creavibc_service_options_nonce');
    578    
     540
     541    // IMPORTANT: keep same nonce name so your existing save handler still works.
     542    //wp_nonce_field('creavibc_save_service_options', 'creavibc_service_options_nonce');
     543
    579544    echo '<p><label for="creavibc_primary_color"><strong>' . esc_html__( 'Primary Brand Color:', 'creavi-booking-service' ) . '</strong></label><br>';
    580 
    581545    echo '<input type="color" id="creavibc_primary_color" name="creavibc_primary_color" value="' . esc_attr($color) . '" style="width: 100px;"></p>';
    582546
    583547    echo '<p><label for="creavibc_button_text"><strong>' . esc_html__( 'Booking Button Text:', 'creavi-booking-service' ) . '</strong></label><br>';
    584 
    585548    echo '<input type="text" id="creavibc_button_text" name="creavibc_button_text" value="' . esc_attr($button_text) . '" style="width: 100%;"></p>';
    586549
    587550    echo '<hr><h4 style="margin-top: 30px;">' . esc_html__( 'Booking Button Style', 'creavi-booking-service' ) . '</h4>';
    588    
    589551
    590552    $allowed = [
     
    603565        echo wp_kses( creavibc_style_field( __( 'Text Color:', 'creavi-booking-service' ), 'creavibc_button_text_color', $text_color, 'color' ), $allowed );
    604566        echo wp_kses( creavibc_style_field( __( 'Background:', 'creavi-booking-service' ), 'creavibc_button_bg_color', $bg_color, 'color' ), $allowed );
    605         echo wp_kses( creavibc_style_field( __( 'Border Color:', 'creavi-booking-service' ), 'creavibc_button_border_color', $border_color, 'color' ), $allowed );   
     567        echo wp_kses( creavibc_style_field( __( 'Border Color:', 'creavi-booking-service' ), 'creavibc_button_border_color', $border_color, 'color' ), $allowed );
    606568    echo '</div>';
    607569
    608570    echo '<div class="creavibc-style-grid">';
    609         echo wp_kses( creavibc_style_field( __( 'Border Radius (px):', 'creavi-booking-service' ), 'creavibc_button_radius', $radius, 'number' ), $allowed );         
     571        echo wp_kses( creavibc_style_field( __( 'Border Radius (px):', 'creavi-booking-service' ), 'creavibc_button_radius', $radius, 'number' ), $allowed );
    610572        echo wp_kses( creavibc_style_field( __( 'Font Size (px):', 'creavi-booking-service' ), 'creavibc_button_font_size', $font_size, 'number' ), $allowed );
    611573    echo '</div>';
     
    613575    echo '<div class="creavibc-style-grid">';
    614576        echo wp_kses( creavibc_style_field( __( 'Padding Vertical (px):', 'creavi-booking-service' ), 'creavibc_button_padding_vertical', $padding_vertical, 'number' ), $allowed );
    615         echo wp_kses( creavibc_style_field( __( 'Padding Horizontal (px):', 'creavi-booking-service' ), 'creavibc_button_padding_horizontal', $padding_horizontal, 'number' ), $allowed );       
     577        echo wp_kses( creavibc_style_field( __( 'Padding Horizontal (px):', 'creavi-booking-service' ), 'creavibc_button_padding_horizontal', $padding_horizontal, 'number' ), $allowed );
    616578    echo '</div>';
    617579
     
    622584        echo wp_kses( creavibc_style_field( __( 'Border Color:', 'creavi-booking-service' ), 'creavibc_hover_border_color', $hover_border_color, 'color' ), $allowed );
    623585    echo '</div>';
     586}
     587
     588function creavibc_render_service_notifications_box($post) {
     589
     590    // IMPORTANT: keep same nonce name so your existing save handler still works.
     591    wp_nonce_field('creavibc_save_service_options', 'creavibc_service_options_nonce');
     592
     593    $thankyou = get_post_meta( $post->ID, '_creavibc_thankyou_text', true );
     594    if ( empty( $thankyou ) ) {
     595        $thankyou = __( 'Thank you for booking! See you soon!', 'creavi-booking-service' );
     596    }
     597
     598    $admin_email_tpl = get_post_meta( $post->ID, '_creavibc_email_admin_template', true );
     599    if ( empty( $admin_email_tpl ) ) {
     600        $admin_email_tpl = __( "New booking:\n\nName: {name}\nEmail: {email}\nDate: {date}\nTime: {time}\nService: {service}", 'creavi-booking-service' );
     601    }
     602
     603    $user_email_tpl = get_post_meta( $post->ID, '_creavibc_email_user_template', true );
     604    if ( empty( $user_email_tpl ) ) {
     605        $user_email_tpl = __( "Hi {name},\n\nThanks for booking {service} on {date} at {time}.", 'creavi-booking-service' );
     606    }
     607
     608    echo '<p><label for="creavibc_thankyou_text"><strong>' . esc_html__( 'Thank You Text:', 'creavi-booking-service' ) . '</strong></label><br>';
     609    echo '<input type="text" id="creavibc_thankyou_text" name="creavibc_thankyou_text" value="' . esc_attr($thankyou) . '" style="width: 100%;"></p>';
     610
     611    echo '<p><label for="creavibc_email_admin_template"><strong>' . esc_html__( 'Admin Email Template:', 'creavi-booking-service' ) . '</strong></label><br>';
     612    echo '<textarea name="creavibc_email_admin_template" id="creavibc_email_admin_template" rows="4" style="width: 100%;">' . esc_textarea($admin_email_tpl) . '</textarea></p>';
     613
     614    // Per-service Admin Notification Email
     615    $gcal_admin_email = get_post_meta($post->ID, '_creavibc_gcal_admin_email', true);
     616    if (empty($gcal_admin_email)) {
     617        $gcal_admin_email = get_option('admin_email');
     618    }
     619
     620    echo '<p><label for="creavibc_gcal_admin_email"><strong>' . esc_html__( 'Admin Notification Email:', 'creavi-booking-service' ) . '</strong></label><br>';
     621    echo '<input type="email" id="creavibc_gcal_admin_email" name="creavibc_gcal_admin_email" value="' . esc_attr($gcal_admin_email) . '" class="regular-text">';
     622    echo '<br><span class="description">';
     623    echo sprintf(
     624        esc_html__( 'Defaults to the site admin email (%s) if not set. Used for notifications and calendar invites.', 'creavi-booking-service' ),
     625        esc_html( get_option( 'admin_email' ) )
     626    );
     627    echo '</span></p>';
     628
     629    echo '<p><label for="creavibc_email_user_template"><strong>' . esc_html__( 'User Email Template:', 'creavi-booking-service' ) . '</strong></label><br>';
     630    echo '<textarea name="creavibc_email_user_template" id="creavibc_email_user_template" rows="4" style="width: 100%;">' . esc_textarea($user_email_tpl) . '</textarea></p>';
     631
     632    echo '<p class="description">' . esc_html__( 'Available tags:', 'creavi-booking-service' ) . ' {name}, {email}, {date}, {time}, {service}</p>';
     633
     634    // Reminder email (per service)
     635    $reminder_enabled = (bool) get_post_meta( $post->ID, '_creavibc_reminder_enabled', true );
     636
     637    $reminder_offset = (int) get_post_meta( $post->ID, '_creavibc_reminder_offset_minutes', true );
     638    if ( $reminder_offset <= 0 ) {
     639        $reminder_offset = 1440;
     640    }
     641
     642    $reminder_subject = get_post_meta( $post->ID, '_creavibc_email_reminder_subject', true );
     643    if ( empty( $reminder_subject ) ) {
     644        $reminder_subject = __( 'Reminder: {service} on {date} at {time}', 'creavi-booking-service' );
     645    }
     646
     647    $reminder_tpl = get_post_meta( $post->ID, '_creavibc_email_reminder_template', true );
     648    if ( empty( $reminder_tpl ) ) {
     649        $reminder_tpl = __( "Hi {name},\n\nJust a friendly reminder about your upcoming appointment:\n\nService: {service}\nDate: {date}\nTime: {time}\n\nSee you soon!", 'creavi-booking-service' );
     650    }
     651
     652    $reminder_options = array(
     653        10   => __( '10 minutes before', 'creavi-booking-service' ),
     654        20   => __( '20 minutes before', 'creavi-booking-service' ),
     655        30   => __( '30 minutes before', 'creavi-booking-service' ),
     656        40   => __( '40 minutes before', 'creavi-booking-service' ),
     657        50   => __( '50 minutes before', 'creavi-booking-service' ),
     658        60   => __( '1 hour before', 'creavi-booking-service' ),
     659        120  => __( '2 hours before', 'creavi-booking-service' ),
     660        180  => __( '3 hours before', 'creavi-booking-service' ),
     661        240  => __( '4 hours before', 'creavi-booking-service' ),
     662        300  => __( '5 hours before', 'creavi-booking-service' ),
     663        360  => __( '6 hours before', 'creavi-booking-service' ),
     664        420  => __( '7 hours before', 'creavi-booking-service' ),
     665        480  => __( '8 hours before', 'creavi-booking-service' ),
     666        540  => __( '9 hours before', 'creavi-booking-service' ),
     667        600  => __( '10 hours before', 'creavi-booking-service' ),
     668        660  => __( '11 hours before', 'creavi-booking-service' ),
     669        720  => __( '12 hours before', 'creavi-booking-service' ),
     670        1440 => __( '1 day before', 'creavi-booking-service' ),
     671    );
     672
     673    echo '<hr><h4 class="creavibc-subsection">' . esc_html__( 'Reminder Email', 'creavi-booking-service' ) . '</h4>';
     674
     675    echo '<label style="display:block; margin:0 0 10px;">';
     676    echo '<input type="checkbox" name="creavibc_reminder_enabled" value="1" ' . checked( $reminder_enabled, true, false ) . '> ';
     677    echo '<strong>' . esc_html__( 'Enable reminder email (user)', 'creavi-booking-service' ) . '</strong><br>';
     678    echo '<span style="color:#666;">' . esc_html__( 'Sends an automatic reminder to the customer before the appointment.', 'creavi-booking-service' ) . '</span>';
     679    echo '</label>';
     680
     681    echo '<p style="margin:0 0 10px;">';
     682    echo '<label for="creavibc_reminder_offset_minutes"><strong>' . esc_html__( 'Send reminder', 'creavi-booking-service' ) . '</strong></label><br>';
     683    echo '<select id="creavibc_reminder_offset_minutes" name="creavibc_reminder_offset_minutes">';
     684    foreach ( $reminder_options as $minutes => $label ) {
     685        echo '<option value="' . esc_attr( $minutes ) . '" ' . selected( $reminder_offset, $minutes, false ) . '>' . esc_html( $label ) . '</option>';
     686    }
     687    echo '</select>';
     688    echo '</p>';
     689
     690    echo '<p><label for="creavibc_email_reminder_subject"><strong>' . esc_html__( 'Reminder Email Subject', 'creavi-booking-service' ) . '</strong></label><br>';
     691    echo '<input type="text" id="creavibc_email_reminder_subject" name="creavibc_email_reminder_subject" value="' . esc_attr( $reminder_subject ) . '" style="width:100%"></p>';
     692
     693    echo '<p><label for="creavibc_email_reminder_template"><strong>' . esc_html__( 'Reminder Email Template', 'creavi-booking-service' ) . '</strong></label><br>';
     694    echo '<textarea name="creavibc_email_reminder_template" id="creavibc_email_reminder_template" rows="6" style="width:100%;">' . esc_textarea( $reminder_tpl ) . '</textarea></p>';
     695
     696    echo '<p class="description">';
     697    echo esc_html__( 'Available tags:', 'creavi-booking-service' ) . ' {name}, {email}, {date}, {time}, {service}';
     698    echo '</p>';
     699}
     700
     701
     702/*
     703function creavibc_render_service_options_box($post) {
     704    $text_color = get_post_meta($post->ID, '_creavibc_button_text_color', true) ?: '#ffffff';
     705    $bg_color = get_post_meta($post->ID, '_creavibc_button_bg_color', true) ?: '#569FF7';
     706    $border_color = get_post_meta($post->ID, '_creavibc_button_border_color', true) ?: '#569FF7';
     707    $radius = get_post_meta($post->ID, '_creavibc_button_radius', true) ?: '25';
     708    $font_size = get_post_meta($post->ID, '_creavibc_button_font_size', true) ?: '16';
     709
     710    $padding_vertical = get_post_meta($post->ID, '_creavibc_button_padding_vertical', true) ?: '12';
     711    $padding_horizontal = get_post_meta($post->ID, '_creavibc_button_padding_horizontal', true) ?: '24';
     712
     713    $hover_text_color = get_post_meta($post->ID, '_creavibc_hover_text_color', true) ?: $bg_color;
     714    $hover_bg_color = get_post_meta($post->ID, '_creavibc_hover_bg_color', true) ?: '#ffffff';
     715    $hover_border_color = get_post_meta($post->ID, '_creavibc_hover_border_color', true) ?: $bg_color;
     716
     717    $button_text = get_post_meta( $post->ID, '_creavibc_button_text', true );
     718    if ( empty( $button_text ) ) {
     719        $button_text = __( 'Book Now', 'creavi-booking-service' );
     720    }
     721
     722    $color = get_post_meta($post->ID, '_creavibc_primary_color', true) ?: '#569FF7';
     723   
     724    $thankyou = get_post_meta( $post->ID, '_creavibc_thankyou_text', true );
     725    if ( empty( $thankyou ) ) {
     726        $thankyou = __( 'Thank you for booking! See you soon!', 'creavi-booking-service' );
     727    }
     728
     729    $admin_email_tpl = get_post_meta( $post->ID, '_creavibc_email_admin_template', true );
     730    if ( empty( $admin_email_tpl ) ) {
     731        $admin_email_tpl = __( "New booking:\n\nName: {name}\nEmail: {email}\nDate: {date}\nTime: {time}\nService: {service}", 'creavi-booking-service' );
     732    }
     733
     734    $user_email_tpl = get_post_meta( $post->ID, '_creavibc_email_user_template', true );
     735    if ( empty( $user_email_tpl ) ) {
     736        $user_email_tpl = __( "Hi {name},\n\nThanks for booking {service} on {date} at {time}.", 'creavi-booking-service' );
     737    }   
     738
     739    wp_nonce_field('creavibc_save_service_options', 'creavibc_service_options_nonce');
     740   
     741    echo '<p><label for="creavibc_primary_color"><strong>' . esc_html__( 'Primary Brand Color:', 'creavi-booking-service' ) . '</strong></label><br>';
     742
     743    echo '<input type="color" id="creavibc_primary_color" name="creavibc_primary_color" value="' . esc_attr($color) . '" style="width: 100px;"></p>';
     744
     745    echo '<p><label for="creavibc_button_text"><strong>' . esc_html__( 'Booking Button Text:', 'creavi-booking-service' ) . '</strong></label><br>';
     746
     747    echo '<input type="text" id="creavibc_button_text" name="creavibc_button_text" value="' . esc_attr($button_text) . '" style="width: 100%;"></p>';
     748
     749    echo '<hr><h4 style="margin-top: 30px;">' . esc_html__( 'Booking Button Style', 'creavi-booking-service' ) . '</h4>';
     750   
     751
     752    $allowed = [
     753        'div'   => [ 'class' => [] ],
     754        'label' => [ 'for' => [] ],
     755        'input' => [
     756            'type'  => [],
     757            'name'  => [],
     758            'id'    => [],
     759            'value' => [],
     760            'min'   => [],
     761        ],
     762    ];
     763
     764    echo '<div class="creavibc-style-grid">';
     765        echo wp_kses( creavibc_style_field( __( 'Text Color:', 'creavi-booking-service' ), 'creavibc_button_text_color', $text_color, 'color' ), $allowed );
     766        echo wp_kses( creavibc_style_field( __( 'Background:', 'creavi-booking-service' ), 'creavibc_button_bg_color', $bg_color, 'color' ), $allowed );
     767        echo wp_kses( creavibc_style_field( __( 'Border Color:', 'creavi-booking-service' ), 'creavibc_button_border_color', $border_color, 'color' ), $allowed );   
     768    echo '</div>';
     769
     770    echo '<div class="creavibc-style-grid">';
     771        echo wp_kses( creavibc_style_field( __( 'Border Radius (px):', 'creavi-booking-service' ), 'creavibc_button_radius', $radius, 'number' ), $allowed );         
     772        echo wp_kses( creavibc_style_field( __( 'Font Size (px):', 'creavi-booking-service' ), 'creavibc_button_font_size', $font_size, 'number' ), $allowed );
     773    echo '</div>';
     774
     775    echo '<div class="creavibc-style-grid">';
     776        echo wp_kses( creavibc_style_field( __( 'Padding Vertical (px):', 'creavi-booking-service' ), 'creavibc_button_padding_vertical', $padding_vertical, 'number' ), $allowed );
     777        echo wp_kses( creavibc_style_field( __( 'Padding Horizontal (px):', 'creavi-booking-service' ), 'creavibc_button_padding_horizontal', $padding_horizontal, 'number' ), $allowed );       
     778    echo '</div>';
     779
     780    echo '<h4 class="creavibc-subsection">' . esc_html__( 'Hover Styles', 'creavi-booking-service' ) . '</h4>';
     781    echo '<div class="creavibc-style-grid">';
     782        echo wp_kses( creavibc_style_field( __( 'Text Color:', 'creavi-booking-service' ), 'creavibc_hover_text_color', $hover_text_color, 'color' ), $allowed );
     783        echo wp_kses( creavibc_style_field( __( 'Background:', 'creavi-booking-service' ), 'creavibc_hover_bg_color', $hover_bg_color, 'color' ), $allowed );
     784        echo wp_kses( creavibc_style_field( __( 'Border Color:', 'creavi-booking-service' ), 'creavibc_hover_border_color', $hover_border_color, 'color' ), $allowed );
     785    echo '</div>';
    624786
    625787    echo '<hr><h4 class="creavibc-subsection">' . esc_html__( 'Notifications', 'creavi-booking-service' ) . '</h4>';
     
    642804    echo '<br><span class="description">';
    643805    echo sprintf(
    644         /* translators: %s: admin email */
     806       
    645807        esc_html__( 'Defaults to the site admin email (%s) if not set. Used for notifications and calendar invites.', 'creavi-booking-service' ),
    646808        esc_html( get_option( 'admin_email' ) )
     
    653815    echo '<p class="description">' . esc_html__( 'Available tags:', 'creavi-booking-service' ) . ' {name}, {email}, {date}, {time}, {service}</p>';
    654816
    655     // ---------------------------
    656     // Reminder email (per service)
    657     // ---------------------------
     817   
    658818   // Reminder email (per service)
    659819    $reminder_enabled = (bool) get_post_meta( $post->ID, '_creavibc_reminder_enabled', true );
     
    735895}
    736896
     897*/
     898
    737899add_filter('manage_creavibc_service_posts_columns', function($columns) {   
    738900    $columns['creavibc_shortcode'] = __( 'Shortcode', 'creavi-booking-service' );
     
    8711033
    8721034
     1035
     1036    // ---------------------------
     1037    // Event details (optional)
     1038    // ---------------------------
     1039    $title_tpl = get_post_meta( $post->ID, '_creavibc_event_title_tpl', true );
     1040    if ( '' === $title_tpl ) {
     1041        $title_tpl = __( 'Booking — {service}', 'creavi-booking-service' );
     1042    }
     1043
     1044    $location = get_post_meta( $post->ID, '_creavibc_meeting_location', true ); // can be empty
     1045
     1046    $desc_tpl = get_post_meta( $post->ID, '_creavibc_event_desc_tpl', true );
     1047    if ( '' === $desc_tpl ) {
     1048        $desc_tpl = __( "Client: {name}\nEmail: {email}\nService: {service}\nDate: {date}\nTime: {time}\n\n{custom}", 'creavi-booking-service' );
     1049    }
     1050
     1051    $invite_attendee = get_post_meta( $post->ID, '_creavibc_gcal_invite_attendee', true );
     1052    $invite_attendee = ( '' === $invite_attendee ) ? 1 : (int) $invite_attendee; // default ON
     1053    ?>
     1054
     1055    <hr style="margin:16px 0;">
     1056
     1057    <details class="creavibc-advanced-details">
     1058        <summary style="cursor:pointer; font-weight:600;">
     1059            <?php esc_html_e( 'Event details', 'creavi-booking-service' ); ?>
     1060        </summary>
     1061
     1062        <div style="margin-top:12px;">
     1063            <p style="margin:0 0 10px; color:#666;">
     1064                <?php esc_html_e( 'These settings affect Google Calendar events and .ics files.', 'creavi-booking-service' ); ?>
     1065            </p>
     1066
     1067            <p>
     1068                <label for="creavibc_event_title_tpl"><strong><?php esc_html_e( 'Event title format', 'creavi-booking-service' ); ?></strong></label><br>
     1069                <input
     1070                    type="text"
     1071                    id="creavibc_event_title_tpl"
     1072                    name="creavibc_event_title_tpl"
     1073                    value="<?php echo esc_attr( $title_tpl ); ?>"
     1074                    style="width:100%;"
     1075                    placeholder="<?php echo esc_attr__( 'Booking — {service}', 'creavi-booking-service' ); ?>"
     1076                >
     1077                <span class="description">
     1078                    <?php esc_html_e( 'Examples:', 'creavi-booking-service' ); ?>
     1079                    <code><?php esc_html_e( 'Call with {name}', 'creavi-booking-service' ); ?></code>,                   
     1080                </span>
     1081            </p>
     1082
     1083            <p>
     1084                <label for="creavibc_meeting_location"><strong><?php esc_html_e( 'Meeting location / link', 'creavi-booking-service' ); ?></strong></label><br>
     1085                <input
     1086                    type="text"
     1087                    id="creavibc_meeting_location"
     1088                    name="creavibc_meeting_location"
     1089                    value="<?php echo esc_attr( $location ); ?>"
     1090                    style="width:100%;"
     1091                    placeholder="<?php echo esc_attr__( 'Leave empty to use site URL', 'creavi-booking-service' ); ?>"
     1092                >
     1093                <span class="description">
     1094                    <?php esc_html_e( 'Shown as the event location in Google Calendar and .ics.', 'creavi-booking-service' ); ?>
     1095                </span>
     1096            </p>
     1097
     1098            <p>
     1099                <label for="creavibc_event_desc_tpl"><strong><?php esc_html_e( 'Event description template', 'creavi-booking-service' ); ?></strong></label><br>
     1100                <textarea
     1101                    id="creavibc_event_desc_tpl"
     1102                    name="creavibc_event_desc_tpl"
     1103                    rows="6"
     1104                    style="width:100%;"
     1105                ><?php echo esc_textarea( $desc_tpl ); ?></textarea>
     1106                <span class="description">
     1107                    <?php esc_html_e( 'Available tags:', 'creavi-booking-service' ); ?>
     1108                    <code>{name}</code> <code>{email}</code> <code>{date}</code> <code>{time}</code> <code>{service}</code>
     1109                </span>
     1110            </p>
     1111
     1112            <label style="display:block; margin:0;">
     1113                <input type="checkbox" name="creavibc_gcal_invite_attendee" value="1" <?php checked( $invite_attendee, 1 ); ?>>
     1114                <strong><?php esc_html_e( 'Invite attendee (send calendar invitation)', 'creavi-booking-service' ); ?></strong><br>
     1115                <span style="color:#666;"><?php esc_html_e( 'If enabled, the client email is added as an attendee when pushing to Google Calendar.', 'creavi-booking-service' ); ?></span>
     1116            </label>
     1117        </div>
     1118    </details>
     1119    <?php
     1120
     1121
     1122
    8731123    echo '</div>';
    8741124}
  • creavi-booking-service/trunk/includes/save-service.php

    r3448790 r3456986  
    216216        update_post_meta($post_id,'_creavibc_gcal_enable', isset($_POST['creavibc_gcal_enable']) ? '1' : '0');
    217217
    218         // ✅ NEW: Live block busy slots from Google Calendar on frontend
     218        // Live block busy slots from Google Calendar on frontend
    219219        update_post_meta($post_id,'_creavibc_gcal_block_live', isset($_POST['creavibc_gcal_block_live']) ? '1' : '0');
     220
     221        // Event details for GCal + .ics
     222        if ( isset($_POST['creavibc_event_title_tpl']) ) {
     223            $title_tpl = sanitize_text_field( wp_unslash($_POST['creavibc_event_title_tpl']) );
     224            if ( '' === $title_tpl ) {
     225                // keep empty allowed? I suggest fallback by deleting meta
     226                delete_post_meta($post_id, '_creavibc_event_title_tpl');
     227            } else {
     228                update_post_meta($post_id, '_creavibc_event_title_tpl', $title_tpl);
     229            }
     230        }
     231
     232        if ( isset($_POST['creavibc_meeting_location']) ) {
     233            // allow URL or plain text
     234            $location = sanitize_text_field( wp_unslash($_POST['creavibc_meeting_location']) );
     235            if ( '' === $location ) {
     236                delete_post_meta($post_id, '_creavibc_meeting_location');
     237            } else {
     238                update_post_meta($post_id, '_creavibc_meeting_location', $location);
     239            }
     240        }
     241
     242        if ( isset($_POST['creavibc_event_desc_tpl']) ) {
     243            $desc_tpl = sanitize_textarea_field( wp_unslash($_POST['creavibc_event_desc_tpl']) );
     244            if ( '' === $desc_tpl ) {
     245                delete_post_meta($post_id, '_creavibc_event_desc_tpl');
     246            } else {
     247                update_post_meta($post_id, '_creavibc_event_desc_tpl', $desc_tpl);
     248            }
     249        }
     250
     251        // default ON if checkbox missing on first save? You can set 1 by default in render,
     252        // but for saving we store explicit 1/0:
     253        $invite = isset($_POST['creavibc_gcal_invite_attendee']) ? '1' : '0';
     254        update_post_meta($post_id, '_creavibc_gcal_invite_attendee', $invite);
     255
    220256
    221257
  • creavi-booking-service/trunk/readme.txt

    r3454590 r3456986  
    55Tested up to: 6.7
    66Requires PHP: 7.4 
    7 Stable tag: 1.1.7
     7Stable tag: 1.1.8
    88License: GPLv2 or later 
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    848412. Frontend: styled popup interface for yoga class booking 
    8585
     86== Languages ==
     87
     88This plugin is fully translation-ready and supports localization via translate.wordpress.org.
     89
     90Available languages include:
     91* English (default)
     92* French
     93* Danish
     94
     95You can help translate this plugin into your language at:
     96https://translate.wordpress.org/projects/wp-plugins/creavi-booking-service/
     97
    8698
    8799== Changelog ==
     100
     101= 1.1.8 =
     102* Extended Google Calendar event details
     103* Improved Google Calendar & .ics event data consistency
     104* Improved Admin metabox reorganization for clearer service setup
     105* Improved Modernized admin styling and layout
    88106
    89107= 1.1.7 =
Note: See TracChangeset for help on using the changeset viewer.