PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.13
VikBooking Hotel Booking Engine & PMS v1.8.13
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / admin / helpers / report / istat_ross1000.php

istat_ross1000.php in VikBooking Hotel Booking Engine & PMS 1.8.13, at admin/helpers/report/istat_ross1000.php

2,496 lines 81.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - E4J srl
6 * @copyright Copyright (C) 2024 E4J srl. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 defined('ABSPATH') or die('No script kiddies please!');
12
13 /**
14 * ISTAT ROSS 1000 è valido per diverse regioni. Sicuramente per la Romagna, Veneto e Piemonte.
15 * Sviluppato e mantenuto da GIES (Repubblica di San Marino). Simile a SITRA. Il sistema
16 * supporta diversi URL regionali/cittadini per l'eventuale trasmissione a mezzo WSDL.
17 *
18 * @link Piemonte https://piemontedatiturismo.regione.piemonte.it/ws/checkinV2?wsdl
19 * @link Città Metropolitana di Firenze https://turismo5firenze.regione.toscana.it/ws/checkinV2?wsdl
20 * @link Provincia di Pistoia https://turismo5pistoia.regione.toscana.it/ws/checkinV2?wsdl
21 * @link Provincia di Prato https://turismo5prato.regione.toscana.it/ws/checkinV2?wsdl
22 * @link Abruzzo https://app.regione.abruzzo.it/Turismo5/ws/checkinV2?wsdl
23 * @link Veneto https://flussituristici.regione.veneto.it/ws/checkinV2?wsdl
24 * @link Emilia-Romagna https://datiturismo.regione.emilia-romagna.it/ws/checkinV2?wsdl
25 * @link Marche https://istrice-ross1000.turismo.marche.it/ws/checkinV2?wsdl
26 * @link Lombardia https://www.flussituristici.servizirl.it/Turismo5/app/ws/checkinV2?wsdl
27 * @link Calabria https://sirdat.regione.calabria.it/ws/checkinV2?wsdl
28 * @link Sardegna https://sardegnaturismo.ross1000.it/ws/checkinV2?wsdl
29 * @link Lazio https://lazioturismo.ross1000.it/ws/checkinV2?wsdl
30 *
31 * @link https://web.gies.sm/cc_allegati/ross1000/Tracciato_XML-WEBSERVICE-2.4.pdf
32 *
33 * @since 1.15.4 (J) - 1.5.10 (WP) report introduced.
34 * @since 1.17.2 (J) - 1.7.2 (WP) report refactoring with settings and custom scoped actions.
35 */
36 class VikBookingReportIstatRoss1000 extends VikBookingReport
37 {
38 /**
39 * Property 'defaultKeySort' is used by the View that renders the report.
40 */
41 public $defaultKeySort = 'idbooking';
42
43 /**
44 * Property 'defaultKeyOrder' is used by the View that renders the report.
45 */
46 public $defaultKeyOrder = 'ASC';
47
48 /**
49 * Property 'customExport' is used by the View to display custom export buttons.
50 */
51 public $customExport = '';
52
53 /**
54 * List of municipality and provinces.
55 *
56 * @var array
57 */
58 protected $comuniProvince = [];
59
60 /**
61 * List of country codes.
62 *
63 * @var array
64 */
65 protected $nazioni = [];
66
67 /**
68 * Indicates if no bookings were found for the requested dates.
69 *
70 * @var bool
71 */
72 protected $datesHaveNoBookings = false;
73
74 /**
75 * List of tourism type codes.
76 *
77 * @var array
78 */
79 protected $tourismTypes = [
80 'CULTURALE' => 'Culturale',
81 'BALNEARE' => 'Balneare',
82 'CONGRESSUALE/AFFARI' => 'Congressuale/Affari',
83 'FIERISTICO' => 'Fieristico',
84 'SPORTIVO/FITNESS' => 'Sportivo/Fitness',
85 'SCOLASTICO' => 'Scolastico',
86 'RELIGIOSO' => 'Religioso',
87 'SOCIALE' => 'Sociale',
88 'PARCHI TEMATICI' => 'Parchi Tematici',
89 'TERMALE/TRATTAMENTI SALUTE' => 'Termale/Trattamenti salute',
90 'ENOGASTRONOMICO' => 'Enogastronomico',
91 'CICLOTURISMO' => 'Cicloturismo',
92 'ESCURSIONISTICO/NATURALISTICO' => 'Escursionistico/Naturalistico',
93 'ALTRO MOTIVO' => 'Altro motivo',
94 'NON SPECIFICATO' => 'Non specificato',
95 ];
96
97 /**
98 * List of meanings of transport codes.
99 *
100 * @var array
101 */
102 protected $meaningsOfTransport = [
103 'AUTO' => 'Auto',
104 'AEREO' => 'Aereo',
105 'AEREO+PULLMAN' => 'Aereo+Pullman',
106 'AEREO+NAVETTA/TAXI/AUTO' => 'Aereo+Navetta/Taxi/Auto',
107 'AEREO+TRENO' => 'Aereo+Treno',
108 'TRENO' => 'Treno',
109 'PULLMAN' => 'Pullman',
110 'CARAVAN/AUTOCARAVAN' => 'Caravan/Autocaravan',
111 'BARCA/NAVE/TRAGHETTO' => 'Barca/Nave/Traghetto',
112 'MOTO' => 'Moto',
113 'BICICLETTA' => 'Bicicletta',
114 'A PIEDI' => 'A piedi',
115 'ALTRO MOTIVO' => 'Altro motivo',
116 'NON SPECIFICATO' => 'Non specificato',
117 ];
118
119 /**
120 * List of booking IDs affected by the export.
121 *
122 * @var array
123 */
124 protected $export_booking_ids = [];
125
126 /**
127 * List of exported check-in dates (range).
128 *
129 * @var array
130 */
131 protected $exported_checkin_dates = [];
132
133 /**
134 * Class constructor should define the name of the report and
135 * other vars. Call the parent constructor to define the DB object.
136 */
137 public function __construct()
138 {
139 $this->reportFile = basename(__FILE__, '.php');
140 $this->reportName = JText::translate('ISTAT Ross 1000');
141 $this->reportFilters = [];
142
143 $this->cols = [];
144 $this->rows = [];
145 $this->footerRow = [];
146
147 $this->registerExportFileName();
148
149 parent::__construct();
150 }
151
152 /**
153 * Returns the name of this report.
154 *
155 * @return string
156 */
157 public function getName()
158 {
159 return $this->reportName;
160 }
161
162 /**
163 * Returns the name of this file without .php.
164 *
165 * @return string
166 */
167 public function getFileName()
168 {
169 return $this->reportFile;
170 }
171
172 /**
173 * @inheritDoc
174 *
175 * @since 1.18.0 (J) - 1.8.0 (WP)
176 */
177 public function allowsProfileSettings()
178 {
179 // allow multiple report profile settings
180 return true;
181 }
182
183 /**
184 * @inheritDoc
185 *
186 * @since 1.17.2 (J) - 1.7.2 (WP)
187 */
188 public function getSettingFields()
189 {
190 $example_urls = [
191 [
192 'name' => 'Piemonte',
193 'url' => 'https://piemontedatiturismo.regione.piemonte.it/ws/checkinV2?wsdl',
194 ],
195 [
196 'name' => 'Città Metropolitana di Firenze',
197 'url' => 'https://turismo5firenze.regione.toscana.it/ws/checkinV2?wsdl',
198 ],
199 [
200 'name' => 'Provincia di Pistoia',
201 'url' => 'https://turismo5pistoia.regione.toscana.it/ws/checkinV2?wsdl',
202 ],
203 [
204 'name' => 'Provincia di Prato',
205 'url' => 'https://turismo5prato.regione.toscana.it/ws/checkinV2?wsdl',
206 ],
207 [
208 'name' => 'Abruzzo',
209 'url' => 'https://app.regione.abruzzo.it/Turismo5/ws/checkinV2?wsdl',
210 ],
211 [
212 'name' => 'Veneto',
213 'url' => 'https://flussituristici.regione.veneto.it/ws/checkinV2?wsdl',
214 ],
215 [
216 'name' => 'Emilia-Romagna',
217 'url' => 'https://datiturismo.regione.emilia-romagna.it/ws/checkinV2?wsdl',
218 ],
219 [
220 'name' => 'Marche',
221 'url' => 'https://istrice-ross1000.turismo.marche.it/ws/checkinV2?wsdl',
222 ],
223 [
224 'name' => 'Lombardia',
225 'url' => 'https://www.flussituristici.servizirl.it/Turismo5/app/ws/checkinV2?wsdl',
226 ],
227 [
228 'name' => 'Calabria',
229 'url' => 'https://sirdat.regione.calabria.it/ws/checkinV2?wsdl',
230 ],
231 [
232 'name' => 'Sardegna',
233 'url' => 'https://sardegnaturismo.ross1000.it/ws/checkinV2?wsdl',
234 ],
235 [
236 'name' => 'Liguria',
237 'url' => 'https://turismows.regione.liguria.it/ws/checkinV2?wsdl',
238 ],
239 [
240 'name' => 'Lazio',
241 'url' => 'https://lazioturismo.ross1000.it/ws/checkinV2?wsdl',
242 ],
243 ];
244
245 $example_urls_str = '';
246 foreach ($example_urls as $example_url) {
247 $example_urls_str .= '<li><strong>' . $example_url['name'] . '</strong>: ' . $example_url['url'] . '</li>' . "\n";
248 }
249
250 // count room units
251 $tot_room_units = $this->countRooms();
252
253 return [
254 'title' => [
255 'type' => 'custom',
256 'label' => '',
257 'html' => '<p class="info">Configura le impostazioni per la generazione dei tracciati record XML e per la trasmissione delle informazioni verso il WebService della tua regione o provincia.</p>',
258 ],
259 'codstru' => [
260 'type' => 'text',
261 'label' => 'Codice Struttura',
262 'help' => 'Codice univoco di identificazione della tua struttura assegnato dall\'Amministrazione competente.',
263 ],
264 'numcamere' => [
265 'type' => 'number',
266 'label' => 'Numero camere disponibili',
267 'help' => 'Numero totale di camere disponibili.',
268 'min' => 1,
269 'default' => $tot_room_units,
270 ],
271 'numletti' => [
272 'type' => 'number',
273 'label' => 'Numero letti disponibili',
274 'help' => 'Numero totale di letti disponibili.',
275 'min' => 0,
276 'default' => $tot_room_units,
277 ],
278 'user' => [
279 'type' => 'text',
280 'label' => 'Utente',
281 'help' => 'Nome utente assegnato dall\'Ufficio Turismo di competenza.',
282 ],
283 'pwd' => [
284 'type' => 'password',
285 'label' => 'Password',
286 'help' => 'Password assegnata dall\'Ufficio Turismo di competenza.',
287 ],
288 'endpoint' => [
289 'type' => 'text',
290 'label' => 'WS Endpoint URL',
291 'help' => 'Indirizzo URL web-service del sito web dell\'ente al quale trasmettere i dati. È suggerito consultare anche la lista di endpoint sottostante.',
292 ],
293 'exampleurls' => [
294 'type' => 'custom',
295 'label' => '',
296 'html' => '<p class="info">Lista Endpoint web-service:</p><ul>' . $example_urls_str . '</ul><p class="warn">È consigliato verificare tramite il proprio portale ISTAT regionale l\'effettivo URL del Web-Service da utilizzare per la trasmissione dei flussi turistici. In alcuni casi l\'indirizzo URL potrebbe non necessitare della parte finale <code>?wsdl</code>.</p>',
297 ],
298 ];
299 }
300
301 /**
302 * @inheritDoc
303 *
304 * @since 1.17.2 (J) - 1.7.2 (WP)
305 */
306 public function getScopedActions($scope = null, $visible = true)
307 {
308 // list of custom actions for this report
309 $actions = [
310 [
311 'id' => 'transmitRecords',
312 'name' => 'Trasmetti flussi turistici',
313 'help' => 'Trasmette i dati riguardanti i flussi turistici.',
314 'icon' => VikBookingIcons::i('cloud-upload-alt'),
315 // flag to indicate that it requires the report data
316 'export_data' => true,
317 'scopes' => [
318 'web',
319 'cron',
320 ],
321 ],
322 ];
323
324 // filter actions by scope
325 if ($scope && (!strcasecmp($scope, 'cron') || !strcasecmp($scope, 'web'))) {
326 $actions = array_filter($actions, function($action) use ($scope) {
327 if (!($action['scopes'] ?? [])) {
328 return true;
329 }
330
331 return in_array(strtolower($scope), $action['scopes']);
332 });
333 }
334
335 // filter by visibility
336 if ($visible) {
337 $actions = array_filter($actions, function($action) {
338 return !($action['hidden'] ?? false);
339 });
340 }
341
342 return array_values($actions);
343 }
344
345 /**
346 * Returns the filters of this report.
347 *
348 * @return array
349 */
350 public function getFilters()
351 {
352 if ($this->reportFilters) {
353 //do not run this method twice, as it could load JS and CSS files.
354 return $this->reportFilters;
355 }
356
357 $app = JFactory::getApplication();
358
359 //get VBO Application Object
360 $vbo_app = VikBooking::getVboApplication();
361
362 // get the possibly injected report options
363 $options = $this->getReportOptions();
364
365 //load the jQuery UI Datepicker
366 $this->loadDatePicker();
367
368 //custom export button
369 $this->customExport = '<a href="JavaScript: void(0);" onclick="vboDownloadSchedaIstat();" class="vbcsvexport"><i class="'.VikBookingIcons::i('download').'"></i> <span>Download File</span></a>';
370
371 // build the hidden values for the selection of Comuni & Province and much more.
372 $this->comuniProvince = $this->loadComuniProvince();
373 $this->nazioni = $this->loadNazioni();
374
375 // open hidden fields
376 $hidden_vals = '<div id="vbo-report-ross1000-hidden" style="display: none;">';
377
378 // build params container HTML structure
379 $hidden_vals .= '<div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact">';
380 $hidden_vals .= ' <div class="vbo-params-wrap">';
381 $hidden_vals .= ' <div class="vbo-params-container">';
382 $hidden_vals .= ' <div class="vbo-params-block vbo-params-block-noborder">';
383
384 // comuni
385 $hidden_vals .= ' <div id="vbo-report-ross1000-comune" class="vbo-report-ross1000-selcont vbo-param-container" style="display: none;">';
386 $hidden_vals .= ' <div class="vbo-param-label">Comune</div>';
387 $hidden_vals .= ' <div class="vbo-param-setting">';
388 $hidden_vals .= ' <select id="choose-comune" onchange="vboReportChosenComune(this);">';
389 $hidden_vals .= ' <option value=""></option>'."\n";
390 $hidden_vals .= ' <option value="ES">- Estero -</option>'."\n";
391 foreach ($this->comuniProvince['comuni'] ?? [] as $code => $comune) {
392 if (empty($code)) {
393 continue;
394 }
395 $hidden_vals .= ' <option value="' . $code . '">' . (is_array($comune) ? $comune['name'] : '') . '</option>'."\n";
396 }
397 $hidden_vals .= ' </select>';
398 $hidden_vals .= ' </div>';
399 $hidden_vals .= ' </div>';
400
401 // mezzi di trasporto
402 $hidden_vals .= ' <div id="vbo-report-trasporto" class="vbo-report-ross1000-selcont vbo-param-container" style="display: none;">';
403 $hidden_vals .= ' <div class="vbo-param-label">Mezzo di trasporto</div>';
404 $hidden_vals .= ' <div class="vbo-param-setting">';
405 $hidden_vals .= ' <select id="choose-trasporto" onchange="vboReportChosenTrasporto(this);"><option value=""></option>';
406 foreach ($this->meaningsOfTransport as $key => $value) {
407 $hidden_vals .= ' <option value="' . $value . '">' . $value . '</option>'."\n";
408 }
409 $hidden_vals .= ' </select>';
410 $hidden_vals .= ' </div>';
411 $hidden_vals .= ' </div>';
412
413 // tipo di turismo
414 $hidden_vals .= ' <div id="vbo-report-turismo" class="vbo-report-ross1000-selcont vbo-param-container" style="display: none;">';
415 $hidden_vals .= ' <div class="vbo-param-label">Tipo di turismo</div>';
416 $hidden_vals .= ' <div class="vbo-param-setting">';
417 $hidden_vals .= ' <select id="choose-turismo" onchange="vboReportChosenTurismo(this);"><option value=""></option>';
418 foreach ($this->tourismTypes as $key => $value) {
419 $hidden_vals .= ' <option value="' . $value . '">' . $value . '</option>'."\n";
420 }
421 $hidden_vals .= ' </select>';
422 $hidden_vals .= ' </div>';
423 $hidden_vals .= ' </div>';
424
425 // province
426 $hidden_vals .= ' <div id="vbo-report-ross1000-provincia" class="vbo-report-ross1000-selcont vbo-param-container" style="display: none;">';
427 $hidden_vals .= ' <div class="vbo-param-label">Provincia</div>';
428 $hidden_vals .= ' <div class="vbo-param-setting">';
429 $hidden_vals .= ' <select id="choose-provincia" onchange="vboReportChosenProvincia(this);"><option value=""></option>';
430 foreach ($this->comuniProvince['province'] ?? [] as $code => $provincia) {
431 $hidden_vals .= ' <option value="' . $code . '">' . $provincia . '</option>'."\n";
432 }
433 $hidden_vals .= ' </select>';
434 $hidden_vals .= ' </div>';
435 $hidden_vals .= ' </div>';
436
437 // nazioni
438 $hidden_vals .= ' <div id="vbo-report-ross1000-nazione" class="vbo-report-ross1000-selcont vbo-param-container" style="display: none;">';
439 $hidden_vals .= ' <div class="vbo-param-label">Paese</div>';
440 $hidden_vals .= ' <div class="vbo-param-setting">';
441 $hidden_vals .= ' <select id="choose-nazione" onchange="vboReportChosenNazione(this);"><option value=""></option>';
442 foreach ($this->nazioni as $code => $nazione) {
443 $hidden_vals .= ' <option value="' . $code . '">' . $nazione['name'] . '</option>';
444 }
445 $hidden_vals .= ' </select>';
446 $hidden_vals .= ' </div>';
447 $hidden_vals .= ' </div>';
448
449 // sesso
450 $hidden_vals .= ' <div id="vbo-report-ross1000-sesso" class="vbo-report-ross1000-selcont vbo-param-container" style="display: none;">';
451 $hidden_vals .= ' <div class="vbo-param-label">Sesso</div>';
452 $hidden_vals .= ' <div class="vbo-param-setting">';
453 $hidden_vals .= ' <select id="choose-sesso" onchange="vboReportChosenSesso(this);"><option value=""></option>';
454 $sessos = [
455 1 => 'M',
456 2 => 'F'
457 ];
458 foreach ($sessos as $code => $ses) {
459 $hidden_vals .= ' <option value="'.$code.'">'.$ses.'</option>'."\n";
460 }
461 $hidden_vals .= ' </select>';
462 $hidden_vals .= ' </div>';
463 $hidden_vals .= ' </div>';
464
465 // data di nascita
466 $hidden_vals .= ' <div id="vbo-report-ross1000-dbirth" class="vbo-report-ross1000-selcont vbo-param-container" style="display: none;">';
467 $hidden_vals .= ' <div class="vbo-param-label">Data di nascita</div>';
468 $hidden_vals .= ' <div class="vbo-param-setting">';
469 $hidden_vals .= ' <input type="text" size="40" id="choose-dbirth" value="" />';
470 $hidden_vals .= ' </div>';
471 $hidden_vals .= ' </div>';
472
473 // close params container HTML structure
474 $hidden_vals .= ' </div>';
475 $hidden_vals .= ' </div>';
476 $hidden_vals .= ' </div>';
477 $hidden_vals .= '</div>';
478
479 // close hidden fields
480 $hidden_vals .= '</div>';
481
482 //From Date Filter (with hidden values for the dropdown menus of Comuni, Province, Stati etc..)
483 $filter_opt = array(
484 'label' => '<label for="fromdate">'.JText::translate('VBOREPORTSDATEFROM').'</label>',
485 'html' => '<input type="text" id="fromdate" name="fromdate" value="" class="vbo-report-datepicker vbo-report-datepicker-from" />'.$hidden_vals,
486 'type' => 'calendar',
487 'name' => 'fromdate'
488 );
489 array_push($this->reportFilters, $filter_opt);
490
491 // To Date Filter
492 $filter_opt = array(
493 'label' => '<label for="todate">'.JText::translate('VBOREPORTSDATETO').'</label>',
494 'html' => '<input type="text" id="todate" name="todate" value="" class="vbo-report-datepicker vbo-report-datepicker-to" />',
495 'type' => 'calendar',
496 'name' => 'todate'
497 );
498 array_push($this->reportFilters, $filter_opt);
499
500 // Listings Filter
501 $filter_opt = array(
502 'label' => '<label for="listingsfilt">' . JText::translate('VBO_LISTINGS') . '</label>',
503 'html' => '<span class="vbo-toolbar-multiselect-wrap">' . $vbo_app->renderElementsDropDown([
504 'id' => 'listingsfilt',
505 'elements' => 'listings',
506 'placeholder' => JText::translate('VBO_LISTINGS'),
507 'allow_clear' => 1,
508 'attributes' => [
509 'name' => 'listings[]',
510 'multiple' => 'multiple',
511 ],
512 'selected_values' => (array) ($app->input->get('listings', [], 'array') ?: $options->get('listings', [])),
513 ]) . '</span>',
514 'type' => 'select',
515 'multiple' => true,
516 'name' => 'listings',
517 );
518 array_push($this->reportFilters, $filter_opt);
519
520 // append button to save the data when creating manual values
521 $filter_opt = array(
522 'label' => '<label class="vbo-report-ross1000-manualsave" style="display: none;">' . JText::translate('VBOGUESTSDETAILS') . '</label>',
523 'html' => '<button type="button" class="btn vbo-config-btn vbo-report-ross1000-manualsave" style="display: none;" onclick="vboRoss1000SaveData();"><i class="' . VikBookingIcons::i('save') . '"></i> ' . JText::translate('VBSAVE') . '</button>',
524 );
525 array_push($this->reportFilters, $filter_opt);
526
527 // jQuery code for the datepicker calendars, select2 and triggers for the dropdown menus
528 $pfromdate = VikRequest::getString('fromdate', '', 'request');
529 $ptodate = VikRequest::getString('todate', '', 'request');
530
531 $js_ajax_base = VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=invoke_report&report=' . $this->reportFile);
532 $js_save_icn = VikBookingIcons::i('save');
533 $js_saving_icn = VikBookingIcons::i('circle-notch', 'fa-spin fa-fw');
534 $js_saved_icn = VikBookingIcons::i('check-circle');
535
536 $js = 'var reportActiveCell = null, reportObj = {};
537 var vbo_report_js_ajax_base = "' . $js_ajax_base . '";
538 var vbo_ross1000_save_icn = "' . $js_save_icn . '";
539 var vbo_ross1000_saving_icn = "' . $js_saving_icn . '";
540 var vbo_ross1000_saved_icn = "' . $js_saved_icn . '";
541 jQuery(function() {
542 // prepare main filters
543 jQuery(".vbo-report-datepicker:input").datepicker({
544 maxDate: "+1m",
545 dateFormat: "'.$this->getDateFormat('jui').'",
546 onSelect: vboReportCheckDates
547 });
548 '.(!empty($pfromdate) ? 'jQuery(".vbo-report-datepicker-from").datepicker("setDate", "'.$pfromdate.'");' : '').'
549 '.(!empty($ptodate) ? 'jQuery(".vbo-report-datepicker-to").datepicker("setDate", "'.$ptodate.'");' : '').'
550 // prepare filler helpers
551 jQuery("#vbo-report-ross1000-hidden").children().detach().appendTo(".vbo-info-overlay-report");
552 jQuery("#choose-comune").select2({placeholder: "- Seleziona un Comune -", width: "200px"});
553 jQuery("#choose-provincia").select2({placeholder: "- Seleziona una Provincia -", width: "200px"});
554 jQuery("#choose-nazione").select2({placeholder: "- Seleziona una Nazione -", width: "200px"});
555 jQuery("#choose-sesso").select2({placeholder: "- Seleziona Sesso -", width: "200px"});
556 jQuery("#choose-turismo").select2({placeholder: "- Seleziona Tipo di Turismo -", width: "300px"});
557 jQuery("#choose-trasporto").select2({placeholder: "- Seleziona Mezzo di Trasporto -", width: "300px"});
558
559 jQuery("#choose-dbirth").datepicker({
560 maxDate: 0,
561 dateFormat: "dd/mm/yy",
562 changeMonth: true,
563 changeYear: true,
564 yearRange: "'.(date('Y') - 100).':'.date('Y').'"
565 });
566 // click events
567 jQuery(".vbo-report-load-comune").click(function() {
568 reportActiveCell = this;
569 jQuery(".vbo-report-ross1000-selcont").hide();
570 jQuery("#vbo-report-ross1000-comune").show();
571 vboShowOverlay({
572 title: "Compila informazioni",
573 extra_class: "vbo-modal-rounded vbo-modal-dialog",
574 });
575 });
576 jQuery(".vbo-report-load-provincia").click(function() {
577 reportActiveCell = this;
578 jQuery(".vbo-report-ross1000-selcont").hide();
579 jQuery("#vbo-report-ross1000-provincia").show();
580 vboShowOverlay({
581 title: "Compila informazioni",
582 extra_class: "vbo-modal-rounded vbo-modal-dialog",
583 });
584 });
585 jQuery(".vbo-report-load-nazione, .vbo-report-load-cittadinanza").click(function() {
586 reportActiveCell = this;
587 jQuery(".vbo-report-ross1000-selcont").hide();
588 jQuery("#vbo-report-ross1000-nazione").show();
589 vboShowOverlay();
590 });
591 jQuery(".vbo-report-load-sesso").click(function() {
592 reportActiveCell = this;
593 jQuery(".vbo-report-ross1000-selcont").hide();
594 jQuery("#vbo-report-ross1000-sesso").show();
595 vboShowOverlay({
596 title: "Compila informazioni",
597 extra_class: "vbo-modal-rounded vbo-modal-dialog vbo-modal-nofooter",
598 });
599 });
600 jQuery(".vbo-report-load-turismo").click(function() {
601 reportActiveCell = this;
602 jQuery(".vbo-report-ross1000-selcont").hide();
603 jQuery("#vbo-report-turismo").show();
604 vboShowOverlay({
605 title: "Compila informazioni",
606 extra_class: "vbo-modal-rounded vbo-modal-dialog",
607 });
608 });
609 jQuery(".vbo-report-load-trasporto").click(function() {
610 reportActiveCell = this;
611 jQuery(".vbo-report-ross1000-selcont").hide();
612 jQuery("#vbo-report-trasporto").show();
613 vboShowOverlay({
614 title: "Compila informazioni",
615 extra_class: "vbo-modal-rounded vbo-modal-dialog",
616 });
617 });
618 jQuery(".vbo-report-load-dbirth").click(function() {
619 reportActiveCell = this;
620 jQuery(".vbo-report-ross1000-selcont").hide();
621 jQuery("#vbo-report-ross1000-dbirth").show();
622 vboShowOverlay({
623 title: "Compila informazioni",
624 extra_class: "vbo-modal-rounded vbo-modal-dialog",
625 footer_right: "<button type=\"button\" class=\"btn btn-success\" onclick=\"vboReportChosenDbirth(document.getElementById(\'choose-dbirth\').value);\">Applica</button>",
626 });
627 });
628 });
629
630 function vboReportCheckDates(selectedDate, inst) {
631 if (selectedDate === null || inst === null) {
632 return;
633 }
634 var cur_from_date = jQuery(this).val();
635 if (jQuery(this).hasClass("vbo-report-datepicker-from") && cur_from_date.length) {
636 var nowstart = jQuery(this).datepicker("getDate");
637 var nowstartdate = new Date(nowstart.getTime());
638 jQuery(".vbo-report-datepicker-to").datepicker("option", {minDate: nowstartdate});
639 }
640 }
641
642 function vboReportChosenComune(comune) {
643 var c_code = comune.value;
644 var c_val = comune.options[comune.selectedIndex].text;
645 if (reportActiveCell !== null) {
646 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
647 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
648 alert("Error, cannot find element to update.");
649 } else {
650 var rep_act_cell = jQuery(reportActiveCell);
651 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
652 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
653 if (!reportObj.hasOwnProperty(nowindex)) {
654 reportObj[nowindex] = {
655 bid: rep_guest_bid,
656 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
657 };
658 }
659 if (jQuery(reportActiveCell).hasClass("vbo-report-load-docplace")) {
660 reportObj[nowindex]["docplace"] = c_code;
661 } else {
662 reportObj[nowindex]["comune_s"] = c_code;
663 }
664 }
665 }
666 reportActiveCell = null;
667 vboHideOverlay();
668 jQuery("#choose-comune").val("").select2("data", null, false);
669 jQuery(".vbo-report-ross1000-manualsave").show();
670 }
671
672 function vboReportChosenProvincia(prov) {
673 var c_code = prov.value;
674 var c_val = prov.options[prov.selectedIndex].text;
675 if (reportActiveCell !== null) {
676 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
677 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
678 alert("Error, cannot find element to update.");
679 } else {
680 var rep_act_cell = jQuery(reportActiveCell);
681 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
682 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
683 if (!reportObj.hasOwnProperty(nowindex)) {
684 reportObj[nowindex] = {
685 bid: rep_guest_bid,
686 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
687 };
688 }
689 reportObj[nowindex]["comune_s"] = c_code;
690 }
691 }
692 reportActiveCell = null;
693 vboHideOverlay();
694 jQuery("#choose-provincia").val("").select2("data", null, false);
695 jQuery(".vbo-report-ross1000-manualsave").show();
696 }
697
698 function vboReportChosenNazione(naz) {
699 var c_code = naz.value;
700 var c_val = naz.options[naz.selectedIndex].text;
701 if (reportActiveCell !== null) {
702 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
703 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
704 alert("Error, cannot find element to update.");
705 } else {
706 var rep_act_cell = jQuery(reportActiveCell);
707 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
708 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
709 if (!reportObj.hasOwnProperty(nowindex)) {
710 reportObj[nowindex] = {
711 bid: rep_guest_bid,
712 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
713 };
714 }
715 if (jQuery(reportActiveCell).hasClass("vbo-report-load-nazione")) {
716 reportObj[nowindex]["country_s"] = c_code;
717 } else if (jQuery(reportActiveCell).hasClass("vbo-report-load-docplace")) {
718 reportObj[nowindex]["docplace"] = c_code;
719 } else {
720 reportObj[nowindex]["country_c"] = c_code;
721 }
722 }
723 }
724 reportActiveCell = null;
725 vboHideOverlay();
726 jQuery("#choose-nazione").val("").select2("data", null, false);
727 jQuery(".vbo-report-ross1000-manualsave").show();
728 }
729
730 function vboReportChosenSesso(sesso) {
731 var c_code = sesso.value;
732 var c_val = sesso.options[sesso.selectedIndex].text;
733 if (reportActiveCell !== null) {
734 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
735 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
736 alert("Error, cannot find element to update.");
737 } else {
738 var rep_act_cell = jQuery(reportActiveCell);
739 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
740 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
741 if (!reportObj.hasOwnProperty(nowindex)) {
742 reportObj[nowindex] = {
743 bid: rep_guest_bid,
744 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
745 };
746 }
747 reportObj[nowindex]["gender"] = c_code;
748 }
749 }
750 reportActiveCell = null;
751 vboHideOverlay();
752 jQuery("#choose-sesso").val("").select2("data", null, false);
753 jQuery(".vbo-report-ross1000-manualsave").show();
754 }
755
756 function vboReportChosenTrasporto(trasporto) {
757 var c_code = trasporto.value;
758 var c_val = trasporto.options[trasporto.selectedIndex].text;
759 if (reportActiveCell !== null) {
760 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
761 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
762 alert("Error, cannot find element to update.");
763 } else {
764 var rep_act_cell = jQuery(reportActiveCell);
765 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
766 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
767 if (!reportObj.hasOwnProperty(nowindex)) {
768 reportObj[nowindex] = {
769 bid: rep_guest_bid,
770 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
771 };
772 }
773 reportObj[nowindex]["mezzo"] = c_code;
774 }
775 }
776 reportActiveCell = null;
777 vboHideOverlay();
778 jQuery("#choose-trasporto").val("").select2("data", null, false);
779 jQuery(".vbo-report-ross1000-manualsave").show();
780 }
781
782 function vboReportChosenTurismo(turismo) {
783 var c_code = turismo.value;
784 var c_val = turismo.options[turismo.selectedIndex].text;
785 if (reportActiveCell !== null) {
786 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
787 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
788 alert("Error, cannot find element to update.");
789 } else {
790 var rep_act_cell = jQuery(reportActiveCell);
791 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
792 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
793 if (!reportObj.hasOwnProperty(nowindex)) {
794 reportObj[nowindex] = {
795 bid: rep_guest_bid,
796 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
797 };
798 }
799 reportObj[nowindex]["turismo"] = c_code;
800 }
801 }
802 reportActiveCell = null;
803 vboHideOverlay();
804 jQuery("#choose-turismo").val("").select2("data", null, false);
805 jQuery(".vbo-report-ross1000-manualsave").show();
806 }
807
808 function vboReportChosenDbirth(val) {
809 var c_code = val, c_val = val;
810 if (reportActiveCell !== null) {
811 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
812 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
813 alert("Error, cannot find element to update.");
814 } else {
815 var rep_act_cell = jQuery(reportActiveCell);
816 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
817 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
818 if (!reportObj.hasOwnProperty(nowindex)) {
819 reportObj[nowindex] = {
820 bid: rep_guest_bid,
821 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
822 };
823 }
824 reportObj[nowindex]["date_birth"] = c_code;
825 }
826 }
827 reportActiveCell = null;
828 vboHideOverlay();
829 jQuery("#choose-dbirth").val("");
830 jQuery(".vbo-report-ross1000-manualsave").show();
831 }
832
833 // download function
834 function vboDownloadSchedaIstat(type) {
835 if (!confirm("Sei sicuro di aver compilato tutti i dati?")) {
836 return false;
837 }
838
839 let use_blank = true;
840 if (typeof type === "undefined") {
841 type = 1;
842 } else {
843 use_blank = false;
844 }
845
846 if (use_blank) {
847 document.adminForm.target = "_blank";
848 document.adminForm.action += "&tmpl=component";
849 }
850
851 vboSetFilters({exportreport: type, filler: JSON.stringify(reportObj)}, true);
852
853 setTimeout(function() {
854 document.adminForm.target = "";
855 document.adminForm.action = document.adminForm.action.replace("&tmpl=component", "");
856 vboSetFilters({exportreport: "0", filler: ""}, false);
857 }, 1000);
858 }
859
860 // save data after manual fillers
861 function vboRoss1000SaveData() {
862 jQuery("button.vbo-report-ross1000-manualsave").find("i").attr("class", vbo_ross1000_saving_icn);
863 VBOCore.doAjax(
864 vbo_report_js_ajax_base,
865 {
866 call: "updatePaxData",
867 params: reportObj,
868 tmpl: "component"
869 },
870 (response) => {
871 if (!response || !response[0]) {
872 alert("An error occurred.");
873 return false;
874 }
875 jQuery("button.vbo-report-ross1000-manualsave").addClass("btn-success").find("i").attr("class", vbo_ross1000_saved_icn);
876 },
877 (error) => {
878 alert(error.responseText);
879 jQuery("button.vbo-report-ross1000-manualsave").removeClass("btn-success").find("i").attr("class", vbo_ross1000_save_icn);
880 }
881 );
882 }';
883 $this->setScript($js);
884
885 return $this->reportFilters;
886 }
887
888 /**
889 * Loads the report data from the DB.
890 * Returns true in case of success, false otherwise.
891 * Sets the columns and rows for the report to be displayed.
892 *
893 * @return boolean
894 */
895 public function getReportData()
896 {
897 // always reset no bookings detection flag
898 $this->datesHaveNoBookings = false;
899
900 if ($this->getError()) {
901 // export functions may set errors rather than exiting the process, and
902 // the View may continue the execution to attempt to render the report.
903 return false;
904 }
905
906 if ($this->rows) {
907 // method must have run already
908 return true;
909 }
910
911 // get the possibly injected report options
912 $options = $this->getReportOptions();
913
914 // injected options will replace request variables, if any
915 $opt_fromdate = $options->get('fromdate', '');
916 $opt_todate = $options->get('todate', '');
917
918 // input fields and other vars
919 $pfromdate = $opt_fromdate ?: VikRequest::getString('fromdate', '', 'request');
920 $ptodate = $opt_todate ?: VikRequest::getString('todate', '', 'request');
921
922 $pkrsort = VikRequest::getString('krsort', $this->defaultKeySort, 'request');
923 $pkrsort = empty($pkrsort) ? $this->defaultKeySort : $pkrsort;
924 $pkrorder = VikRequest::getString('krorder', $this->defaultKeyOrder, 'request');
925 $pkrorder = empty($pkrorder) ? $this->defaultKeyOrder : $pkrorder;
926 $pkrorder = $pkrorder == 'DESC' ? 'DESC' : 'ASC';
927 $plistings = ((array) VikRequest::getVar('listings', array())) ?: ((array) $options->get('listings', []));
928 $plistings = array_filter(array_map('intval', $plistings));
929
930 $currency_symb = VikBooking::getCurrencySymb();
931 $df = $this->getDateFormat();
932 $datesep = VikBooking::getDateSeparator();
933 if (empty($ptodate)) {
934 $ptodate = $pfromdate;
935 }
936
937 // get date timestamps
938 $from_ts = VikBooking::getDateTimestamp($pfromdate, 0, 0);
939 $to_ts = VikBooking::getDateTimestamp($ptodate, 23, 59, 59);
940 if (empty($pfromdate) || empty($from_ts) || empty($to_ts) || $from_ts > $to_ts) {
941 $this->setError(JText::translate('VBOREPORTSERRNODATES'));
942 return false;
943 }
944
945 // set the dates being exported
946 $this->exported_checkin_dates = [
947 date('Y-m-d', $from_ts),
948 date('Y-m-d', $to_ts),
949 ];
950
951 // load settings
952 $settings = $this->loadSettings();
953
954 if (empty($settings['codstru'])) {
955 $this->setError('Inserisci il codice della tua Struttura dalle impostazioni.<br/>Si tratta di un codice univoco di identificazione che ti viene assegnato dall\'Amministrazione competente.');
956 return false;
957 }
958
959 // query to obtain the records (all check-ins, check-outs and reservations created within the dates filter)
960 $q = $this->dbo->getQuery(true)
961 ->select([
962 $this->dbo->qn('o.id'),
963 $this->dbo->qn('o.custdata'),
964 $this->dbo->qn('o.ts'),
965 $this->dbo->qn('o.days'),
966 $this->dbo->qn('o.checkin'),
967 $this->dbo->qn('o.checkout'),
968 $this->dbo->qn('o.totpaid'),
969 $this->dbo->qn('o.roomsnum'),
970 $this->dbo->qn('o.total'),
971 $this->dbo->qn('o.idorderota'),
972 $this->dbo->qn('o.channel'),
973 $this->dbo->qn('o.country'),
974 $this->dbo->qn('o.custmail'),
975 $this->dbo->qn('o.phone'),
976 $this->dbo->qn('or.idorder'),
977 $this->dbo->qn('or.idroom'),
978 $this->dbo->qn('or.adults'),
979 $this->dbo->qn('or.children'),
980 $this->dbo->qn('or.t_first_name'),
981 $this->dbo->qn('or.t_last_name'),
982 $this->dbo->qn('or.cust_cost'),
983 $this->dbo->qn('or.cust_idiva'),
984 $this->dbo->qn('or.extracosts'),
985 $this->dbo->qn('or.room_cost'),
986 $this->dbo->qn('co.idcustomer'),
987 $this->dbo->qn('co.pax_data'),
988 $this->dbo->qn('c.first_name'),
989 $this->dbo->qn('c.last_name'),
990 $this->dbo->qn('c.country', 'customer_country'),
991 $this->dbo->qn('c.address'),
992 $this->dbo->qn('c.doctype'),
993 $this->dbo->qn('c.docnum'),
994 $this->dbo->qn('c.gender'),
995 $this->dbo->qn('c.bdate'),
996 $this->dbo->qn('c.pbirth'),
997 ])
998 ->from($this->dbo->qn('#__vikbooking_orders', 'o'))
999 ->leftJoin($this->dbo->qn('#__vikbooking_ordersrooms', 'or') . ' ON ' . $this->dbo->qn('or.idorder') . ' = ' . $this->dbo->qn('o.id'))
1000 ->leftJoin($this->dbo->qn('#__vikbooking_customers_orders', 'co') . ' ON ' . $this->dbo->qn('co.idorder') . ' = ' . $this->dbo->qn('o.id'))
1001 ->leftJoin($this->dbo->qn('#__vikbooking_customers', 'c') . ' ON ' . $this->dbo->qn('c.id') . ' = ' . $this->dbo->qn('co.idcustomer'))
1002 ->where($this->dbo->qn('o.status') . ' = ' . $this->dbo->q('confirmed'))
1003 ->where($this->dbo->qn('o.closure') . ' = 0')
1004 // fetch all bookings with check-in, check-out or reservation date within date filters
1005 ->andWhere([
1006 '(' . $this->dbo->qn('o.checkin') . ' BETWEEN ' . $from_ts . ' AND ' . $to_ts . ')',
1007 '(' . $this->dbo->qn('o.checkout') . ' BETWEEN ' . $from_ts . ' AND ' . $to_ts . ')',
1008 '(' . $this->dbo->qn('o.ts') . ' BETWEEN ' . $from_ts . ' AND ' . $to_ts . ')',
1009 ], 'OR')
1010 ->order($this->dbo->qn('o.checkin') . ' ASC')
1011 ->order($this->dbo->qn('o.id') . ' ASC')
1012 ->order($this->dbo->qn('or.id') . ' ASC');
1013
1014 if ($plistings) {
1015 $q->where($this->dbo->qn('or.idroom') . ' IN (' . implode(', ', $plistings) . ')');
1016 }
1017
1018 $this->dbo->setQuery($q);
1019 $records = $this->dbo->loadAssocList();
1020 if (!$records) {
1021 // set error message
1022 $this->setError(JText::translate('VBOREPORTSERRNORESERV'));
1023 $this->setError('Nessun check-in, check-out o prenotazione nelle date selezionate.');
1024
1025 // set no bookings detection flag
1026 $this->datesHaveNoBookings = true;
1027
1028 // abort
1029 return false;
1030 }
1031
1032 // nest records with multiple rooms booked inside sub-array
1033 $bookings = [];
1034 foreach ($records as $v) {
1035 if (!isset($bookings[$v['id']])) {
1036 $bookings[$v['id']] = [];
1037 }
1038 array_push($bookings[$v['id']], $v);
1039 }
1040
1041 // define the columns of the report
1042 $this->cols = array(
1043 // booking code idswh for the PMS software
1044 array(
1045 'key' => 'idswh',
1046 'label' => 'IDSWH',
1047 'tip' => 'Codice identificativo del movimento. Generato automaticamente dal sistema.',
1048 ),
1049 // booking id (this is not a duplicate value of "idbooking", it should go before "resdate", "checkin" and "checkout")
1050 array(
1051 'key' => 'booking_id',
1052 'label' => 'ID',
1053 // hide this field in the View
1054 'ignore_view' => 1,
1055 ),
1056 // reservation date
1057 array(
1058 'key' => 'resdate',
1059 'attr' => array(
1060 'class="center"'
1061 ),
1062 'label' => JText::translate('VBPCHOOSEBUSYORDATE'),
1063 ),
1064 // check-in
1065 array(
1066 'key' => 'checkin',
1067 'attr' => array(
1068 'class="center"'
1069 ),
1070 'label' => JText::translate('VBPICKUPAT'),
1071 ),
1072 // checkout
1073 array(
1074 'key' => 'checkout',
1075 'attr' => array(
1076 'class="center"'
1077 ),
1078 'label' => JText::translate('VBRELEASEAT'),
1079 ),
1080 // nome
1081 array(
1082 'key' => 'nome',
1083 'label' => JText::translate('VBTRAVELERNAME'),
1084 ),
1085 // cognome
1086 array(
1087 'key' => 'cognome',
1088 'label' => JText::translate('VBTRAVELERLNAME'),
1089 ),
1090 // sesso
1091 array(
1092 'key' => 'gender',
1093 'attr' => array(
1094 'class="center"'
1095 ),
1096 'label' => JText::translate('VBCUSTOMERGENDER')
1097 ),
1098 // data di nascita
1099 array(
1100 'key' => 'dbirth',
1101 'attr' => array(
1102 'class="center"'
1103 ),
1104 'label' => JText::translate('VBCUSTOMERBDATE')
1105 ),
1106 // cittadinanza
1107 array(
1108 'key' => 'citizen',
1109 'attr' => array(
1110 'class="center"'
1111 ),
1112 'label' => 'Cittadinanza'
1113 ),
1114 // residenza
1115 array(
1116 'key' => 'stares',
1117 'attr' => array(
1118 'class="center"'
1119 ),
1120 'label' => 'Stato di Residenza'
1121 ),
1122 // comune di residenza
1123 array(
1124 'key' => 'comres',
1125 'attr' => array(
1126 'class="center"'
1127 ),
1128 'label' => 'Comune Residenza',
1129 'tip' => 'Inserire il comune di residenza solo se il cittadino è di nazionalità italiana.'
1130 ),
1131 // tipo
1132 array(
1133 'key' => 'tipo',
1134 'attr' => array(
1135 'class="vbo-report-longlbl"',
1136 ),
1137 'label' => 'Tipo Alloggiato',
1138 ),
1139 // tipo di turismo
1140 array(
1141 'key' => 'turismo',
1142 'attr' => array(
1143 'class="center"',
1144 ),
1145 'label' => 'Tipo di Turismo',
1146 ),
1147 // mezzo di trasporto
1148 array(
1149 'key' => 'mezzo',
1150 'attr' => array(
1151 'class="center"',
1152 ),
1153 'label' => 'Mezzo di Trasporto',
1154 ),
1155 // numero di ospiti
1156 array(
1157 'key' => 'guestsnum',
1158 'attr' => array(
1159 'class="center"'
1160 ),
1161 'label' => 'Numero Ospiti',
1162 'ignore_view' => 1,
1163 ),
1164 // occupazione
1165 array(
1166 'key' => 'roomsbooked',
1167 'attr' => array(
1168 'class="center"'
1169 ),
1170 'label' => 'Camere',
1171 'ignore_view' => 1,
1172 ),
1173 // id booking
1174 array(
1175 'key' => 'idbooking',
1176 'attr' => array(
1177 'class="center"'
1178 ),
1179 'label' => 'ID / #',
1180 ),
1181 );
1182
1183 // line number (to facilitate identifying a specific guest in case of errors with the file submission)
1184 $line_number = 0;
1185
1186 // loop over the bookings to build the rows of the report
1187 $from_info = getdate($from_ts);
1188 foreach ($bookings as $gbook) {
1189 $guestsnum = 0;
1190 $guests_rows = [$gbook[0]];
1191 $room_guests = [];
1192 $tot_guests_rows = 1;
1193
1194 $tipo = 16;
1195 // Codici Tipo Alloggiato
1196 // 16 = Ospite Singolo
1197 // 17 = Capofamiglia
1198 // 18 = Capogruppo
1199 // 19 = Familiare
1200 // 20 = Membro Gruppo
1201
1202 foreach ($gbook as $book) {
1203 $guestsnum += $book['adults'] + $book['children'];
1204 $room_guests[] = ($book['adults'] + $book['children']);
1205 }
1206 $pax_data = null;
1207 if (!empty($gbook[0]['pax_data'])) {
1208 $pax_data = json_decode($gbook[0]['pax_data'], true);
1209 if (is_array($pax_data) && count($pax_data)) {
1210 $guests_rows[0]['pax_data'] = $pax_data;
1211 $tot_guests_rows = 0;
1212 foreach ($pax_data as $roomguests) {
1213 $tot_guests_rows += count($roomguests);
1214 }
1215 for ($i = 1; $i < $tot_guests_rows; $i++) {
1216 array_push($guests_rows, $guests_rows[0]);
1217 }
1218 $tipo = count($guests_rows) > 1 ? 17 : $tipo;
1219 }
1220 }
1221
1222 // create one row for each guest
1223 $guest_ind = 1;
1224 foreach ($guests_rows as $ind => $guests) {
1225 // prepare row record
1226 $insert_row = [];
1227
1228 // find the actual guest-room-index
1229 $guest_room_ind = $this->calcGuestRoomIndex($room_guests, $guest_ind);
1230
1231 // booking code idswh for the PMS software
1232 array_push($insert_row, array(
1233 'key' => 'idswh',
1234 'value' => $guests['id'] . '-' . $guest_ind,
1235 ));
1236
1237 // booking id
1238 array_push($insert_row, array(
1239 'key' => 'booking_id',
1240 'value' => $guests['id'],
1241 'ignore_view' => 1,
1242 ));
1243
1244 // reservation date
1245 array_push($insert_row, array(
1246 'key' => 'resdate',
1247 'attr' => array(
1248 'class="center"'
1249 ),
1250 'callback' => function($val) {
1251 return date('d/m/Y', $val);
1252 },
1253 'callback_export' => function($val) {
1254 return date('Ymd', $val);
1255 },
1256 'value' => $guests['ts'],
1257 ));
1258
1259 // checkin date
1260 array_push($insert_row, array(
1261 'key' => 'checkin',
1262 'attr' => array(
1263 'class="center"'
1264 ),
1265 'callback' => function($val) {
1266 return date('d/m/Y', $val);
1267 },
1268 'callback_export' => function($val) {
1269 return date('Ymd', $val);
1270 },
1271 'value' => $guests['checkin'],
1272 ));
1273
1274 // checkout date
1275 array_push($insert_row,array(
1276 'key' => 'checkout',
1277 'attr' => array(
1278 'class="center"'
1279 ),
1280 'callback' => function($val) {
1281 return date('d/m/Y', $val);
1282 },
1283 'callback_export' => function($val) {
1284 return date('Ymd', $val);
1285 },
1286 'value' => $guests['checkout'],
1287 ));
1288
1289 // nome
1290 $nome = !empty($guests['t_first_name']) ? $guests['t_first_name'] : $guests['first_name'];
1291 $pax_nome = $this->getGuestPaxDataValue($pax_data, $room_guests, $guest_ind, 'first_name');
1292 $nome = !empty($pax_nome) ? $pax_nome : $nome;
1293 array_push($insert_row, array(
1294 'key' => 'nome',
1295 'value' => $nome
1296 ));
1297
1298 // cognome
1299 $cognome = !empty($guests['t_last_name']) ? $guests['t_last_name'] : $guests['last_name'];
1300 $pax_cognome = $this->getGuestPaxDataValue($pax_data, $room_guests, $guest_ind, 'last_name');
1301 $cognome = !empty($pax_cognome) ? $pax_cognome : $cognome;
1302 array_push($insert_row, array(
1303 'key' => 'cognome',
1304 'value' => $cognome
1305 ));
1306
1307 // sesso
1308 $gender = !empty($guests['gender']) && $guest_ind < 2 ? strtoupper($guests['gender']) : '';
1309 $gender = $gender == 'F' ? 2 : ($gender == 'M' ? 1 : $gender);
1310 $pax_gender = $this->getGuestPaxDataValue($pax_data, $room_guests, $guest_ind, 'gender');
1311 $gender = !empty($pax_gender) ? $pax_gender : $gender;
1312 if (is_numeric($gender)) {
1313 $gender = (int)$gender;
1314 } elseif (!strcasecmp($gender, 'F')) {
1315 $gender = 2;
1316 } elseif (!strcasecmp($gender, 'M')) {
1317 $gender = 1;
1318 }
1319 array_push($insert_row, array(
1320 'key' => 'gender',
1321 'attr' => array(
1322 'class="center'.(empty($gender) ? ' vbo-report-load-sesso' : '').'"'
1323 ),
1324 'callback' => function($val) {
1325 return $val == 2 ? 'F' : ($val == 1 ? 'M' : '?');
1326 },
1327 'callback_export' => function($val) {
1328 return $val == 2 ? 'F' : ($val == 1 ? 'M' : '?');
1329 },
1330 'value' => $gender
1331 ));
1332
1333 // data di nascita
1334 $dbirth = !empty($guests['bdate']) && $guest_ind < 2 ? VikBooking::getDateTimestamp($guests['bdate'], 0, 0) : '';
1335 $pax_dbirth = $this->getGuestPaxDataValue($pax_data, $room_guests, $guest_ind, 'date_birth');
1336 $dbirth = !empty($pax_dbirth) ? $pax_dbirth : $dbirth;
1337 if (!empty($dbirth)) {
1338 $dbirth = str_replace(['/', '-', '.'], VikBooking::getDateSeparator(), (string) $dbirth);
1339 }
1340 $dbirth = (strpos($dbirth, '/') === false && strpos($dbirth, VikBooking::getDateSeparator()) === false) ? $dbirth : VikBooking::getDateTimestamp($dbirth, 0, 0);
1341 array_push($insert_row, array(
1342 'key' => 'dbirth',
1343 'attr' => array(
1344 'class="center'.(empty($dbirth) ? ' vbo-report-load-dbirth' : '').'"'
1345 ),
1346 'callback' => function($val) {
1347 if (!empty($val) && strpos($val, VikBooking::getDateSeparator()) === false) {
1348 return date('d/m/Y', $val);
1349 }
1350 if (!empty($val) && strpos($val, VikBooking::getDateSeparator()) !== false) {
1351 return $val;
1352 }
1353 return '?';
1354 },
1355 'callback_export' => function($val) {
1356 if (!empty($val) && is_numeric($val)) {
1357 return date('Ymd', $val);
1358 }
1359 if (!empty($val) && strlen($val) > 8) {
1360 return date('Ymd', VikBooking::getDateTimestamp($val));
1361 }
1362 return $val ?: '?';
1363 },
1364 'value' => $dbirth
1365 ));
1366
1367 // cittadinanza (compatible with pax data field of driver "Italy")
1368 $pax_country_c = $this->getGuestPaxDataValue($pax_data, $room_guests, $guest_ind, 'country_c');
1369 $citizen = !empty($guests['country']) && $guest_ind < 2 ? $guests['country'] : '';
1370 $citizenval = '';
1371 if (!empty($citizen) && $guest_ind < 2) {
1372 $citizenval = $this->checkCountry($citizen);
1373 }
1374
1375 // check nationality field from pre-checkin
1376 $pax_citizen = $this->getGuestPaxDataValue($pax_data, $room_guests, $guest_ind, 'nationality');
1377 $citizen = !empty($pax_citizen) ? $pax_citizen : $citizen;
1378 $citizen = !empty($pax_country_c) ? $pax_country_c : $citizen;
1379 $citizenval = $this->checkCountry((!empty($pax_country_c) ? $pax_country_c : $citizen));
1380 array_push($insert_row, array(
1381 'key' => 'citizen',
1382 'attr' => array(
1383 'class="center'.(empty($citizen) ? ' vbo-report-load-cittadinanza' : '').'"',
1384 ),
1385 'callback' => function($val) {
1386 return !empty($val) && isset($this->nazioni[$val]) ? $this->nazioni[$val]['name'] : '?';
1387 },
1388 'no_export_callback' => 1,
1389 'value' => !empty($citizenval) ? $citizenval : '',
1390 ));
1391
1392 // stato di residenza
1393 $provstay = '';
1394 $pax_provstay = $this->getGuestPaxDataValue($pax_data, $room_guests, $guest_ind, ($guest_ind < 2 ? 'country_s' : 'country_b'));
1395 $provstay = !empty($pax_provstay) ? $pax_provstay : $provstay;
1396 array_push($insert_row, array(
1397 'key' => 'stares',
1398 'attr' => array(
1399 'class="center'.(empty($provstay) ? ' vbo-report-load-nazione' : '').'"',
1400 ),
1401 'callback' => function($val) {
1402 if (!empty($val) && isset($this->nazioni[$val])) {
1403 return $this->nazioni[$val]['name'];
1404 }
1405 // information is missing and should be provided
1406 return '?';
1407 },
1408 'no_export_callback' => 1,
1409 'value' => $provstay,
1410 ));
1411
1412 // comune di residenza
1413 $comstay = '';
1414 $pax_comstay = $this->getGuestPaxDataValue($pax_data, $room_guests, $guest_ind, ($guest_ind < 2 ? 'comune_s' : 'comune_b'));
1415 $pax_comstay = $pax_comstay ?: $this->getGuestPaxDataValue($pax_data, $room_guests, $guest_ind, ($guest_ind < 2 ? 'province_s' : 'province_b'));
1416 $comstay = !empty($pax_comstay) ? $pax_comstay : $comstay;
1417 if (empty($comstay) && !empty($provstay) && $provstay != '100000100' && strtoupper(substr((string) $provstay, 0, 2)) != 'IT') {
1418 // we assume the guest is not Italian
1419 $comstay = 'ES';
1420 }
1421 array_push($insert_row, array(
1422 'key' => 'comres',
1423 'attr' => array(
1424 'class="center'.(empty($comstay) ? ' vbo-report-load-comune' : ' vbo-report-load-comune vbo-report-load-elem-filled').'"'
1425 ),
1426 'callback' => function($val) {
1427 if (!empty($val) && isset($this->comuniProvince['comuni'][$val])) {
1428 return $this->comuniProvince['comuni'][$val]['name'];
1429 }
1430 if ($val === 'ES') {
1431 return 'Estero';
1432 }
1433 // information is missing and should be provided
1434 return $val ?: '?';
1435 },
1436 'no_export_callback' => 1,
1437 'value' => $comstay,
1438 ));
1439
1440 // tipo alloggiato
1441 $use_tipo = $ind > 0 && $tipo == 17 ? 19 : $tipo;
1442 $pax_guest_type = $this->getGuestPaxDataValue($pax_data, $room_guests, $guest_ind, 'guest_type');
1443 $use_tipo = !empty($pax_guest_type) ? $pax_guest_type : $use_tipo;
1444 array_push($insert_row, array(
1445 'key' => 'tipo',
1446 'callback' => function($val) {
1447 switch ($val) {
1448 case 16:
1449 return 'Ospite Singolo';
1450 case 17:
1451 return 'Capofamiglia';
1452 case 18:
1453 return 'Capogruppo';
1454 case 19:
1455 return 'Familiare';
1456 case 20:
1457 return 'Membro Gruppo';
1458 }
1459 return '?';
1460 },
1461 'no_export_callback' => 1,
1462 'value' => $use_tipo
1463 ));
1464
1465 // tipo di turismo
1466 $pax_turismo = $this->getGuestPaxDataValue($pax_data, $room_guests, $guest_ind, 'turismo');
1467 array_push($insert_row, array(
1468 'key' => 'turismo',
1469 'attr' => array(
1470 'class="center' . (empty($pax_turismo) ? ' vbo-report-load-turismo vbo-report-load-elem-filled' : ' vbo-report-load-field-optional') . '"',
1471 ),
1472 'callback' => function($val) {
1473 return $this->tourismTypes[$val] ?? $val;
1474 },
1475 'no_export_callback' => 1,
1476 'value' => ($pax_turismo ?: 'NON SPECIFICATO'),
1477 ));
1478
1479 // mezzo di trasporto
1480 $pax_mezzo = $this->getGuestPaxDataValue($pax_data, $room_guests, $guest_ind, 'mezzo');
1481 array_push($insert_row, array(
1482 'key' => 'mezzo',
1483 'attr' => array(
1484 'class="center' . (empty($pax_mezzo) ? ' vbo-report-load-trasporto vbo-report-load-elem-filled' : ' vbo-report-load-field-optional') . '"',
1485 ),
1486 'callback' => function($val) {
1487 return $this->meaningsOfTransport[$val] ?? $val;
1488 },
1489 'no_export_callback' => 1,
1490 'value' => ($pax_mezzo ?: 'NON SPECIFICATO'),
1491 ));
1492
1493 // numero di ospiti
1494 array_push($insert_row, array(
1495 'key' => 'guestsnum',
1496 'attr' => array(
1497 'class="center"',
1498 ),
1499 'value' => $guestsnum,
1500 'ignore_view' => 1,
1501 ));
1502
1503 // camere prenotate
1504 array_push($insert_row, array(
1505 'key' => 'roomsbooked',
1506 'attr' => array(
1507 'class="center"',
1508 ),
1509 'value' => count($gbook),
1510 'ignore_view' => 1,
1511 ));
1512
1513 // id booking
1514 array_push($insert_row, array(
1515 'key' => 'idbooking',
1516 'attr' => array(
1517 'class="center"',
1518 ),
1519 'callback' => function($val) use ($line_number) {
1520 // make sure to keep the data-bid attribute as it's used by JS to identify the booking ID
1521 return '<a data-bid="' . $val . '" href="index.php?option=com_vikbooking&task=editorder&cid[]=' . $val . '" target="_blank"><i class="' . VikBookingIcons::i('external-link') . '"></i> ' . $val . '</a> / <span>#' . $line_number . '</span>';
1522 },
1523 'ignore_export' => 1,
1524 'value' => $guests['id'],
1525 ));
1526
1527 // push fields in the rows array as a new row
1528 array_push($this->rows, $insert_row);
1529
1530 // increment guest index
1531 $guest_ind++;
1532
1533 // increment line number
1534 $line_number++;
1535 }
1536 }
1537
1538 // do not sort the rows for this report because the lines of the guests of the same booking must be consecutive
1539 // $this->sortRows($pkrsort, $pkrorder);
1540
1541 // the footer row will just print the amount of records to export
1542 array_push($this->footerRow, array(
1543 array(
1544 'attr' => array(
1545 'class="vbo-report-total"',
1546 ),
1547 'value' => '<h3>' . JText::translate('VBOREPORTSTOTALROW') . '</h3>',
1548 ),
1549 array(
1550 'attr' => array(
1551 'colspan="' . (count($this->cols) - 1) . '"',
1552 ),
1553 'value' => count($this->rows),
1554 )
1555 ));
1556
1557 return true;
1558 }
1559
1560 public function formatXML($xml)
1561 {
1562 if (!class_exists('DOMDocument')) {
1563 // we cannot format the XML because DOMDocument is missing
1564 return $xml;
1565 }
1566
1567 $dom = new DOMDocument;
1568 $dom->preserveWhiteSpace = false;
1569 $dom->loadXML($xml);
1570 $dom->formatOutput = true;
1571 $xml = $dom->saveXML();
1572
1573 return $xml;
1574 }
1575
1576 /**
1577 * Generates the authority file, then sends it to output for download.
1578 * In case of errors, the process is not terminated (exit) to let the View display the
1579 * error message(s). The export type argument can eventually determine an action to run.
1580 *
1581 * @param string $export_type Differentiates the type of export requested.
1582 *
1583 * @return void|bool Void in case of script termination, boolean otherwise.
1584 *
1585 * @since 1.17.2 (J) - 1.7.2 (WP) method's logic refactoring.
1586 */
1587 public function customExport($export_type = null)
1588 {
1589 // build the XML file
1590 $xml = $this->buildXMLFile();
1591
1592 if ($this->datesHaveNoBookings) {
1593 // reset errors that may have been set for the View in case of no bookings to proceed
1594 $this->resetErrors();
1595 }
1596
1597 // build report action data, if needed
1598 $action_data = array_merge($this->getActionData($registry = false), ['xml' => $xml]);
1599
1600 /**
1601 * Custom export method can run a custom action.
1602 */
1603 if ($export_type && !is_numeric($export_type)) {
1604 try {
1605 // ensure the type of export is a callable scoped action, hidden or visible
1606 $actions = $this->getScopedActions($this->getScope(), $visible = false);
1607 $action_ids = array_column($actions, 'id');
1608 $action_names = array_column($actions, 'name');
1609 if (!in_array($export_type, $action_ids)) {
1610 throw new Exception(sprintf('Cannot invoke the requested type of export [%s].', $export_type), 403);
1611 }
1612
1613 // get the requested action readable name
1614 $action_name = $action_names[array_search($export_type, $action_ids)];
1615
1616 if ($this->getScope() === 'web') {
1617 // run the action and output the HTML response string
1618 $html_result = $this->_callActionReturn($export_type, 'html', $this->getScope(), $action_data);
1619
1620 // build the action result data object
1621 $js_result = json_encode([
1622 'actionName' => $action_name,
1623 'actionHtml' => $html_result,
1624 ]);
1625
1626 // render modal script with the action result
1627 JFactory::getDocument()->addScriptDeclaration(
1628 <<<JS
1629 ;(function($) {
1630 $(function() {
1631 let result = $js_result;
1632 VBOCore.displayModal({
1633 suffix: 'report-custom-scopedaction-result',
1634 extra_class: 'vbo-modal-rounded vbo-modal-tall vbo-modal-nofooter',
1635 title: result.actionName,
1636 body: result.actionHtml,
1637 });
1638 });
1639 })(jQuery);
1640 JS
1641 );
1642
1643 // abort and let the View render the result within a modal
1644 return;
1645 }
1646
1647 // let the report custom action run and return a boolean value if invoked by a cron
1648 return (bool) $this->_callActionReturn($export_type, 'success', $this->getScope(), $action_data);
1649
1650 } catch (Exception $e) {
1651 // silently catch the error and set it
1652 $this->setError(sprintf('(%s) %s', $e->getCode(), $e->getMessage()));
1653
1654 // abort
1655 return false;
1656 }
1657 }
1658
1659 // proceed with the regular export function (write on file through cron or download file through web)
1660
1661 if (!$xml) {
1662 // abort
1663 return false;
1664 }
1665
1666 // update history for all bookings affected before exporting
1667 foreach ($this->export_booking_ids as $bid) {
1668 VikBooking::getBookingHistoryInstance($bid)->store('RP', $this->reportName);
1669 }
1670
1671 /**
1672 * Custom export method supports a custom export handler, if previously set.
1673 *
1674 * @since 1.16.1 (J) - 1.6.1 (WP)
1675 */
1676 if ($this->hasExportHandler()) {
1677 // write data onto the custom file handler
1678 $fp = $this->getExportCSVHandler();
1679 fwrite($fp, $xml);
1680 fclose($fp);
1681
1682 // return true as data was written
1683 return true;
1684 }
1685
1686 // force file download in case of regular export
1687 header("Content-type: text/xml");
1688 header("Cache-Control: no-store, no-cache");
1689 header('Content-Disposition: attachment; filename="' . $this->getExportCSVFileName() . '"');
1690 echo $xml;
1691
1692 exit;
1693 }
1694
1695 /**
1696 * Builds the XML file for export or transmission.
1697 *
1698 * @return string Empty string in case of errors, XML otherwise.
1699 *
1700 * @since 1.17.2 (J) - 1.7.2 (WP) implemented dedicated method to file generation.
1701 */
1702 protected function buildXMLFile()
1703 {
1704 if (!$this->getReportData() && !$this->datesHaveNoBookings) {
1705 // do not generate the XML file
1706 return '';
1707 }
1708
1709 // load report settings
1710 $settings = $this->loadSettings();
1711
1712 // get the possibly injected report options
1713 $options = $this->getReportOptions();
1714
1715 // injected options will substitute request variables, if any
1716 $pfromdate = VikRequest::getString('fromdate', $options->get('fromdate', ''), 'request');
1717 $ptodate = VikRequest::getString('todate', $options->get('todate', ''), 'request');
1718
1719 // access manually filled values, if any
1720 $pfiller = VikRequest::getString('filler', '', 'request', VIKREQUEST_ALLOWRAW);
1721 $pfiller = !empty($pfiller) ? (array) json_decode($pfiller, true) : [];
1722
1723 // numero letti
1724 $total_beds = (int) ($settings['numletti'] ?? 1);
1725
1726 // count total number of room units
1727 $total_rooms = (int) ($settings['numcamere'] ?? $this->countRooms());
1728
1729 // date timestamps
1730 $from_ts = VikBooking::getDateTimestamp($pfromdate, 0, 0);
1731 $to_ts = VikBooking::getDateTimestamp($ptodate, 23, 59, 59);
1732
1733 // suppress warning messages
1734 libxml_use_internal_errors(true);
1735
1736 // get the SimpleXMLElement object
1737 $xml = simplexml_load_string('<movimenti></movimenti>');
1738
1739 // add property code and PMS name
1740 $xml->addChild('codice', htmlspecialchars($settings['codstru']));
1741 $xml->addChild('prodotto', htmlspecialchars('E4jConnect/VikBooking'));
1742
1743 // scan all given dates
1744 $date_info = getdate($from_ts);
1745 while ($date_info[0] <= $to_ts) {
1746 // get today's date in Y-m-d format
1747 $today_ymd = date('Y-m-d', $date_info[0]);
1748
1749 // open movimento node for this day
1750 $movement = $xml->addChild('movimento');
1751
1752 // populate movimento date
1753 $movement->addChild('data', date('Ymd', $date_info[0]));
1754
1755 // populate movimento property data
1756 $property = $movement->addChild('struttura');
1757
1758 // whether the property is open today
1759 $is_open = (bool) (!VikBooking::validateClosingDates($date_info[0], $date_info[0]));
1760 $property->addChild('apertura', ($is_open ? 'SI' : 'NO'));
1761
1762 // number of occupied rooms today, rooms and beds available
1763 if (!$is_open) {
1764 // property is closed today, so all values should be set as 0
1765 $property->addChild('camereoccupate', 0);
1766 $property->addChild('cameredisponibili', 0);
1767 $property->addChild('lettidisponibili', 0);
1768
1769 // go to next day without even checking the bookings or report rows
1770 $date_info = getdate(mktime(0, 0, 0, $date_info['mon'], $date_info['mday'] + 1, $date_info['year']));
1771
1772 // do not proceed for today
1773 continue;
1774 }
1775
1776 // check if some listings were filtered
1777 $filtered_listings = (array) (VikRequest::getVar('listings', array()) ?: $options->get('listings', []));
1778 $filtered_listings = array_filter(array_map('intval', $filtered_listings));
1779
1780 // property is open today
1781 $property->addChild('camereoccupate', $this->countDayRoomsOccupied($date_info[0], $filtered_listings));
1782 $property->addChild('cameredisponibili', $total_rooms);
1783 $property->addChild('lettidisponibili', $total_beds);
1784
1785 // check for arrivals, departures or reservations dated today
1786 $today_arrivals = [];
1787 $today_departures = [];
1788 $today_reservations = [];
1789
1790 // scan all report rows to gather the needed record types
1791 foreach ($this->rows as $ind => $row) {
1792 // booking ID flags
1793 $current_bid = 0;
1794 $current_idswh = '';
1795
1796 // scan row column values
1797 foreach ($row as $field) {
1798 if ($field['key'] == 'idswh') {
1799 // set booking (software) ID
1800 $current_idswh = (string) $field['value'];
1801 continue;
1802 }
1803
1804 if ($field['key'] == 'booking_id') {
1805 // set booking ID
1806 $current_bid = (int) $field['value'];
1807 continue;
1808 }
1809
1810 // check where this whole booking row should be allocated
1811 if ($current_bid && $current_idswh) {
1812 if ($field['key'] == 'resdate' && date('Y-m-d', $field['value']) == $today_ymd && !($today_reservations[$current_bid] ?? null)) {
1813 // this is a reservation made today (one per booking ID, not one per guest)
1814 $today_reservations[$current_bid] = $row;
1815 }
1816
1817 if ($field['key'] == 'checkin' && date('Y-m-d', $field['value']) == $today_ymd && !($today_arrivals[$current_idswh] ?? null)) {
1818 // this is a guest arriving today (one row per guest, not per booking)
1819 $today_arrivals[$current_idswh] = $row;
1820 }
1821
1822 if ($field['key'] == 'checkout' && date('Y-m-d', $field['value']) == $today_ymd && !($today_departures[$current_idswh] ?? null)) {
1823 // this is a guest departing today (one row per guest, not per booking)
1824 $today_departures[$current_idswh] = $row;
1825 }
1826 }
1827 }
1828
1829 // push booking ID involved for the export
1830 if ($current_bid && !in_array($current_bid, $this->export_booking_ids)) {
1831 $this->export_booking_ids[] = $current_bid;
1832 }
1833 }
1834
1835 // check for arrivals first
1836 if ($today_arrivals) {
1837 // open "arrivals" node
1838 $arrivals = $movement->addChild('arrivi');
1839
1840 // map of booking guest heads
1841 $booking_guest_heads = [];
1842
1843 // scan all arrivals for today
1844 foreach ($today_arrivals as $today_arrival_row) {
1845 // open "arrival" node
1846 $arrival = $arrivals->addChild('arrivo');
1847
1848 // booking ID flags
1849 $current_bid = 0;
1850 $current_idswh = '';
1851
1852 // guest registration values
1853 $guest_reg_vals = [];
1854
1855 // scan row column values
1856 foreach ($today_arrival_row as $field) {
1857 // store guest registration value
1858 $guest_reg_vals[$field['key']] = $this->getExportFieldValue($field);
1859
1860 if ($field['key'] == 'idswh') {
1861 // set booking (software) ID
1862 $current_idswh = (string) $field['value'];
1863 continue;
1864 }
1865
1866 if ($field['key'] == 'booking_id') {
1867 // set booking ID
1868 $current_bid = (int) $field['value'];
1869 continue;
1870 }
1871
1872 if ($current_idswh && $current_bid && $field['key'] == 'tipo' && in_array((int) $field['value'], [17, 18])) {
1873 // this is either a "Capofamiglia" or "Capogruppo", add relation
1874 $booking_guest_heads[$current_bid] = $current_idswh;
1875 }
1876 }
1877
1878 // add elements
1879 $arrival->addChild('idswh', htmlspecialchars($guest_reg_vals['idswh']));
1880 $arrival->addChild('tipoalloggiato', htmlspecialchars($guest_reg_vals['tipo'] ?? ''));
1881 if (($booking_guest_heads[$current_bid] ?? null) && $booking_guest_heads[$current_bid] != $guest_reg_vals['idswh']) {
1882 $arrival->addChild('idcapo', htmlspecialchars($booking_guest_heads[$current_bid]));
1883 }
1884 $arrival->addChild('cognome', htmlspecialchars($guest_reg_vals['cognome'] ?? ''));
1885 $arrival->addChild('nome', htmlspecialchars($guest_reg_vals['nome'] ?? ''));
1886 $arrival->addChild('sesso', htmlspecialchars($guest_reg_vals['gender'] ?? 'M'));
1887 $arrival->addChild('cittadinanza', htmlspecialchars($guest_reg_vals['citizen'] ?? ''));
1888 $arrival->addChild('statoresidenza', htmlspecialchars($guest_reg_vals['stares'] ?? ''));
1889 $arrival->addChild('luogoresidenza', htmlspecialchars($guest_reg_vals['comres'] ?? ''));
1890 $arrival->addChild('datanascita', htmlspecialchars($guest_reg_vals['dbirth'] ?? ''));
1891 $arrival->addChild('tipoturismo', htmlspecialchars($guest_reg_vals['turismo'] ?? ''));
1892 $arrival->addChild('mezzotrasporto', htmlspecialchars($guest_reg_vals['mezzo'] ?? ''));
1893 }
1894 }
1895
1896 // then check for departures
1897 if ($today_departures) {
1898 // open "departures" node
1899 $departures = $movement->addChild('partenze');
1900
1901 // scan all departures for today
1902 foreach ($today_departures as $today_departure_row) {
1903 // open "departure" node
1904 $departure = $departures->addChild('partenza');
1905
1906 // guest registration values
1907 $guest_reg_vals = $this->getAssocRowData($today_departure_row);
1908
1909 // add elements
1910 $departure->addChild('idswh', htmlspecialchars($guest_reg_vals['idswh'] ?? ''));
1911 $departure->addChild('tipoalloggiato', htmlspecialchars($guest_reg_vals['tipo'] ?? ''));
1912 $departure->addChild('arrivo', htmlspecialchars($guest_reg_vals['checkin'] ?? ''));
1913 }
1914 }
1915
1916 // finally, check for reservations
1917 if ($today_reservations) {
1918 // open "reservations" node
1919 $reservations = $movement->addChild('prenotazioni');
1920
1921 // scan all reservations for today
1922 foreach ($today_reservations as $today_reservation_row) {
1923 // open "reservation" node
1924 $reservation = $reservations->addChild('prenotazione');
1925
1926 // guest registration values
1927 $guest_reg_vals = $this->getAssocRowData($today_reservation_row);
1928
1929 // add elements
1930 $reservation->addChild('idswh', htmlspecialchars($guest_reg_vals['idswh'] ?? ''));
1931 $reservation->addChild('arrivo', htmlspecialchars($guest_reg_vals['checkin'] ?? ''));
1932 $reservation->addChild('partenza', htmlspecialchars($guest_reg_vals['checkout'] ?? ''));
1933 $reservation->addChild('ospiti', intval($guest_reg_vals['guestsnum'] ?? 1));
1934 $reservation->addChild('camere', intval($guest_reg_vals['roomsbooked'] ?? 1));
1935 }
1936 }
1937
1938 // go to next day
1939 $date_info = getdate(mktime(0, 0, 0, $date_info['mon'], $date_info['mday'] + 1, $date_info['year']));
1940 }
1941
1942 // format XML document and return it
1943 return $this->formatXML($xml->asXML());
1944 }
1945
1946 /**
1947 * Given a field (row-column) associative list of data, performs
1948 * the necessary callback operations to get the value to export.
1949 *
1950 * @param array $field The row-column exporting data.
1951 *
1952 * @return mixed The field value to export.
1953 *
1954 * @since 1.17.2 (J) - 1.7.2 (WP)
1955 */
1956 protected function getExportFieldValue(array $field)
1957 {
1958 $field_val = $field['value'];
1959
1960 if ($field['callback_export'] ?? null) {
1961 $field['callback'] = $field['callback_export'];
1962 }
1963
1964 if (!($field['no_export_callback'] ?? 0) && is_callable($field['callback'] ?? null)) {
1965 $field_val = $field['callback']($field_val);
1966 }
1967
1968 return $field_val;
1969 }
1970
1971 /**
1972 * Parses a report row into an associative list of key-value pairs.
1973 *
1974 * @param array $row The report row to parse.
1975 *
1976 * @return array
1977 *
1978 * @since 1.17.2 (J) - 1.7.2 (WP)
1979 */
1980 protected function getAssocRowData(array $row)
1981 {
1982 $row_data = [];
1983
1984 foreach ($row as $field) {
1985 $field_val = $field['value'];
1986
1987 if ($field['callback_export'] ?? null) {
1988 $field['callback'] = $field['callback_export'];
1989 }
1990
1991 if (!($field['no_export_callback'] ?? 0) && is_callable($field['callback'] ?? null)) {
1992 $field_val = $field['callback']($field_val);
1993 }
1994
1995 $row_data[$field['key']] = $field_val;
1996 }
1997
1998 return $row_data;
1999 }
2000
2001 /**
2002 * Counts the number of occupied rooms on the given day.
2003 *
2004 * @param int $day_ts The day timestamp (at midnight).
2005 * @param array $listings Optional list of filtered listing IDs.
2006 *
2007 * @return int Number of occupied rooms found.
2008 *
2009 * @since 1.17.2 (J) - 1.7.2 (WP)
2010 * @since 1.18.0 (J) - 1.8.0 (WP) added support for $listings argument.
2011 */
2012 protected function countDayRoomsOccupied($day_ts, array $listings = [])
2013 {
2014 // get the timestamp at the last second of the day
2015 $day_ts = strtotime('23:59:59', $day_ts);
2016
2017 $dbo = JFactory::getDbo();
2018
2019 if ($listings) {
2020 // perform a more accurate query over the listings specified
2021 $dbo->setQuery(
2022 $dbo->getQuery(true)
2023 ->select('COUNT(*)')
2024 ->from($dbo->qn('#__vikbooking_orders', 'o'))
2025 ->leftJoin($dbo->qn('#__vikbooking_ordersrooms', 'or') . ' ON ' . $dbo->qn('o.id') . ' = ' . $dbo->qn('or.idorder'))
2026 ->where($dbo->qn('o.status') . ' = ' . $dbo->q('confirmed'))
2027 ->where($dbo->qn('o.closure') . ' = 0')
2028 ->where($dbo->qn('o.checkin') . ' < ' . (int) $day_ts)
2029 ->where($dbo->qn('o.checkout') . ' > ' . (int) $day_ts)
2030 ->where($dbo->qn('or.idroom') . ' IN (' . implode(', ', array_map('intval', $listings)) . ')')
2031 );
2032 } else {
2033 // quick query to fetch the total number of occupied rooms
2034 $dbo->setQuery(
2035 $dbo->getQuery(true)
2036 ->select('SUM(' . $dbo->qn('roomsnum') . ')')
2037 ->from($dbo->qn('#__vikbooking_orders'))
2038 ->where($dbo->qn('status') . ' = ' . $dbo->q('confirmed'))
2039 ->where($dbo->qn('closure') . ' = 0')
2040 ->where($dbo->qn('checkin') . ' < ' . (int) $day_ts)
2041 ->where($dbo->qn('checkout') . ' > ' . (int) $day_ts)
2042 );
2043 }
2044
2045 return (int) $dbo->loadResult();
2046 }
2047
2048 /**
2049 * Custom scoped action to transmit the XML ISTAT file to the local authority.
2050 * Accepted scopes are "web" and "cron", so the "success" property must be returned.
2051 *
2052 * @param string $scope Optional scope identifier (cron, web, etc..).
2053 * @param array $data Optional associative list of data to process.
2054 *
2055 * @return array The execution result properties.
2056 *
2057 * @throws Exception
2058 */
2059 protected function transmitRecords($scope = null, array $data = [])
2060 {
2061 if (!($data['xml'] ?? '') && $scope === 'web') {
2062 // start the process through the interface by submitting the current data
2063 return [
2064 'html' => '<script type="text/javascript">vboDownloadSchedaIstat(\'transmitRecords\');</script>',
2065 ];
2066 }
2067
2068 if (!($data['xml'] ?? '')) {
2069 // attempt to build the XML file if not set
2070 $data['xml'] = $this->buildXMLFile();
2071 }
2072
2073 if (!$data['xml']) {
2074 throw new Exception('Missing XML request message.', 500);
2075 }
2076
2077 // load report settings
2078 $settings = $this->loadSettings();
2079
2080 if (!$settings || empty($settings['codstru']) || empty($settings['user']) || empty($settings['pwd']) || empty($settings['endpoint'])) {
2081 throw new Exception(sprintf('[%s] error: impostazioni report mancanti.', __METHOD__), 500);
2082 }
2083
2084 // build the SOAP XML string for the request body
2085 $soap_xml = '';
2086 $soap_xml .= '<?xml version="1.0"?>
2087 <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
2088 <S:Body>
2089 <ns2:inviaMovimentazione xmlns:ns2="http://checkin.ws.service.turismo5.gies.it/">' . "\n";
2090
2091 // adjust regular XML to SOAP format
2092 $soap_body_xml = $data['xml'];
2093 $soap_body_xml = preg_replace("/^<\?xml version=\"([0-9\.]+)\"\s?\?>(\r\n|\r|\n)?/", '', $soap_body_xml);
2094 $soap_body_xml = str_replace(['<movimenti>', '</movimenti>'], ['<movimentazione>', '</movimentazione>'], $soap_body_xml);
2095
2096 // append SOAP XML body
2097 $soap_xml .= trim($soap_body_xml);
2098
2099 // close SOAP XML string
2100 $soap_xml .= "\n" . '</ns2:inviaMovimentazione>
2101 </S:Body>
2102 </S:Envelope>';
2103
2104 // start the HTTP transporter
2105 $http = new JHttp();
2106
2107 /**
2108 * POST requests should be made to the endpoint meant as "location", not to the endpoint
2109 * representing the Web-Service (WSDL). In this case we should strip out the ending "?wsdl".
2110 */
2111 $settings['endpoint'] = preg_replace("/\?wsdl$/", '', $settings['endpoint']);
2112
2113 try {
2114 // make a POST request to the specified endpoint URL
2115 $response = $http->post($settings['endpoint'], $soap_xml, [
2116 'Accept' => 'text/xml, multipart/related',
2117 'Authorization' => 'Basic ' . base64_encode(sprintf('%s:%s', $settings['user'], $settings['pwd'])),
2118 'Content-Type' => 'text/xml; charset=utf-8',
2119 'SOAPAction' => '',
2120 // 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14.7; rv:133.0) Gecko/20100101 Firefox/133.0',
2121 'Connection' => 'keep-alive',
2122 'Content-Length' => strlen($soap_xml),
2123 ], 20);
2124
2125 // response text
2126 $response_txt = strip_tags((string) $response->body);
2127
2128 if ($response->code != 200) {
2129 // invalid response, throw an exception
2130 throw new Exception($response_txt, $response->code);
2131 }
2132
2133 // build HTML response string
2134 $html = '';
2135 $html .= '<p class="successmade">Flusso dati turistici inviati con successo.</p>';
2136 $html .= 'Responso:<br/><pre><code>' . htmlentities($response->body) . '</code></pre>';
2137
2138 } catch (Exception $e) {
2139 // propagate the error caught
2140 throw new Exception(sprintf('[%s] error: %s', __METHOD__, $e->getMessage()), $e->getCode() ?: 500);
2141 }
2142
2143 // get the currently active profile ID, if any
2144 $activeProfileId = $this->getActiveProfile();
2145
2146 /**
2147 * When the report is executed through a cron-job, the transmission of the guest details
2148 * will also dump the data transmitted onto a resource file, useful for sending it via email.
2149 *
2150 * @since 1.18.0 (J) - 1.8.0 (WP)
2151 */
2152 if ($scope === 'cron') {
2153 // prepare the XML resource file information
2154 $xml_name = implode('_', array_filter([$this->getFileName(), 'trasmissione', 'flussi', time(), $activeProfileId, rand(100, 999)])) . '.xml';
2155 $xml_dest = $this->getDataMediaPath() . DIRECTORY_SEPARATOR . $xml_name;
2156 $xml_url = $this->getDataMediaUrl() . $xml_name;
2157
2158 // store the XML bytes into a local file on disk
2159 $stored = JFile::write($xml_dest, $soap_xml);
2160
2161 if ($stored && VBOPlatformDetection::isWordPress()) {
2162 /**
2163 * Trigger files mirroring operation
2164 */
2165 VikBookingLoader::import('update.manager');
2166 VikBookingUpdateManager::triggerUploadBackup($xml_dest);
2167 }
2168
2169 if ($stored) {
2170 // define a new report resource for the generated file
2171 $this->defineResourceFile([
2172 'summary' => sprintf(
2173 '%sFlussi turistici generati e trasmessi con successo.',
2174 ($activeProfileId ? '(' . ucwords($activeProfileId) . ') ' : '')
2175 ),
2176 'url' => $xml_url,
2177 'path' => $xml_dest,
2178 ]);
2179 }
2180 }
2181
2182 // when executed through a cron, store an event in the Notifications Center
2183 if ($scope === 'cron') {
2184 // build the notification record
2185 $notification = [
2186 'sender' => 'reports',
2187 'type' => 'pmsreport.sendxml.ok',
2188 'title' => 'Ross1000 - Trasmissione flussi turistici',
2189 'summary' => sprintf(
2190 '%sI flussi turistici sono stati trasmessi con successo per le date dal %s al %s.',
2191 ($activeProfileId ? '(' . ucwords($activeProfileId) . ') ' : ''),
2192 $this->exported_checkin_dates[0] ?? '',
2193 $this->exported_checkin_dates[1] ?? ''
2194 ),
2195 ];
2196
2197 try {
2198 // store the notification record
2199 VBOFactory::getNotificationCenter()->store([$notification]);
2200 } catch (Exception $e) {
2201 // silently catch the error without doing anything
2202 }
2203 }
2204
2205 return [
2206 'html' => $html,
2207 'success' => true,
2208 'response' => $response->body,
2209 ];
2210 }
2211
2212 /**
2213 * Helper method invoked via AJAX by the controller.
2214 * Needed to save the manual entries for the pax data.
2215 *
2216 * @param array $manual_data the object representation of the manual entries.
2217 *
2218 * @return array one boolean value array with the operation result.
2219 */
2220 public function updatePaxData($manual_data = [])
2221 {
2222 if (!is_array($manual_data) || !$manual_data) {
2223 VBOHttpDocument::getInstance()->close(400, 'Nothing to save!');
2224 }
2225
2226 // re-build manual entries object representation
2227 $bids_guests = [];
2228 foreach ($manual_data as $guest_ind => $guest_data) {
2229 if (!is_numeric($guest_ind) || !is_array($guest_data) || empty($guest_data['bid']) || !isset($guest_data['bid_index']) || count($guest_data) < 2) {
2230 // empty or invalid manual entries array
2231 continue;
2232 }
2233 // the guest index in the reportObj starts from 0
2234 $use_guest_ind = ($guest_ind + 1 - (int)$guest_data['bid_index']);
2235 if (!isset($bids_guests[$guest_data['bid']])) {
2236 $bids_guests[$guest_data['bid']] = [];
2237 }
2238 // set manual entries for this guest number
2239 $bids_guests[$guest_data['bid']][$use_guest_ind] = $guest_data;
2240 // remove the "bid" and "bid_index" keys
2241 unset($bids_guests[$guest_data['bid']][$use_guest_ind]['bid'], $bids_guests[$guest_data['bid']][$use_guest_ind]['bid_index']);
2242 }
2243
2244 if (!$bids_guests) {
2245 VBOHttpDocument::getInstance()->close(400, 'No manual entries to save found');
2246 }
2247
2248 // loop through all bookings to update the data for the various rooms and guests
2249 $bids_updated = 0;
2250 foreach ($bids_guests as $bid => $entries) {
2251 $b_rooms = VikBooking::loadOrdersRoomsData($bid);
2252 if (empty($b_rooms)) {
2253 continue;
2254 }
2255 // count guests per room (adults + children)
2256 $room_guests = [];
2257 foreach ($b_rooms as $b_room) {
2258 $room_guests[] = $b_room['adults'] + $b_room['children'];
2259 }
2260 // get current booking pax data
2261 $pax_data = VBOCheckinPax::getBookingPaxData($bid);
2262 $pax_data = empty($pax_data) ? [] : $pax_data;
2263 foreach ($entries as $guest_ind => $guest_data) {
2264 // find room index for this guest
2265 $room_num = 0;
2266 $use_guest_ind = $guest_ind;
2267 foreach ($room_guests as $room_index => $tot_guests) {
2268 // find the proper guest index for the room to which this belongs
2269 if ($use_guest_ind <= $tot_guests) {
2270 // proper room index found for this guest
2271 $room_num = $room_index;
2272 break;
2273 } else {
2274 // it's probably in a next room
2275 $use_guest_ind -= $tot_guests;
2276 }
2277 }
2278 // push new pax data for this room and guest
2279 if (!isset($pax_data[$room_num])) {
2280 $pax_data[$room_num] = [];
2281 }
2282 if (!isset($pax_data[$room_num][$use_guest_ind])) {
2283 $pax_data[$room_num][$use_guest_ind] = $guest_data;
2284 } else {
2285 $pax_data[$room_num][$use_guest_ind] = array_merge($pax_data[$room_num][$use_guest_ind], $guest_data);
2286 }
2287 }
2288 // update booking pax data
2289 if (VBOCheckinPax::setBookingPaxData($bid, $pax_data)) {
2290 $bids_updated++;
2291 }
2292 }
2293
2294 return $bids_updated ? [true] : [false];
2295 }
2296
2297 /**
2298 * Registers the name to give to the file being exported.
2299 *
2300 * @return void
2301 *
2302 * @since 1.16.1 (J) - 1.6.1 (WP)
2303 */
2304 protected function registerExportFileName()
2305 {
2306 $pfromdate = VikRequest::getString('fromdate', '', 'request');
2307 $ptodate = VikRequest::getString('todate', '', 'request');
2308
2309 $this->setExportCSVFileName(str_replace(' ', '_', $this->reportName) . '-' . str_replace('/', '_', $pfromdate) . '-' . str_replace('/', '_', $ptodate) . '.xml');
2310 }
2311
2312 /**
2313 * Parses the file Comuni.csv and returns two associative
2314 * arrays: one for the Comuni and one for the Province.
2315 * Every line of the CSV is composed of: Codice, Comune, Provincia.
2316 *
2317 * @return array
2318 */
2319 protected function loadComuniProvince()
2320 {
2321 $vals = [
2322 'comuni' => [
2323 '-- Estero --',
2324 ],
2325 'province' => [
2326 '-- Estero --',
2327 ]
2328 ];
2329
2330 $csv = dirname(__FILE__).DIRECTORY_SEPARATOR.'Comuni.csv';
2331 $rows = file($csv);
2332 foreach ($rows as $row) {
2333 if (empty($row)) {
2334 continue;
2335 }
2336
2337 $v = explode(';', $row);
2338 if (count($v) != 3) {
2339 continue;
2340 }
2341
2342 // trim values
2343 $v[0] = trim($v[0]);
2344 $v[1] = trim($v[1]);
2345 $v[2] = trim($v[2]);
2346
2347 $vals['comuni'][$v[0]]['name'] = $v[1];
2348 $vals['comuni'][$v[0]]['province'] = $v[2];
2349 $vals['province'][$v[2]] = $v[2];
2350 }
2351
2352 return $vals;
2353 }
2354
2355 /**
2356 * Parses the file Nazioni.csv and returns an associative
2357 * array with the code and name of the Nazione.
2358 * Every line of the CSV is composed of: Codice, Nazione.
2359 *
2360 * @return array
2361 */
2362 protected function loadNazioni()
2363 {
2364 $nazioni = [];
2365
2366 $csv = dirname(__FILE__).DIRECTORY_SEPARATOR.'Nazioni.csv';
2367 $rows = file($csv);
2368 foreach ($rows as $row) {
2369 if (empty($row)) {
2370 continue;
2371 }
2372 $v = explode(';', $row);
2373 if (count($v) != 3) {
2374 continue;
2375 }
2376
2377 // trim values
2378 $v[0] = trim($v[0]);
2379 $v[1] = trim($v[1]);
2380 $v[2] = trim($v[2]);
2381
2382 $nazioni[$v[0]]['name'] = $v[1];
2383 $nazioni[$v[0]]['three_code'] = $v[2];
2384
2385 }
2386
2387 return $nazioni;
2388 }
2389
2390 /**
2391 * Returns the key of the state selected by the user, if any.
2392 *
2393 * @param string $country The selected country code.
2394 *
2395 * @return string
2396 */
2397 protected function checkCountry($country)
2398 {
2399 $found = false;
2400 $staval = '';
2401
2402 if (!$this->nazioni) {
2403 $this->nazioni = $this->loadNazioni();
2404 }
2405
2406 if ($country && isset($this->nazioni[$country])) {
2407 return $country;
2408 }
2409
2410 foreach ($this->nazioni as $key => $value) {
2411 if (trim($value['three_code']) == trim($country)) {
2412 $staval = $key;
2413 $found = true;
2414 break;
2415 }
2416 }
2417
2418 if ($found !== true) {
2419 $staval = '';
2420 }
2421
2422 return $staval;
2423 }
2424
2425 /**
2426 * Helper method to quickly get a pax_data property for the guest.
2427 *
2428 * @param array $pax_data the current pax_data stored.
2429 * @param array $guests list of total guests per room.
2430 * @param int $guest_ind the guest index.
2431 * @param string $key the pax_data key to look for.
2432 *
2433 * @return mixed null on failure or value fetched.
2434 */
2435 protected function getGuestPaxDataValue($pax_data, $guests, $guest_ind, $key)
2436 {
2437 if (!is_array($pax_data) || !count($pax_data) || empty($key)) {
2438 return null;
2439 }
2440
2441 // find room index for this guest number
2442 $room_num = 0;
2443 $use_guest_ind = $guest_ind;
2444 foreach ($guests as $room_index => $room_tot_guests) {
2445 // find the proper guest index for the room to which this belongs
2446 if ($use_guest_ind <= $room_tot_guests) {
2447 // proper room index found for this guest
2448 $room_num = $room_index;
2449 break;
2450 } else {
2451 // it's probably in a next room
2452 $use_guest_ind -= $room_tot_guests;
2453 }
2454 }
2455
2456 // check if a value exists for the requested key in the found room and guest indexes
2457 if (isset($pax_data[$room_num]) && isset($pax_data[$room_num][$use_guest_ind])) {
2458 if (isset($pax_data[$room_num][$use_guest_ind][$key])) {
2459 // we've got a value previously stored
2460 return $pax_data[$room_num][$use_guest_ind][$key];
2461 }
2462 }
2463
2464 // nothing was found
2465 return null;
2466 }
2467
2468 /**
2469 * Helper method to determine the exact number for this guest in the room booked.
2470 *
2471 * @param array $guests list of total guests per room.
2472 * @param int $guest_ind the guest index.
2473 *
2474 * @return int the actual guest room index starting from 1.
2475 */
2476 protected function calcGuestRoomIndex($guests, $guest_ind)
2477 {
2478 // find room index for this guest number
2479 $room_num = 0;
2480 $use_guest_ind = $guest_ind;
2481 foreach ($guests as $room_index => $room_tot_guests) {
2482 // find the proper guest index for the room to which this belongs
2483 if ($use_guest_ind <= $room_tot_guests) {
2484 // proper room index found for this guest
2485 $room_num = $room_index;
2486 break;
2487 } else {
2488 // it's probably in a next room
2489 $use_guest_ind -= $room_tot_guests;
2490 }
2491 }
2492
2493 return $use_guest_ind;
2494 }
2495 }
2496