Changeset 3456986
- Timestamp:
- 02/09/2026 11:13:25 AM (7 weeks ago)
- Location:
- creavi-booking-service
- Files:
-
- 45 added
- 7 edited
-
tags/1.1.8 (added)
-
tags/1.1.8/assets (added)
-
tags/1.1.8/assets/css (added)
-
tags/1.1.8/assets/css/admin.css (added)
-
tags/1.1.8/assets/css/creavibc-deactivation-feedback.css (added)
-
tags/1.1.8/assets/css/style.css (added)
-
tags/1.1.8/assets/images (added)
-
tags/1.1.8/assets/images/service_placeholder.png (added)
-
tags/1.1.8/assets/js (added)
-
tags/1.1.8/assets/js/admin.js (added)
-
tags/1.1.8/assets/js/booking.js (added)
-
tags/1.1.8/assets/js/cbs-gcal-busy-admin.js (added)
-
tags/1.1.8/assets/js/creavibc-deactivation-feedback.js (added)
-
tags/1.1.8/assets/vendor (added)
-
tags/1.1.8/assets/vendor/flatpickr (added)
-
tags/1.1.8/assets/vendor/flatpickr/flatpickr.min.css (added)
-
tags/1.1.8/assets/vendor/flatpickr/flatpickr.min.js (added)
-
tags/1.1.8/assets/vendor/flatpickr/l10n (added)
-
tags/1.1.8/assets/vendor/flatpickr/l10n/da.js (added)
-
tags/1.1.8/assets/vendor/flatpickr/l10n/fr.js (added)
-
tags/1.1.8/assets/vendor/luxon (added)
-
tags/1.1.8/assets/vendor/luxon/luxon.min.js (added)
-
tags/1.1.8/creavi-booking-service.php (added)
-
tags/1.1.8/includes (added)
-
tags/1.1.8/includes/admin.php (added)
-
tags/1.1.8/includes/ajax-handlers.php (added)
-
tags/1.1.8/includes/cbs-gcal-remote.php (added)
-
tags/1.1.8/includes/deactivation-feedback.php (added)
-
tags/1.1.8/includes/functions.php (added)
-
tags/1.1.8/includes/gcal-freebusy.php (added)
-
tags/1.1.8/includes/meta-boxes.php (added)
-
tags/1.1.8/includes/placeholders.php (added)
-
tags/1.1.8/includes/post-types.php (added)
-
tags/1.1.8/includes/reminders.php (added)
-
tags/1.1.8/includes/render-booking-inline.php (added)
-
tags/1.1.8/includes/save-service.php (added)
-
tags/1.1.8/languages (added)
-
tags/1.1.8/languages/creavi-booking-service-da_DK-creavibc-script.json (added)
-
tags/1.1.8/languages/creavi-booking-service-da_DK.mo (added)
-
tags/1.1.8/languages/creavi-booking-service-da_DK.po (added)
-
tags/1.1.8/languages/creavi-booking-service-fr_FR-creavibc-script.json (added)
-
tags/1.1.8/languages/creavi-booking-service-fr_FR.mo (added)
-
tags/1.1.8/languages/creavi-booking-service-fr_FR.po (added)
-
tags/1.1.8/languages/creavi-booking-service.pot (added)
-
tags/1.1.8/readme.txt (added)
-
trunk/assets/css/admin.css (modified) (4 diffs)
-
trunk/assets/css/creavibc-deactivation-feedback.css (modified) (1 diff)
-
trunk/creavi-booking-service.php (modified) (2 diffs)
-
trunk/includes/ajax-handlers.php (modified) (6 diffs)
-
trunk/includes/meta-boxes.php (modified) (10 diffs)
-
trunk/includes/save-service.php (modified) (1 diff)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
creavi-booking-service/trunk/assets/css/admin.css
r3431070 r3456986 74 74 font-size: 14px; 75 75 } 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; 84 78 } 85 79 … … 130 124 .creavibc-grid-table td, 131 125 .creavibc-grid-table th { 132 border: 1px solid #ccc; 133 padding: 8px; 126 border: 1px solid #dfdfdf; 134 127 text-align: center; 128 width: 100px; 135 129 } 136 130 … … 298 292 /* Tab buttons */ 299 293 .creavibc-tab-nav { 300 display: inline-flex; 301 background: #f3f4f6; 294 display: inline-flex; 302 295 border-radius: 999px; 303 296 padding: 4px; … … 384 377 box-shadow: 0 0 0 3px rgba(96, 165, 250, .25); 385 378 } 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, 572 details 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, 767 label[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, 787 input[type="text"], 788 input[type="email"], 789 input[type="number"], 790 select{ 791 padding: 12px 14px !important; /* <- more breathing room */ 792 line-height: 1.4 !important; 793 } 794 795 /* ----------------------------------------------------- 796 TEXTAREAS — even more comfort 797 ----------------------------------------------------- */ 798 textarea, 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 ----------------------------------------------------- */ 810 input[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) */ 820 input[type="color"]::-webkit-color-swatch-wrapper{ 821 padding: 0; 822 } 823 input[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, 841 input[type="text"], 842 input[type="email"], 843 input[type="number"], 844 select{ 845 min-height: 42px; 846 } 847 848 textarea{ 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, 865 details 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 5 5 6 6 :root{ 7 --creavibc-accent: #7a00a0; /* <-- change to yourbrand color */7 --creavibc-accent: #7a00a0; /* brand color */ 8 8 --creavibc-accent-2: #a56db4; /* optional second tone */ 9 9 --creavibc-text: #111827; -
creavi-booking-service/trunk/creavi-booking-service.php
r3454590 r3456986 5 5 * Text Domain: creavi-booking-service 6 6 * Domain Path: /languages 7 * Version: 1.1. 77 * Version: 1.1.8 8 8 * Author: Creavi 9 9 * License: GPL2 … … 16 16 define('CREAVIBC_PLUGIN_URL', plugin_dir_url(__FILE__)); 17 17 define('CREAVIBC_PLUGIN_PATH', plugin_dir_path(__FILE__)); 18 define('CREAVIBC_VERSION', '1.1. 7');18 define('CREAVIBC_VERSION', '1.1.8'); 19 19 20 20 -
creavi-booking-service/trunk/includes/ajax-handlers.php
r3441012 r3456986 12 12 } 13 13 } 14 15 if ( ! 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 25 if ( ! 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 14 34 15 35 // ===================================================== … … 114 134 } 115 135 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); 136 function 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 } 312 413 } 414 415 313 416 314 417 add_action('wp_ajax_creavibc_get_booked_slots', 'creavibc_get_booked_slots'); … … 575 678 } 576 679 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 577 714 // 8. Attendees (client + optional admin invite) 578 715 $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 584 733 585 734 // 9. Target calendar … … 592 741 // 10. Log preview 593 742 $preview = [ 594 'summary' => sprintf('Booking — %s', get_the_title($service_id)), 743 'summary' => $event_title, 744 'location' => $location, 595 745 'start' => $start->format('c'), 596 746 'end' => $end->format('c'), … … 605 755 // 11. Call connector 606 756 $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, 609 760 'start' => $start->format('c'), 610 761 'end' => $end->format('c'), … … 615 766 ]); 616 767 768 617 769 if (is_wp_error($res)) { 618 770 creavibc_log('push failed (WP_Error)', $res->get_error_message()); -
creavi-booking-service/trunk/includes/meta-boxes.php
r3454590 r3456986 2 2 if ( ! defined( 'ABSPATH' ) ) exit; 3 3 4 4 5 add_action('add_meta_boxes', function () { 5 6 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'); 15 12 16 13 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 17 21 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 45 22 46 23 }); 24 47 25 48 26 add_action('init', function () { … … 539 517 } 540 518 541 function creavibc_render_service_options_box($post) { 519 function creavibc_render_service_appearance_box($post) { 520 542 521 $text_color = get_post_meta($post->ID, '_creavibc_button_text_color', true) ?: '#ffffff'; 543 522 $bg_color = get_post_meta($post->ID, '_creavibc_button_bg_color', true) ?: '#569FF7'; … … 559 538 560 539 $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 579 544 echo '<p><label for="creavibc_primary_color"><strong>' . esc_html__( 'Primary Brand Color:', 'creavi-booking-service' ) . '</strong></label><br>'; 580 581 545 echo '<input type="color" id="creavibc_primary_color" name="creavibc_primary_color" value="' . esc_attr($color) . '" style="width: 100px;"></p>'; 582 546 583 547 echo '<p><label for="creavibc_button_text"><strong>' . esc_html__( 'Booking Button Text:', 'creavi-booking-service' ) . '</strong></label><br>'; 584 585 548 echo '<input type="text" id="creavibc_button_text" name="creavibc_button_text" value="' . esc_attr($button_text) . '" style="width: 100%;"></p>'; 586 549 587 550 echo '<hr><h4 style="margin-top: 30px;">' . esc_html__( 'Booking Button Style', 'creavi-booking-service' ) . '</h4>'; 588 589 551 590 552 $allowed = [ … … 603 565 echo wp_kses( creavibc_style_field( __( 'Text Color:', 'creavi-booking-service' ), 'creavibc_button_text_color', $text_color, 'color' ), $allowed ); 604 566 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 ); 606 568 echo '</div>'; 607 569 608 570 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 ); 610 572 echo wp_kses( creavibc_style_field( __( 'Font Size (px):', 'creavi-booking-service' ), 'creavibc_button_font_size', $font_size, 'number' ), $allowed ); 611 573 echo '</div>'; … … 613 575 echo '<div class="creavibc-style-grid">'; 614 576 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 ); 616 578 echo '</div>'; 617 579 … … 622 584 echo wp_kses( creavibc_style_field( __( 'Border Color:', 'creavi-booking-service' ), 'creavibc_hover_border_color', $hover_border_color, 'color' ), $allowed ); 623 585 echo '</div>'; 586 } 587 588 function 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 /* 703 function 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>'; 624 786 625 787 echo '<hr><h4 class="creavibc-subsection">' . esc_html__( 'Notifications', 'creavi-booking-service' ) . '</h4>'; … … 642 804 echo '<br><span class="description">'; 643 805 echo sprintf( 644 /* translators: %s: admin email */806 645 807 esc_html__( 'Defaults to the site admin email (%s) if not set. Used for notifications and calendar invites.', 'creavi-booking-service' ), 646 808 esc_html( get_option( 'admin_email' ) ) … … 653 815 echo '<p class="description">' . esc_html__( 'Available tags:', 'creavi-booking-service' ) . ' {name}, {email}, {date}, {time}, {service}</p>'; 654 816 655 // --------------------------- 656 // Reminder email (per service) 657 // --------------------------- 817 658 818 // Reminder email (per service) 659 819 $reminder_enabled = (bool) get_post_meta( $post->ID, '_creavibc_reminder_enabled', true ); … … 735 895 } 736 896 897 */ 898 737 899 add_filter('manage_creavibc_service_posts_columns', function($columns) { 738 900 $columns['creavibc_shortcode'] = __( 'Shortcode', 'creavi-booking-service' ); … … 871 1033 872 1034 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 873 1123 echo '</div>'; 874 1124 } -
creavi-booking-service/trunk/includes/save-service.php
r3448790 r3456986 216 216 update_post_meta($post_id,'_creavibc_gcal_enable', isset($_POST['creavibc_gcal_enable']) ? '1' : '0'); 217 217 218 // ✅ NEW:Live block busy slots from Google Calendar on frontend218 // Live block busy slots from Google Calendar on frontend 219 219 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 220 256 221 257 -
creavi-booking-service/trunk/readme.txt
r3454590 r3456986 5 5 Tested up to: 6.7 6 6 Requires PHP: 7.4 7 Stable tag: 1.1. 77 Stable tag: 1.1.8 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 84 84 12. Frontend: styled popup interface for yoga class booking 85 85 86 == Languages == 87 88 This plugin is fully translation-ready and supports localization via translate.wordpress.org. 89 90 Available languages include: 91 * English (default) 92 * French 93 * Danish 94 95 You can help translate this plugin into your language at: 96 https://translate.wordpress.org/projects/wp-plugins/creavi-booking-service/ 97 86 98 87 99 == 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 88 106 89 107 = 1.1.7 =
Note: See TracChangeset
for help on using the changeset viewer.