PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / trunk
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution vtrunk
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 1.7.2 All 33 releases
fluent-booking / app / Hooks / Handlers / DataExporter.php

DataExporter.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution trunk, at app/Hooks/Handlers/DataExporter.php

237 lines 8.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\App\Hooks\Handlers;
4
5 use FluentBooking\App\Models\Booking;
6 use FluentBooking\App\Models\Calendar;
7 use FluentBooking\App\Models\Availability;
8 use FluentBooking\App\Services\PermissionManager;
9
10 class DataExporter
11 {
12 public function exportCalendar()
13 {
14 if (!$this->verifyNonce()) {
15 wp_die(esc_html__('Security check failed. Please refresh and try again.', 'fluent-booking'), 403);
16 }
17
18 $calendarId = isset($_REQUEST['calendar_id']) ? (int)$_REQUEST['calendar_id'] : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
19
20 if (!$calendarId) {
21 die(esc_html__('Please provide Calendar ID', 'fluent-booking'));
22 }
23
24 $calendar = Calendar::with(['metas', 'events' => function ($query) {
25 $query->with('event_metas');
26 }])->find($calendarId);
27
28 if (!$calendar) {
29 die(esc_html__('Calendar not found', 'fluent-booking'));
30 }
31
32 if (!PermissionManager::hasCalendarAccess($calendar)) {
33 die(esc_html__('You do not have permission to export data', 'fluent-booking'));
34 }
35
36 $calendarData = $this->prepareCalendarExportData($calendar);
37
38 header('Content-Type: application/json');
39 header('Content-Disposition: attachment; filename=CluentBookingHostExport-' . $calendarId . '.json');
40 echo json_encode($calendarData, JSON_PRETTY_PRINT); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
41 exit();
42 }
43
44 public function exportBookingHosts()
45 {
46 if (!$this->verifyNonce()) {
47 wp_die(esc_html__('Security check failed. Please refresh and try again.', 'fluent-booking'), 403);
48 }
49
50 $groupId = isset($_REQUEST['group_id']) ? (int)$_REQUEST['group_id'] : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
51
52 if (!$groupId) {
53 die(esc_html__('Please provide Group ID', 'fluent-booking'));
54 }
55
56 $attendees = Booking::where('group_id', $groupId)->get();
57
58 if ($attendees->isEmpty()) {
59 die(esc_html__('No bookings found for the provided Group ID', 'fluent-booking'));
60 }
61
62 if (!PermissionManager::userCanSeeAllBookings() && !$attendees->first()->hasBookingAccess()) {
63 die(esc_html__('You do not have permission to export this group\'s attendees', 'fluent-booking'));
64 }
65
66 $csvData[] = [
67 'First Name',
68 'Last Name',
69 'Email',
70 'Message',
71 'Location Details',
72 'Source',
73 'Booking Type',
74 'Status',
75 'Source URL',
76 'Duration',
77 'Start Time',
78 'End Time',
79 'Payment Status',
80 'Payment Order Status',
81 'Payment Method',
82 'Currency',
83 'Total Amount',
84 'Order Created At',
85 'Transaction ID',
86 'Vendor Charge ID',
87 'Transaction Payment Method',
88 'Transaction Status',
89 'Transaction Total',
90 'Transaction Created At',
91 ];
92
93 foreach ($attendees as $attendee) {
94 $row = [
95 $this->sanitizeCsvCell($attendee->first_name),
96 $this->sanitizeCsvCell($attendee->last_name),
97 $this->sanitizeCsvCell($attendee->email),
98 $this->sanitizeCsvCell($attendee->message),
99 $this->sanitizeCsvCell($attendee->getLocationAsText()),
100 $this->sanitizeCsvCell($attendee->source),
101 $this->sanitizeCsvCell($attendee->booking_type),
102 $this->sanitizeCsvCell($attendee->status),
103 $this->sanitizeCsvCell($attendee->source_url),
104 $this->sanitizeCsvCell($attendee->slot_minutes),
105 $this->sanitizeCsvCell($attendee->start_time),
106 $this->sanitizeCsvCell($attendee->end_time),
107 $this->sanitizeCsvCell($attendee->payment_status),
108 ];
109
110 $paymentOrder = $attendee->payment_status ? $attendee->payment_order : null;
111 $row = array_merge($row, $this->buildPaymentColumns($paymentOrder));
112
113 $csvData[] = $row;
114 }
115
116 $csvData = apply_filters('fluent_booking/exporting_booking_data_csv', $csvData, $attendees);
117
118 $output = fopen('php://output', 'w');
119 header('Content-Type: text/csv');
120 header('Content-Disposition: attachment; filename=Booking-Event-Guests-' . $groupId . '.csv');
121
122 foreach ($csvData as $index => $row) {
123 // Sanitize header row cells for consistency (formula-neutralize and strip control chars)
124 if ($index === 0) {
125 $row = array_map([$this, 'sanitizeCsvCell'], $row);
126 }
127 fputcsv($output, $row);
128 }
129
130 fclose($output); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
131 exit();
132 }
133
134 /*
135 * Prepare calendar data for export
136 * @param Calendar|int $calendar Calendar Model or ID
137 * @return array
138 */
139 public function prepareCalendarExportData($calendar = null)
140 {
141 if (is_numeric($calendar)) {
142 $calendar = Calendar::with(['metas', 'events' => function ($query) {
143 $query->with('event_metas');
144 }])->find($calendar);
145 } else if (is_null($calendar)) {
146 $calendar = Calendar::with(['metas', 'events' => function ($query) {
147 $query->with('event_metas');
148 }])->first();
149 }
150
151 if (!$calendar) {
152 return [];
153 }
154
155 $availabilities = [];
156
157 foreach ($calendar->events as $event) {
158 if (isset($availabilities[$event->availability_id])) {
159 continue;
160 }
161 $availability = Availability::find($event->availability_id);
162 if ($availability) {
163 $availabilities[$event->availability_id] = $availability;
164 }
165 }
166
167 $calendarData = $calendar->toArray();
168
169 $calendarData['data_type'] = 'host';
170 $calendarData['availabilities'] = $availabilities;
171
172 $calendarData = apply_filters('fluent_booking/exporting_calendar_data_json', $calendarData, $calendar);
173
174 return $calendarData;
175 }
176
177 private function buildPaymentColumns($order)
178 {
179 if (!$order) {
180 return array_fill(0, 11, '');
181 }
182
183 $order->load(['items', 'transaction']);
184 $trans = $order->transaction;
185
186 return [
187 $this->sanitizeCsvCell($order->status),
188 $this->sanitizeCsvCell($order->payment_method),
189 $this->sanitizeCsvCell($order->currency),
190 $order->total_amount / 100,
191 $this->sanitizeCsvCell($order->created_at),
192 $this->sanitizeCsvCell($trans ? $trans->id : ''),
193 $this->sanitizeCsvCell($trans ? $trans->vendor_charge_id : ''),
194 $this->sanitizeCsvCell($trans ? $trans->payment_method : ''),
195 $this->sanitizeCsvCell($trans ? $trans->status : ''),
196 $trans ? $trans->total / 100 : '',
197 $this->sanitizeCsvCell($trans ? $trans->created_at : ''),
198 ];
199 }
200
201 /**
202 * Sanitize a value for safe CSV output: neutralize formula injection and strip control chars.
203 * Prefix with single quote when value starts with =, +, -, or @ so spreadsheets treat as text.
204 *
205 * @param mixed $value Cell value (string, number, or null).
206 * @return string Safe string for fputcsv.
207 */
208 private function sanitizeCsvCell($value)
209 {
210 if (empty($value)) {
211 return '';
212 }
213 $value = (string) $value;
214 // Strip control characters (ASCII 0-31 except tab, LF, CR).
215 $value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $value);
216 // Neutralize formula injection: prefix with ' so Excel/LibreOffice treat as text.
217 $first = isset($value[0]) ? $value[0] : '';
218 if (in_array($first, ['=', '+', '-', '@'], true)) {
219 $value = "'" . $value;
220 }
221
222 return $value;
223 }
224
225 /**
226 * Verify the request nonce for AJAX actions.
227 *
228 * @return bool
229 */
230 private function verifyNonce()
231 {
232 $nonce = isset($_REQUEST['nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['nonce'])) : '';
233
234 return !empty($nonce) && wp_verify_nonce($nonce, 'fluent-booking');
235 }
236 }
237