PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
2.5.0 2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 All 34 releases
← All changes | app/Http/Controllers/SchedulesController.php +212 -20 2.1.2 → 2.5.0 View file →
@@ -14,8 +14,11 @@
14 14 use FluentBooking\Framework\Http\Request\Request;
15 15 use FluentBooking\App\Services\PermissionManager;
16 16 use FluentBooking\App\Services\CalendarService;
17 17 use FluentBooking\App\Services\ExportHelper;
18 +use FluentBooking\App\Services\Integrations\FluentCRM\CrmContactService;
19 +use FluentBooking\App\Services\Integrations\FluentCart\CustomerProfileService;
20 +use FluentCrm\App\Services\PermissionManager as CrmPermissionManager;
18 21
19 22 class SchedulesController extends Controller
20 23 {
21 24 public function index(Request $request)
@@ -138,9 +141,9 @@
138 141
139 142 $query = Booking::with(['calendar_event']);
140 143
141 144 $hasPermission = PermissionManager::userCanSeeAllBookings();
142 -
145 +
143 146 if (!$hasPermission || $author == 'me') {
144 147 $query->where('host_user_id', get_current_user_id());
145 148 }
146 149
@@ -175,9 +178,9 @@
175 178
176 179 private function addCountsForFirstPage($author, &$data)
177 180 {
178 181 $bookingQuery = Booking::query()
179 - ->when($author == 'me', function($query) {
182 + ->when(!PermissionManager::userCanSeeAllBookings() || $author == 'me', function($query) {
180 183 return $query->where('host_user_id', get_current_user_id());
181 184 })
182 185 ->when($author && is_numeric($author), function($query) use ($author) {
183 186 return $query->where('calendar_id', $author);
@@ -237,10 +240,19 @@
237 240 if (!in_array($value, ['scheduled', 'completed', 'cancelled', 'rejected', 'no_show'])) {
238 241 return $this->sendError(['message' => __('Invalid status', 'fluent-booking')]);
239 242 }
240 243
244 + if (in_array($booking->status, ['cancelled', 'rejected'])) {
245 + return $this->sendError(['message' => __('A cancelled or rejected booking can not be changed', 'fluent-booking')]);
246 + }
247 +
241 248 if ($value == 'scheduled' && $booking->payment_method && $booking->payment_order) {
242 249 $order = $booking->payment_order;
250 +
251 + if (in_array($order->status, ['refunded', 'partially-refunded'])) {
252 + return $this->sendError(['message' => __('A refunded payment can not be marked as paid', 'fluent-booking')]);
253 + }
254 +
243 255 $order->total_paid = $order->total_amount;
244 256 $order->completed_at = gmdate('Y-m-d H:i:s'); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
245 257 $order->status = 'paid';
246 258 $order->save();
@@ -255,18 +267,24 @@
255 267 do_action('fluent_booking/log_booking_activity', $this->getConfirmLog($booking->id));
256 268 }
257 269
258 270 if ($value == 'cancelled') {
259 - $cancelReason = sanitize_text_field($data['cancel_reason']);
271 + $cancelReason = sanitize_text_field(Arr::get($data, 'cancel_reason', ''));
260 272 $booking->cancelMeeting($cancelReason, 'host', get_current_user_id());
261 273 }
262 274
263 275 if ($value == 'rejected') {
264 - $rejectReason = sanitize_text_field($data['reject_reason']);
276 + $rejectReason = sanitize_text_field(Arr::get($data, 'reject_reason', ''));
265 277 $booking->rejectMeeting($rejectReason, get_current_user_id());
266 278 }
267 279
268 280 if (in_array($value, ['cancelled', 'rejected'])) {
281 + // cancelMeeting() and rejectMeeting() refuse some statuses without changing the booking.
282 + if ($booking->status != $value) {
283 + /* translators: %s: Booking status */
284 + return $this->sendError(['message' => sprintf(__('This booking can not be %s', 'fluent-booking'), $value)]);
285 + }
286 +
269 287 if ($booking->payment_method && Arr::get($data, 'refund_payment') == 'yes') {
270 288 do_action('fluent_booking/refund_payment_' . $booking->payment_method, $booking, $booking->calendar_event);
271 289 }
272 290 return [
@@ -336,11 +354,9 @@
336 354 {
337 355 $booking = Booking::with('calendar_event');
338 356
339 357 if (!PermissionManager::userCanSeeAllBookings()) {
340 - $booking->whereHas('calendar', function ($q) {
341 - $q->where('user_id', get_current_user_id());
342 - });
358 + $booking->whereHostAccess(get_current_user_id());
343 359 }
344 360
345 361 $booking = $booking->findOrFail($bookingId);
346 362 $booking = $this->formatBooking($booking);
@@ -405,11 +421,9 @@
405 421 {
406 422 $booking = Booking::with('slot');
407 423
408 424 if (!PermissionManager::userCanSeeAllBookings()) {
409 - $booking->whereHas('calendar', function ($q) {
410 - $q->where('user_id', get_current_user_id());
411 - });
425 + $booking->whereHostAccess(get_current_user_id());
412 426 }
413 427
414 428 $booking = $booking->where('group_id', $groupId)->first();
415 429
@@ -438,8 +452,9 @@
438 452 {
439 453 $this->resolveOwnedBookingOrFail($bookingId);
440 454
441 455 $activities = BookingActivity::where('booking_id', $bookingId)
456 + ->where('type', '!=', BookingActivity::TYPE_NOTE)
442 457 ->orderBy('id', 'DESC')
443 458 ->get();
444 459
445 460 return [
@@ -451,11 +466,12 @@
451 466 {
452 467 $booking = $this->resolveOwnedBookingOrFail($bookingId);
453 468
454 469 $activities = BookingActivity::where('booking_id', $booking->id)
470 + ->where('type', '!=', BookingActivity::TYPE_NOTE)
455 471 ->orderBy('id', 'DESC')
456 472 ->get();
457 -
473 +
458 474 $activities->each(function ($activity) {
459 475 $activity->description = wp_unslash($activity->description);
460 476 });
461 477
@@ -461,19 +477,31 @@
461 477
462 478 $sidebarContents = [];
463 479 $mainBodyContents = [];
464 480
465 - if (defined('FLUENTCRM')) {
466 - $profileHtml = fluentcrm_get_crm_profile_html($booking->email, false);
467 - if ($profileHtml) {
468 - $sidebarContents[] = [
469 - 'id' => 'fluent_crm_profule',
470 - 'title' => __('CRM Profile', 'fluent-booking'),
471 - 'content' => $profileHtml
472 - ];
473 - }
481 + $canReadCrm = CrmContactService::isActive() && CrmPermissionManager::currentUserCan('fcrm_read_contacts');
482 + $crmProfile = $canReadCrm ? CrmContactService::getProfileData($booking->email) : null;
483 + if ($crmProfile) {
484 + $sidebarContents[] = [
485 + 'id' => 'fluent_crm_profule',
486 + 'title' => __('CRM Profile', 'fluent-booking'),
487 + 'type' => 'crm_profile',
488 + 'profile' => $crmProfile,
489 + ];
474 490 }
475 491
492 + $cartProfile = CustomerProfileService::canView()
493 + ? CustomerProfileService::getProfileData($booking->email)
494 + : null;
495 + if ($cartProfile) {
496 + $sidebarContents[] = [
497 + 'id' => 'fluent_cart_profile',
498 + 'title' => __('Cart Profile', 'fluent-booking'),
499 + 'type' => 'cart_profile',
500 + 'profile' => $cartProfile,
501 + ];
502 + }
503 +
476 504 $order = null;
477 505 if ($booking->payment_status && $booking->payment_order) {
478 506 $order = $booking->payment_order;
479 507 $relations = ['items', 'transaction'];
@@ -492,8 +520,172 @@
492 520 'sidebar_contents' => $sidebarContents,
493 521 'payment_order' => $order,
494 522 'main_body_contents' => $mainBodyContents
495 523 ];
524 + }
525 +
526 + /**
527 + * Return the CRM contact state for a booking plus the full tag/list option
528 + * sets, so the admin can manage tags/lists inline from the CRM Profile card.
529 + */
530 + public function getCrmContact(Request $request, $bookingId)
531 + {
532 + $booking = $this->resolveOwnedBookingOrFail($bookingId);
533 +
534 + if (!CrmContactService::isActive()) {
535 + return $this->sendError([
536 + 'message' => __('FluentCRM is not active.', 'fluent-booking')
537 + ]);
538 + }
539 +
540 + if (!CrmPermissionManager::currentUserCan('fcrm_read_contacts')) {
541 + return $this->sendError([
542 + 'message' => __('You do not have permission to read CRM contacts.', 'fluent-booking')
543 + ], 403);
544 + }
545 +
546 + $state = CrmContactService::getContactState($booking->email);
547 +
548 + if (!$state) {
549 + return $this->sendError([
550 + 'message' => __('No CRM contact found for this booking.', 'fluent-booking')
551 + ], 404);
552 + }
553 +
554 + return $state;
555 + }
556 +
557 + /**
558 + * Guest-field prefill for the CRM "Book Appointment" action.
559 + * Keyed by contact id, so it carries its own CRM-read gate.
560 + */
561 + public function getCrmContactPrefill(Request $request)
562 + {
563 + if (!CrmContactService::isActive()) {
564 + return $this->sendError([
565 + 'message' => __('FluentCRM is not active.', 'fluent-booking')
566 + ]);
567 + }
568 +
569 + if (!CrmPermissionManager::currentUserCan('fcrm_read_contacts')) {
570 + return $this->sendError([
571 + 'message' => __('You do not have permission to read CRM contacts.', 'fluent-booking')
572 + ], 403);
573 + }
574 +
575 + $contactId = absint($request->get('contact_id'));
576 +
577 + if (!$contactId) {
578 + return $this->sendError([
579 + 'message' => __('Invalid contact.', 'fluent-booking')
580 + ], 422);
581 + }
582 +
583 + $prefill = CrmContactService::getBookingPrefillData($contactId);
584 +
585 + if (!$prefill) {
586 + return $this->sendError([
587 + 'message' => __('No CRM contact found.', 'fluent-booking')
588 + ], 404);
589 + }
590 +
591 + return [
592 + 'prefill' => $prefill,
593 + ];
594 + }
595 +
596 + /**
597 + * Typeahead search for the admin booking modal's CRM contact picker.
598 + * Same intersection gate as getCrmContactPrefill: booking access (policy) AND fcrm_read_contacts.
599 + */
600 + public function searchCrmContacts(Request $request)
601 + {
602 + if (!CrmContactService::isActive()) {
603 + return $this->sendError([
604 + 'message' => __('FluentCRM is not active.', 'fluent-booking')
605 + ]);
606 + }
607 +
608 + if (!CrmPermissionManager::currentUserCan('fcrm_read_contacts')) {
609 + return $this->sendError([
610 + 'message' => __('You do not have permission to read CRM contacts.', 'fluent-booking')
611 + ], 403);
612 + }
613 +
614 + $search = sanitize_text_field($request->get('search', ''));
615 +
616 + return [
617 + 'contacts' => CrmContactService::searchContacts($search),
618 + ];
619 + }
620 +
621 + /**
622 + * Bounded, searchable tag/list options for the CRM Profile picker.
623 + */
624 + public function getCrmOptions(Request $request, $bookingId)
625 + {
626 + $this->resolveOwnedBookingOrFail($bookingId);
627 +
628 + if (!CrmContactService::isActive()) {
629 + return $this->sendError([
630 + 'message' => __('FluentCRM is not active.', 'fluent-booking')
631 + ]);
632 + }
633 +
634 + if (!CrmPermissionManager::currentUserCan('fcrm_read_contacts')) {
635 + return $this->sendError([
636 + 'message' => __('You do not have permission to read CRM contacts.', 'fluent-booking')
637 + ], 403);
638 + }
639 +
640 + $type = $request->get('type') === 'lists' ? 'lists' : 'tags';
641 + $search = sanitize_text_field($request->get('search', ''));
642 +
643 + return [
644 + 'options' => CrmContactService::getOptions($type, $search),
645 + ];
646 + }
647 +
648 + public function updateCrmTags(Request $request, $bookingId)
649 + {
650 + return $this->updateCrmTaxonomy($request, $bookingId, 'tags');
651 + }
652 +
653 + public function updateCrmLists(Request $request, $bookingId)
654 + {
655 + return $this->updateCrmTaxonomy($request, $bookingId, 'lists');
656 + }
657 +
658 + private function updateCrmTaxonomy(Request $request, $bookingId, $type)
659 + {
660 + $booking = $this->resolveOwnedBookingOrFail($bookingId);
661 +
662 + if (!CrmContactService::isActive()) {
663 + return $this->sendError([
664 + 'message' => __('FluentCRM is not active.', 'fluent-booking')
665 + ]);
666 + }
667 +
668 + if (!CrmPermissionManager::currentUserCan('fcrm_manage_contacts')) {
669 + return $this->sendError([
670 + 'message' => __('You do not have permission to manage CRM contacts.', 'fluent-booking')
671 + ], 403);
672 + }
673 +
674 + $requestKey = $type === 'tags' ? 'tag_ids' : 'list_ids';
675 + $desired = (array) $request->get($requestKey, []);
676 +
677 + $result = CrmContactService::syncTaxonomy($booking->email, $type, $desired);
678 +
679 + if ($result === null) {
680 + return $this->sendError([
681 + 'message' => __('No CRM contact found for this booking.', 'fluent-booking')
682 + ], 404);
683 + }
684 +
685 + return $this->sendSuccess(array_merge([
686 + 'message' => __('CRM contact updated successfully.', 'fluent-booking'),
687 + ], $result));
496 688 }
497 689
498 690 private function resolveOwnedBookingOrFail($bookingId)
499 691 {