PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.9
VikBooking Hotel Booking Engine & PMS v1.8.9
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 / widgets / door_access_control.php

door_access_control.php in VikBooking Hotel Booking Engine & PMS 1.8.9, at admin/helpers/widgets/door_access_control.php

2,135 lines 107.7 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 core
5 * @author E4J srl
6 * @copyright Copyright (C) 2025 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 * Class handler for admin widget "Door Access Control".
15 *
16 * @since 1.18.4 (J) - 1.8.4 (WP)
17 */
18 class VikBookingAdminWidgetDoorAccessControl extends VikBookingAdminWidget
19 {
20 /**
21 * The instance counter of this widget. Since we do not load individual parameters
22 * for each widget's instance, we use a static counter to determine its settings.
23 *
24 * @var int
25 */
26 protected static $instance_counter = -1;
27
28 /**
29 * @inheritDOc
30 */
31 public function __construct()
32 {
33 // call parent constructor
34 parent::__construct();
35
36 $this->widgetName = JText::translate('VBO_W_DOORACCESSCONTROL_TITLE');
37 $this->widgetDescr = JText::translate('VBO_W_DOORACCESSCONTROL_DESCR');
38 $this->widgetId = basename(__FILE__, '.php');
39
40 $this->widgetIcon = '<i class="' . VikBookingIcons::i('door-open') . '"></i>';
41 $this->widgetStyleName = 'orange';
42
43 // load widget's settings
44 $this->widgetSettings = (array) $this->loadSettings();
45 }
46
47 /**
48 * @inheritDoc
49 */
50 public function preflight()
51 {
52 if (!JFactory::getUser()->authorise('core.vbo.pms', 'com_vikbooking')) {
53 // insufficient user capabilities
54 return false;
55 }
56
57 return parent::preflight();
58 }
59
60 /**
61 * @inheritDoc
62 */
63 public function preload()
64 {
65 // load assets
66 $this->vbo_app->loadSelect2();
67 }
68
69 /**
70 * @inheritDoc
71 */
72 public function getWidgetDetails()
73 {
74 // get common widget details from parent abstract class
75 $details = parent::getWidgetDetails();
76
77 // append the modal rendering information
78 $details['modal'] = [
79 'add_class' => 'vbo-modal-nopadding vbo-modal-large',
80 ];
81
82 return $details;
83 }
84
85 /**
86 * @inheritDoc
87 */
88 public function render(?VBOMultitaskData $data = null)
89 {
90 // increase widget's instance counter
91 static::$instance_counter++;
92
93 // check whether the widget is being rendered via AJAX when adding it through the customizer
94 $is_ajax = $this->isAjaxRendering();
95
96 // generate a unique ID for the sticky notes wrapper instance
97 $wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
98 $wrapper_id = 'vbo-widget-door-access-control-' . $wrapper_instance;
99
100 // default provider, profile and device values (profile and device start from null)
101 $defaultProvider = $this->widgetSettings['id_provider'] ?? null;
102 $defaultProfile = null;
103 $defaultDevice = null;
104 $defaultTab = null;
105
106 // check for multitask data values
107 $js_intvals_id = '';
108 if ($data) {
109 // access Multitask data
110 if ($data->isModalRendering()) {
111 // get modal JS identifier
112 $js_intvals_id = $data->getModalJsIdentifier();
113 }
114 // check if provider, profile, device or tab values were set
115 $defaultProvider = $this->getOption('provider') ?: $data->get('provider') ?: $defaultProvider;
116 $defaultProfile = $this->getOption('profile') ?: $data->get('profile') ?: $defaultProfile;
117 $defaultDevice = $this->getOption('device') ?: $data->get('device') ?: $defaultDevice;
118 $defaultTab = $this->getOption('tab') ?: $data->get('tab') ?: $defaultTab;
119 }
120
121 // access the door access factory object
122 $factory = VBOFactory::getDoorAccessControl();
123
124 // load the list of available integration providers
125 $providers = $factory->getIntegrationProviders($assoc = true);
126
127 if (!$providers) {
128 // abort
129 return;
130 }
131
132 ?>
133 <div id="<?php echo $wrapper_id; ?>" class="vbo-admin-widget-wrapper" data-instance="<?php echo $wrapper_instance; ?>">
134 <div class="vbo-admin-widget-head">
135 <div class="vbo-admin-widget-head-inline">
136 <h4><?php echo $this->widgetIcon; ?> <span><?php echo $this->widgetName; ?></span></h4>
137 <div class="vbo-admin-widget-head-commands">
138 <span class="vbo-admin-widget-loading-entry"></span>
139 <select class="vbo-door-access-control-setting" data-setting="id_provider">
140 <option value="">- <?php echo JText::translate('VBO_PROVIDER'); ?></option>
141 <?php
142 foreach ($providers as $providerAlias => $providerName) {
143 ?>
144 <option value="<?php echo $providerAlias; ?>"<?php echo $providerAlias === $defaultProvider ? ' selected="selected"' : ''; ?>><?php echo $providerName; ?></option>
145 <?php
146 }
147 ?>
148 </select>
149 </div>
150 </div>
151 </div>
152 <div class="vbo-widget-door-access-control-wrap"></div>
153 <div class="vbo-widget-dac-add-listing-helper" style="display: none;">
154 <div class="vbo-widget-dac-add-listing-wrap">
155 <div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact">
156 <div class="vbo-params-wrap">
157 <div class="vbo-params-container">
158 <div class="vbo-params-block">
159 <div class="vbo-param-container">
160 <div class="vbo-param-setting">
161 <?php
162 echo VikBooking::getVboApplication()->renderElementsDropDown([
163 'elements' => 'listings',
164 'subunits' => [
165 'entire_listing' => true,
166 'value_format' => '%d-%d',
167 ],
168 'placeholder' => JText::translate('VBO_LISTING'),
169 'attributes' => [
170 'class' => 'vbo-door-access-control-add-listing-id',
171 ],
172 ]);
173 ?>
174 </div>
175 </div>
176 </div>
177 </div>
178 </div>
179 </div>
180 </div>
181 </div>
182 <div class="vbo-widget-dac-cap-params-helper" style="display: none;">
183 <div class="vbo-widget-dac-cap-params-wrap">
184 <div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact">
185 <div class="vbo-params-wrap">
186 <div class="vbo-params-container">
187 <div class="vbo-params-block"></div>
188 </div>
189 </div>
190 </div>
191 </div>
192 </div>
193 </div>
194 <?php
195 if (static::$instance_counter === 0 || $is_ajax) {
196 /**
197 * JavaScript code that should be declared only once per
198 * widget instance, unless we are within AJAX rendering.
199 */
200 ?>
201 <script>
202 /**
203 * Toggle loading animation state.
204 */
205 function vboWidgetDACSetLoading(wrapper_id, state) {
206 let widget_wrapper = document.getElementById(wrapper_id);
207
208 if (!widget_wrapper) {
209 throw new Error('Widget instance not found');
210 }
211
212 let loadingEl = widget_wrapper.querySelector('.vbo-admin-widget-loading-entry');
213
214 if (state) {
215 loadingEl.innerHTML = '<?php VikBookingIcons::e('circle-notch', 'fa-spin fa-fw'); ?>';
216 } else {
217 loadingEl.innerHTML = '';
218 }
219 }
220
221 /**
222 * Register function for loading the provider settings.
223 */
224 function vboWidgetDACLoadProvider(wrapper_id, id_provider, id_profile, tab_redirect, id_device) {
225 let widget_element = document.getElementById(wrapper_id);
226
227 if (!widget_element) {
228 throw new Error('Widget instance not found');
229 }
230
231 let widget_wrapper = widget_element.querySelector('.vbo-widget-door-access-control-wrap');
232
233 // empty the current widget body
234 widget_wrapper.innerHTML = '';
235
236 if (!id_provider) {
237 // abort in case of empty provider selection
238 return;
239 }
240
241 // gather options, if any
242 let options = vbo_widget_dac_options_oo || {};
243 if (vbo_widget_dac_options_oo) {
244 // multitask data options should be used only once ("oo")
245 vbo_widget_dac_options_oo = null;
246 }
247
248 // start loading animation
249 vboWidgetDACSetLoading(wrapper_id, true);
250
251 // the widget method to call
252 let call_method = 'loadProviderSettings';
253
254 // make a request to load the selected provider
255 VBOCore.doAjax(
256 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
257 {
258 widget_id: "<?php echo $this->getIdentifier(); ?>",
259 call: call_method,
260 return: 1,
261 provider: id_provider,
262 profile: id_profile,
263 tab: tab_redirect,
264 id_device: id_device,
265 _options: options,
266 wrapper: wrapper_id,
267 tmpl: "component"
268 },
269 (response) => {
270 try {
271 let obj_res = typeof response === 'string' ? JSON.parse(response) : response;
272 if (!obj_res.hasOwnProperty(call_method)) {
273 console.error('Unexpected JSON response', obj_res);
274 return false;
275 }
276
277 // stop loading animation
278 vboWidgetDACSetLoading(wrapper_id, false);
279
280 // set response HTML body
281 jQuery(widget_wrapper).html(obj_res[call_method]['html']);
282
283 // check if retry-data options were set
284 if (options?.retry_data?.callback) {
285 // trigger click on device capability for retry
286 setTimeout(() => {
287 let deviceCap = widget_wrapper.querySelector('.vbo-dac-device-capability-execute-btn[data-callback="' + options.retry_data.callback + '"]');
288 if (deviceCap) {
289 // inject the retry-data options, if any
290 vbo_widget_dac_retry_data_oo = options?.retry_data?.options || null;
291 // trigger click on capability
292 deviceCap.click();
293 }
294 }, 100);
295 }
296 } catch(err) {
297 // log and display error
298 console.error('could not parse JSON response', err, response);
299 alert('could not parse JSON response');
300 }
301 },
302 (error) => {
303 // log and display error
304 console.error(error);
305 alert(error.responseText);
306
307 // stop loading animation
308 vboWidgetDACSetLoading(wrapper_id, false);
309 }
310 );
311 }
312
313 /**
314 * Register function for saving the integration profile settings.
315 */
316 function vboWidgetDACSaveProfileSettings(wrapper_id) {
317 let widget_element = document.getElementById(wrapper_id);
318
319 if (!widget_element) {
320 throw new Error('Widget instance not found');
321 }
322
323 // gather profile record information
324 let profileData = {};
325 widget_element.querySelectorAll('.vbo-door-access-control-setting[data-setting]').forEach((el) => {
326 let settingType = el.getAttribute('data-setting');
327 profileData[settingType] = el.value;
328 });
329
330 if (!profileData.id_provider) {
331 throw new Error('Could not find provider alias identifier.');
332 }
333
334 if (!profileData.gentype) {
335 alert(<?php echo json_encode('Please select: ' . JText::translate('VBO_PASSCODE_GENERATION')); ?>);
336 return false;
337 }
338
339 // gather provider integration settings
340 let providerSettings = {};
341 widget_element.querySelectorAll('[name*="integration[' + profileData.id_provider + ']"]').forEach((settingEl) => {
342 let settingName = settingEl.getAttribute('name');
343 let reSafe = (profileData.id_provider + '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
344 let reFull = new RegExp(`^integration\\[${reSafe}\\]`);
345 let paramName = settingName.replace(reFull, '').replace(/^\[|\]$/g, '');
346 if (settingEl.matches('select') && settingEl.getAttribute('multiple')) {
347 // array-value element
348 let selectedValues = Array.from(settingEl.options).filter((option) => option.selected).map((option) => option.value);
349 providerSettings[paramName] = selectedValues;
350 } else if (settingEl.matches('input[type="checkbox"]')) {
351 // ensure it's checked
352 providerSettings[paramName] = settingEl.checked ? 1 : 0;
353 } else {
354 // single-value element
355 providerSettings[paramName] = settingEl.value;
356 }
357 });
358
359 // start loading animation
360 vboWidgetDACSetLoading(wrapper_id, true);
361
362 // the widget method to call
363 let call_method = 'saveProviderSettings';
364
365 // make a request to load the selected provider
366 VBOCore.doAjax(
367 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
368 {
369 widget_id: "<?php echo $this->getIdentifier(); ?>",
370 call: call_method,
371 return: 1,
372 profile: profileData,
373 settings: providerSettings,
374 wrapper: wrapper_id,
375 tmpl: "component"
376 },
377 (response) => {
378 try {
379 let obj_res = typeof response === 'string' ? JSON.parse(response) : response;
380 if (!obj_res.hasOwnProperty(call_method)) {
381 console.error('Unexpected JSON response', obj_res);
382 return false;
383 }
384
385 // stop loading animation
386 vboWidgetDACSetLoading(wrapper_id, false);
387
388 // reload provider settings
389 vboWidgetDACLoadProvider(wrapper_id, profileData.id_provider, obj_res[call_method]['id_profile'] || 0);
390 } catch(err) {
391 // log and display error
392 console.error('could not parse JSON response', err, response);
393 alert('could not parse JSON response');
394 }
395 },
396 (error) => {
397 // log and display error
398 console.error(error);
399 alert(error.responseText);
400
401 // stop loading animation
402 vboWidgetDACSetLoading(wrapper_id, false);
403 }
404 );
405 }
406
407 /**
408 * Register function for changing the generation type setting.
409 */
410 function vboWidgetDACSwitchGenType(wrapper_id) {
411 let widget_element = document.getElementById(wrapper_id);
412
413 if (!widget_element) {
414 throw new Error('Widget instance not found');
415 }
416
417 const gentypeVal = widget_element.querySelector('.vbo-door-access-control-setting[data-setting="gentype"]').value;
418 const genperiodEl = widget_element.querySelector('.vbo-door-access-control-setting[data-setting="genperiod"]');
419
420 if (gentypeVal == 'checkin') {
421 genperiodEl.style.display = '';
422 } else {
423 genperiodEl.style.display = 'none';
424 }
425 }
426
427 /**
428 * Register function for changing profile record ID.
429 */
430 function vboWidgetDACSwitchProfileSettings(wrapper_id) {
431 let widget_element = document.getElementById(wrapper_id);
432
433 if (!widget_element) {
434 throw new Error('Widget instance not found');
435 }
436
437 let id_profile_el = widget_element.querySelector('.vbo-door-access-control-setting[data-setting="id_profile"]');
438 if (!id_profile_el) {
439 throw new Error('Could not find id profile element');
440 }
441
442 let id_provider_el = widget_element.querySelector('.vbo-door-access-control-setting[data-setting="id_provider"]');
443 if (!id_provider_el) {
444 throw new Error('Could not find id provider element');
445 }
446
447 let id_profile = id_profile_el.value;
448 let id_provider = id_provider_el.value;
449
450 if (id_profile == '-1') {
451 // create new profile
452 widget_element.querySelector('.vbo-door-access-control-setting[data-setting="profile_name"]').value = '';
453 widget_element.querySelector('.vbo-door-access-control-setting[data-setting="gentype"]').value = '';
454
455 // make most of the currently displayed settings empty when asking to create a new profile
456 widget_element.querySelectorAll('[name*="integration[' + id_provider + ']"]').forEach((settingEl) => {
457 if (!settingEl.matches('select') || !settingEl.getAttribute('multiple')) {
458 // single-value element
459 settingEl.value = '';
460 }
461 });
462
463 // abort
464 return;
465 }
466
467 // reload settings for the selected and existing profile ID
468 vboWidgetDACLoadProvider(wrapper_id, id_provider, id_profile);
469 }
470
471 /**
472 * Register function for changing device ID.
473 */
474 function vboWidgetDACSwitchDeviceID(wrapper_id) {
475 let widget_element = document.getElementById(wrapper_id);
476
477 if (!widget_element) {
478 throw new Error('Widget instance not found');
479 }
480
481 let id_profile_el = widget_element.querySelector('.vbo-door-access-control-setting[data-setting="id_profile"]');
482 if (!id_profile_el) {
483 throw new Error('Could not find id profile element');
484 }
485
486 let id_provider_el = widget_element.querySelector('.vbo-door-access-control-setting[data-setting="id_provider"]');
487 if (!id_provider_el) {
488 throw new Error('Could not find id provider element');
489 }
490
491 let id_device_el = widget_element.querySelector('.vbo-door-access-control-device-id');
492 if (!id_device_el) {
493 throw new Error('Could not find id device element');
494 }
495
496 let id_device = id_device_el.value;
497 let id_profile = id_profile_el.value;
498 let id_provider = id_provider_el.value;
499
500 if (!id_device) {
501 // display message to choose a device from the list
502 widget_element.querySelector('.vbo-dac-capabilities-list').innerHTML = '<p class="info">' + <?php echo json_encode(JText::translate('VBO_CHOOSE_DEVICE')); ?> + '</p>';
503
504 // abort
505 return;
506 }
507
508 // reload widget with the selected device
509 vboWidgetDACLoadProvider(wrapper_id, id_provider, id_profile, 'dashboard', id_device);
510 }
511
512 /**
513 * Register function for handling a global body click event delegation.
514 */
515 function vbo_w_dac_click_delegation(e) {
516 // tabs-panels switching
517 if (e.target.matches('.vbo-widget-door-access-control-tab') || e.target.closest('.vbo-widget-door-access-control-tab')) {
518 const tabEl = !e.target.matches('.vbo-widget-door-access-control-tab') ? e.target.closest('.vbo-widget-door-access-control-tab') : e.target;
519 const tabType = tabEl.getAttribute('data-type');
520 const content = tabEl.closest('.vbo-widget-door-access-control-content');
521 content.querySelectorAll('.vbo-widget-door-access-control-panel[data-type]').forEach((panel) => {
522 const panelType = panel.getAttribute('data-type');
523 if (panelType === tabType) {
524 // show panel
525 panel.style.display = '';
526 tabEl.classList.add('vbo-widget-tab-active');
527 } else {
528 // hide panel
529 panel.style.display = 'none';
530 let controlTab = content.querySelector('.vbo-widget-door-access-control-tab[data-type="' + panelType + '"]');
531 if (controlTab) {
532 controlTab.classList.remove('vbo-widget-tab-active');
533 }
534 }
535 });
536
537 // do not proceed
538 return;
539 }
540
541 // update devices button
542 if (e.target.matches('.vbo-widget-dac-update-devices') || e.target.closest('.vbo-widget-dac-update-devices')) {
543 const buttonEl = !e.target.matches('.vbo-widget-dac-update-devices') ? e.target.closest('.vbo-widget-dac-update-devices') : e.target;
544 const wrapper_id = buttonEl.closest('.vbo-admin-widget-wrapper').getAttribute('id');
545 const id_provider = document.getElementById(wrapper_id).querySelector('.vbo-door-access-control-setting[data-setting="id_provider"]').value;
546 const id_profile = document.getElementById(wrapper_id).querySelector('.vbo-door-access-control-setting[data-setting="id_profile"]').value;
547
548 // disable button
549 buttonEl.disabled = true;
550
551 // change button icon for loading
552 buttonEl.querySelector('i').setAttribute('class', '<?php echo VikBookingIcons::i('sync', 'fa-spin fa-fw'); ?>');
553
554 // start loading animation
555 vboWidgetDACSetLoading(wrapper_id, true);
556
557 // the widget method to call
558 let call_method = 'updateProviderDevices';
559
560 // make a request to update the provider devices
561 VBOCore.doAjax(
562 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
563 {
564 widget_id: "<?php echo $this->getIdentifier(); ?>",
565 call: call_method,
566 return: 1,
567 provider: id_provider,
568 profile: id_profile,
569 wrapper: wrapper_id,
570 tmpl: "component"
571 },
572 (response) => {
573 // stop loading animation
574 vboWidgetDACSetLoading(wrapper_id, false);
575
576 // change button icon for non-loading
577 buttonEl.querySelector('i').setAttribute('class', '<?php echo VikBookingIcons::i('sync'); ?>');
578
579 try {
580 let obj_res = typeof response === 'string' ? JSON.parse(response) : response;
581 if (!obj_res.hasOwnProperty(call_method)) {
582 console.error('Unexpected JSON response', obj_res);
583 return false;
584 }
585
586 if (obj_res[call_method]['tot_devices']) {
587 // reload the provider with the new data updated
588 vboWidgetDACLoadProvider(wrapper_id, id_provider, id_profile, 'devices');
589 } else {
590 // re-enable button
591 buttonEl.disabled = false;
592 }
593 } catch(err) {
594 // log and display error
595 console.error('could not parse JSON response', err, response);
596 alert('could not parse JSON response');
597
598 // re-enable button
599 buttonEl.disabled = false;
600 }
601 },
602 (error) => {
603 // log and display error
604 console.error(error);
605 alert(error.responseText);
606
607 // change button icon for non-loading
608 buttonEl.querySelector('i').setAttribute('class', '<?php echo VikBookingIcons::i('sync'); ?>');
609
610 // stop loading animation
611 vboWidgetDACSetLoading(wrapper_id, false);
612
613 // re-enable button
614 buttonEl.disabled = false;
615 }
616 );
617
618 // do not proceed
619 return;
620 }
621
622 // add device connected listing button
623 if (e.target.matches('.vbo-widget-dac-device-add-listing') || e.target.closest('.vbo-widget-dac-device-add-listing')) {
624 const buttonEl = !e.target.matches('.vbo-widget-dac-device-add-listing') ? e.target.closest('.vbo-widget-dac-device-add-listing') : e.target;
625 const wrapper_id = buttonEl.closest('.vbo-admin-widget-wrapper').getAttribute('id');
626 const wrapper_el = document.getElementById(wrapper_id);
627 const id_provider = wrapper_el.querySelector('.vbo-door-access-control-setting[data-setting="id_provider"]').value;
628 const id_profile = wrapper_el.querySelector('.vbo-door-access-control-setting[data-setting="id_profile"]').value;
629 const id_device = buttonEl.closest('.vbo-widget-dac-device-listings-data').getAttribute('data-device-id');
630 const device_name = buttonEl.closest('.vbo-widget-dac-device-listings-data').getAttribute('data-device-name');
631 const add_listing_section = wrapper_el.querySelector('.vbo-widget-dac-add-listing-wrap');
632 const add_listing_select = wrapper_el.querySelector('select.vbo-door-access-control-add-listing-id');
633
634 // build modal buttons
635 let cancel_btn = document.createElement('button');
636 cancel_btn.setAttribute('type', 'button');
637 cancel_btn.classList.add('btn');
638 cancel_btn.innerText = <?php echo json_encode(JText::translate('VBANNULLA')); ?>;
639 cancel_btn.addEventListener('click', () => {
640 VBOCore.emitEvent('wdac-assign-listing-dismiss');
641 });
642
643 let apply_btn = document.createElement('button');
644 apply_btn.setAttribute('type', 'button');
645 apply_btn.classList.add('btn', 'btn-success');
646 apply_btn.innerText = <?php echo json_encode(JText::translate('VBAPPLY')); ?>;
647 apply_btn.addEventListener('click', (e) => {
648 // save the new device-listing connection
649 const listingId = add_listing_select.value;
650
651 // check if we have to connect a specific sub-unit index
652 let listingSubUnit = null;
653 let idParts = (listingId + '').split('-');
654 if (idParts[1]) {
655 listingSubUnit = idParts[1];
656 }
657
658 // disable button
659 e.target.disabled = true;
660
661 // start loading animation
662 VBOCore.emitEvent('wdac-assign-listing-loading');
663
664 // the widget method to call
665 let call_method = 'setDeviceListingConnection';
666
667 // make a request to set the device listing connection
668 VBOCore.doAjax(
669 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
670 {
671 widget_id: "<?php echo $this->getIdentifier(); ?>",
672 call: call_method,
673 return: 1,
674 provider: id_provider,
675 profile: id_profile,
676 device: id_device,
677 listing: listingId,
678 subunit: listingSubUnit,
679 wrapper: wrapper_id,
680 tmpl: "component"
681 },
682 (response) => {
683 // stop loading animation
684 VBOCore.emitEvent('wdac-assign-listing-loading');
685
686 try {
687 let obj_res = typeof response === 'string' ? JSON.parse(response) : response;
688 if (!obj_res.hasOwnProperty(call_method)) {
689 console.error('Unexpected JSON response', obj_res);
690 return false;
691 }
692
693 if (obj_res[call_method]['device_name']) {
694 // reload the provider with the new data updated
695 vboWidgetDACLoadProvider(wrapper_id, id_provider, id_profile, 'devices');
696
697 // reset listing selection
698 add_listing_select.value = '';
699 add_listing_select.dispatchEvent(new Event('change'));
700
701 // dismiss the modal
702 VBOCore.emitEvent('wdac-assign-listing-dismiss');
703 } else {
704 // re-enable button
705 e.target.disabled = false;
706 }
707 } catch(err) {
708 // log and display error
709 console.error('could not parse JSON response', err, response);
710 alert('could not parse JSON response');
711
712 // re-enable button
713 e.target.disabled = false;
714 }
715 },
716 (error) => {
717 // log and display error
718 console.error(error);
719 alert(error.responseText);
720
721 // stop loading animation
722 VBOCore.emitEvent('wdac-assign-listing-loading');
723
724 // re-enable button
725 e.target.disabled = false;
726 }
727 );
728 });
729
730 // display modal
731 let modalBody = VBOCore.displayModal({
732 suffix: 'wdac-assign-listing',
733 extra_class: 'vbo-modal-rounded vbo-modal-prompt',
734 title: device_name + ' - ' + <?php echo json_encode(JText::translate('VBO_CONNECTED_LISTINGS')); ?>,
735 body_prepend: true,
736 draggable: true,
737 footer_left: cancel_btn,
738 footer_right: apply_btn,
739 loading_event: 'wdac-assign-listing-loading',
740 dismiss_event: 'wdac-assign-listing-dismiss',
741 onDismiss: () => {
742 // move back the helper section
743 wrapper_el.querySelector('.vbo-widget-dac-add-listing-helper').append(add_listing_section);
744 },
745 });
746
747 // append helper to modal body
748 (modalBody[0] || modalBody).append(add_listing_section);
749
750 // do not proceed
751 return;
752 }
753
754 // delete device connected listing button
755 if (e.target.matches('.vbo-widget-dac-device-cn-listing') || e.target.closest('.vbo-widget-dac-device-cn-listing')) {
756 const buttonEl = !e.target.matches('.vbo-widget-dac-device-cn-listing') ? e.target.closest('.vbo-widget-dac-device-cn-listing') : e.target;
757 const wrapper_id = buttonEl.closest('.vbo-admin-widget-wrapper').getAttribute('id');
758 const wrapper_el = document.getElementById(wrapper_id);
759 const id_provider = wrapper_el.querySelector('.vbo-door-access-control-setting[data-setting="id_provider"]').value;
760 const id_profile = wrapper_el.querySelector('.vbo-door-access-control-setting[data-setting="id_profile"]').value;
761 const id_device = buttonEl.closest('.vbo-widget-dac-device-listings-data').getAttribute('data-device-id');
762 const listingId = buttonEl.getAttribute('data-listing-id');
763 const subunitId = buttonEl.getAttribute('data-subunit-id') || 0;
764
765 if (confirm(<?php echo json_encode(JText::translate('VBDELCONFIRM')); ?>)) {
766 // start loading animation
767 vboWidgetDACSetLoading(wrapper_id, true);
768
769 // the widget method to call
770 let call_method = 'unsetDeviceListingConnection';
771
772 // make a request to unset the device listing connection
773 VBOCore.doAjax(
774 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
775 {
776 widget_id: "<?php echo $this->getIdentifier(); ?>",
777 call: call_method,
778 return: 1,
779 provider: id_provider,
780 profile: id_profile,
781 device: id_device,
782 listing: listingId,
783 subunit: subunitId,
784 wrapper: wrapper_id,
785 tmpl: "component"
786 },
787 (response) => {
788 // stop loading animation
789 vboWidgetDACSetLoading(wrapper_id, false);
790
791 try {
792 let obj_res = typeof response === 'string' ? JSON.parse(response) : response;
793 if (!obj_res.hasOwnProperty(call_method)) {
794 console.error('Unexpected JSON response', obj_res);
795 return false;
796 }
797
798 // delete element
799 buttonEl.remove();
800
801 // reload the provider with the new data updated
802 vboWidgetDACLoadProvider(wrapper_id, id_provider, id_profile, 'devices');
803 } catch(err) {
804 // log and display error
805 console.error('could not parse JSON response', err, response);
806 alert('could not parse JSON response');
807 }
808 },
809 (error) => {
810 // log and display error
811 console.error(error);
812 alert(error.responseText);
813
814 // stop loading animation
815 vboWidgetDACSetLoading(wrapper_id, false);
816 }
817 );
818 }
819
820 // do not proceed
821 return;
822 }
823
824 // execute device capability button
825 if (e.target.matches('.vbo-dac-device-capability-execute-btn') || e.target.closest('.vbo-dac-device-capability-execute-btn')) {
826 const buttonEl = !e.target.matches('.vbo-dac-device-capability-execute-btn') ? e.target.closest('.vbo-dac-device-capability-execute-btn') : e.target;
827 const wrapper_id = buttonEl.closest('.vbo-admin-widget-wrapper').getAttribute('id');
828 const wrapper_el = document.getElementById(wrapper_id);
829 const id_provider = wrapper_el.querySelector('.vbo-door-access-control-setting[data-setting="id_provider"]').value;
830 const id_profile = wrapper_el.querySelector('.vbo-door-access-control-setting[data-setting="id_profile"]').value;
831 const device_el = wrapper_el.querySelector('.vbo-door-access-control-device-id');
832 const id_device = device_el.value;
833 const device_name = device_el.options[device_el.selectedIndex].text || '';
834 const id_capability = buttonEl.getAttribute('data-cap-id');
835 const capabilityNameEl = buttonEl.querySelector('.vbo-dac-device-capability-btn-name');
836 const capabilityName = capabilityNameEl.innerText;
837 const capabilityParamsHelper = wrapper_el.querySelector('.vbo-widget-dac-cap-params-wrap');
838 const capabilityParamsContent = capabilityParamsHelper.querySelector('.vbo-params-block');
839
840 // disable button to prevent double clicks
841 buttonEl.disabled = true;
842
843 // check if we have retry-data options to be used only once ("oo")
844 let retry_data_options = vbo_widget_dac_retry_data_oo;
845 if (vbo_widget_dac_retry_data_oo) {
846 vbo_widget_dac_retry_data_oo = null;
847 }
848
849 // start button animation
850 capabilityNameEl.innerHTML = '<?php VikBookingIcons::e('sync', 'fa-spin fa-fw'); ?>';
851
852 // the widget method to call
853 let call_method = 'executeDeviceCapability';
854
855 // make a request to execute the device capability
856 VBOCore.doAjax(
857 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
858 {
859 widget_id: "<?php echo $this->getIdentifier(); ?>",
860 call: call_method,
861 return: 1,
862 provider: id_provider,
863 profile: id_profile,
864 device: id_device,
865 capability: id_capability,
866 noparams: 1,
867 retry_data: retry_data_options,
868 wrapper: wrapper_id,
869 tmpl: "component"
870 },
871 (response) => {
872 // stop button animation
873 capabilityNameEl.innerHTML = '';
874 capabilityNameEl.innerText = capabilityName;
875
876 // re-enable button
877 buttonEl.disabled = false;
878
879 // init params modal body
880 var paramsModalBody;
881
882 try {
883 let obj_res = typeof response === 'string' ? JSON.parse(response) : response;
884 if (!obj_res.hasOwnProperty(call_method)) {
885 console.error('Unexpected JSON response', obj_res);
886 return false;
887 }
888
889 if (obj_res[call_method]['result_html']) {
890 // capability was executed, render the HTML result
891
892 // set capability name
893 wrapper_el.querySelector('.vbo-dac-capability-result-name').innerText = capabilityName;
894
895 // set capability result
896 wrapper_el.querySelector('.vbo-dac-capability-result-body').innerHTML = obj_res[call_method]['result_html'];
897
898 // switch visible section
899 wrapper_el.querySelector('.vbo-dac-capabilities-list').classList.remove('vbo-dac-cap-section-active');
900 wrapper_el.querySelector('.vbo-dac-capability-result-wrap').classList.add('vbo-dac-cap-section-active');
901 wrapper_el.querySelector('.vbo-dac-device-capability-sections').classList.add('vbo-dac-cap-section-active-result');
902
903 if (obj_res[call_method]['device_updated']) {
904 // set data-attribute flag to navigation back button
905 let backBtn = wrapper_el.querySelector('.vbo-dac-capability-result-back');
906 if (backBtn) {
907 backBtn.setAttribute('data-device-updated', 1);
908 }
909 }
910 } else if (obj_res[call_method]['capability_params']) {
911 // capability execution provides parameters
912 let cancel_btn = document.createElement('button');
913 cancel_btn.setAttribute('type', 'button');
914 cancel_btn.classList.add('btn');
915 cancel_btn.innerText = <?php echo json_encode(JText::translate('VBANNULLA')); ?>;
916 cancel_btn.addEventListener('click', () => {
917 VBOCore.emitEvent('wdac-capability-params-dismiss');
918 });
919
920 let apply_btn = document.createElement('button');
921 apply_btn.setAttribute('type', 'button');
922 apply_btn.classList.add('btn', 'btn-primary');
923 apply_btn.innerText = capabilityName;
924 apply_btn.addEventListener('click', () => {
925 // modal params start loading animation
926 VBOCore.emitEvent('wdac-capability-params-loading');
927
928 // collect settings from capability parameters
929 let capSettings = {};
930 (paramsModalBody[0] || paramsModalBody).querySelectorAll('[name*="capability_settings[' + id_capability + ']"]').forEach((settingEl) => {
931 let settingName = settingEl.getAttribute('name');
932 let reSafe = (id_capability + '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
933 let reFull = new RegExp(`^capability_settings\\[${reSafe}\\]`);
934 let paramName = settingName.replace(reFull, '').replace(/^\[|\]$/g, '');
935 if (settingEl.matches('select') && settingEl.getAttribute('multiple')) {
936 // array-value element
937 let selectedValues = Array.from(settingEl.options).filter((option) => option.selected).map((option) => option.value);
938 capSettings[paramName] = selectedValues;
939 } else {
940 // single-value element
941 capSettings[paramName] = settingEl.value;
942 }
943 });
944
945 // make a request to execute the device capability
946 VBOCore.doAjax(
947 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
948 {
949 widget_id: "<?php echo $this->getIdentifier(); ?>",
950 call: call_method,
951 return: 1,
952 provider: id_provider,
953 profile: id_profile,
954 device: id_device,
955 capability: id_capability,
956 capability_settings: capSettings,
957 wrapper: wrapper_id,
958 tmpl: "component"
959 },
960 (response) => {
961 // modal params stop loading animation
962 VBOCore.emitEvent('wdac-capability-params-loading');
963
964 // dismiss modal params
965 VBOCore.emitEvent('wdac-capability-params-dismiss');
966
967 try {
968 let obj_res = typeof response === 'string' ? JSON.parse(response) : response;
969 if (!obj_res.hasOwnProperty(call_method)) {
970 console.error('Unexpected JSON response', obj_res);
971 return false;
972 }
973
974 if (obj_res[call_method]['result_html']) {
975 // capability was executed, render the HTML result
976
977 // set capability name
978 wrapper_el.querySelector('.vbo-dac-capability-result-name').innerText = capabilityName;
979
980 // set capability result
981 wrapper_el.querySelector('.vbo-dac-capability-result-body').innerHTML = obj_res[call_method]['result_html'];
982
983 // switch visible section
984 wrapper_el.querySelector('.vbo-dac-capabilities-list').classList.remove('vbo-dac-cap-section-active');
985 wrapper_el.querySelector('.vbo-dac-capability-result-wrap').classList.add('vbo-dac-cap-section-active');
986 wrapper_el.querySelector('.vbo-dac-device-capability-sections').classList.add('vbo-dac-cap-section-active-result');
987 } else {
988 // capability was executed, display the result text
989 alert(obj_res[call_method]['result_text'] || (capabilityName + ' operation completed'));
990 }
991 } catch(err) {
992 // log and display error
993 console.error('could not parse JSON response', err, response);
994 alert('could not parse JSON response');
995 }
996 },
997 (error) => {
998 // log and display error
999 console.error(error);
1000 alert(error.responseText);
1001
1002 // modal params stop loading animation
1003 VBOCore.emitEvent('wdac-capability-params-loading');
1004 }
1005 );
1006 });
1007
1008 // display modal
1009 let paramsModalBody = VBOCore.displayModal({
1010 suffix: 'wdac-capability-params',
1011 extra_class: 'vbo-modal-rounded',
1012 title: capabilityName + ' - ' + device_name,
1013 body_prepend: true,
1014 draggable: true,
1015 footer_left: cancel_btn,
1016 footer_right: apply_btn,
1017 loading_event: 'wdac-capability-params-loading',
1018 dismiss_event: 'wdac-capability-params-dismiss',
1019 onDismiss: () => {
1020 // empty the helper element
1021 capabilityParamsContent.innerHTML = '';
1022 // move back the helper section
1023 wrapper_el.querySelector('.vbo-widget-dac-cap-params-helper').append(capabilityParamsHelper);
1024 },
1025 });
1026
1027 // append helper to modal body
1028 jQuery(capabilityParamsContent).html(obj_res[call_method]['capability_params']);
1029 jQuery(capabilityParamsHelper).appendTo(paramsModalBody);
1030 } else {
1031 // capability was executed, display the result text
1032 alert(obj_res[call_method]['result_text'] || (capabilityName + ' operation completed'));
1033
1034 if (obj_res[call_method]['device_updated']) {
1035 // reload widget with the selected device upon device update
1036 vboWidgetDACLoadProvider(wrapper_id, id_provider, id_profile, 'dashboard', id_device);
1037 }
1038 }
1039 } catch(err) {
1040 // log and display error
1041 console.error('could not parse JSON response', err, response);
1042 alert('could not parse JSON response');
1043 }
1044 },
1045 (error) => {
1046 // log and display error
1047 console.error(error);
1048 alert(error.responseText);
1049
1050 // stop button animation
1051 capabilityNameEl.innerHTML = '';
1052 capabilityNameEl.innerText = capabilityName;
1053
1054 // re-enable button
1055 buttonEl.disabled = false;
1056 }
1057 );
1058
1059 // do not proceed
1060 return;
1061 }
1062
1063 // delete profile button
1064 if (e.target.matches('.vbo-dac-delete-profile-btn') || e.target.closest('.vbo-dac-delete-profile-btn')) {
1065 const buttonEl = !e.target.matches('.vbo-dac-delete-profile-btn') ? e.target.closest('.vbo-dac-delete-profile-btn') : e.target;
1066 const wrapper_id = buttonEl.closest('.vbo-admin-widget-wrapper').getAttribute('id');
1067 const wrapper_el = document.getElementById(wrapper_id);
1068 const id_provider = wrapper_el.querySelector('.vbo-door-access-control-setting[data-setting="id_provider"]').value;
1069 const id_profile = wrapper_el.querySelector('.vbo-door-access-control-setting[data-setting="id_profile"]').value;
1070
1071 if (!id_profile || id_profile == '-1') {
1072 // cannot delete an empty profile record ID
1073 return;
1074 }
1075
1076 if (confirm(<?php echo json_encode(JText::translate('VBDELCONFIRM')); ?>)) {
1077 // start loading animation and disable button
1078 buttonEl.innerHTML = '<?php VikBookingIcons::e('sync', 'fa-spin fa-fw icn-nomargin'); ?>';
1079 buttonEl.disabled = true;
1080
1081 // make a request
1082 VBOCore.doAjax(
1083 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
1084 {
1085 widget_id: "<?php echo $this->getIdentifier(); ?>",
1086 call: 'deleteProfileRecord',
1087 return: 1,
1088 provider: id_provider,
1089 profile: id_profile,
1090 wrapper: wrapper_id,
1091 tmpl: "component"
1092 },
1093 (response) => {
1094 // we trust the response was successful and we reload the provider
1095 vboWidgetDACLoadProvider(wrapper_id, id_provider, id_profile);
1096 },
1097 (error) => {
1098 // log and display error
1099 console.error(error);
1100 alert(error.responseText);
1101
1102 // stop loading animation and re-enable button
1103 buttonEl.innerHTML = '<?php VikBookingIcons::e('trash'); ?>';
1104 buttonEl.disabled = false;
1105 }
1106 );
1107 }
1108
1109 // do not proceed
1110 return;
1111 }
1112
1113 // execute device capability back button
1114 if (e.target.matches('.vbo-dac-capability-result-back') || e.target.closest('.vbo-dac-capability-result-back')) {
1115 const buttonEl = !e.target.matches('.vbo-dac-capability-result-back') ? e.target.closest('.vbo-dac-capability-result-back') : e.target;
1116 const wrapper_id = buttonEl.closest('.vbo-admin-widget-wrapper').getAttribute('id');
1117 const wrapper_el = document.getElementById(wrapper_id);
1118
1119 // switch visible section
1120 wrapper_el.querySelector('.vbo-dac-capability-result-wrap').classList.remove('vbo-dac-cap-section-active');
1121 wrapper_el.querySelector('.vbo-dac-capabilities-list').classList.add('vbo-dac-cap-section-active');
1122 wrapper_el.querySelector('.vbo-dac-device-capability-sections').classList.remove('vbo-dac-cap-section-active-result');
1123
1124 // empty capability name
1125 wrapper_el.querySelector('.vbo-dac-capability-result-name').innerText = '';
1126
1127 // empty result
1128 wrapper_el.querySelector('.vbo-dac-capability-result-body').innerHTML = '';
1129
1130 // check if device was updated
1131 if (buttonEl.getAttribute('data-device-updated') == '1') {
1132 // unset data-attribute
1133 buttonEl.setAttribute('data-device-updated', 0);
1134 // reload widget with the selected device upon device update
1135 vboWidgetDACLoadProvider(wrapper_id, buttonEl.getAttribute('data-provider-id'), buttonEl.getAttribute('data-profile-id'), 'dashboard', buttonEl.getAttribute('data-device-id'));
1136 }
1137
1138 // do not proceed
1139 return;
1140 }
1141 }
1142 </script>
1143 <?php
1144 }
1145 ?>
1146 <script>
1147 // store widget options, if any
1148 var vbo_widget_dac_options_oo = <?php echo json_encode($this->getOptions()); ?>;
1149
1150 // start container for retry-data options
1151 var vbo_widget_dac_retry_data_oo = null;
1152
1153 VBOCore.DOMLoaded(() => {
1154 let widget_wrapper = document.querySelector('#<?php echo $wrapper_id; ?>');
1155
1156 /**
1157 * Add event listener for the provider alias selection.
1158 */
1159 widget_wrapper
1160 .querySelector('.vbo-door-access-control-setting[data-setting="id_provider"]')
1161 .addEventListener('change', (e) => {
1162 vboWidgetDACLoadProvider('<?php echo $wrapper_id; ?>', e.target.value);
1163 });
1164
1165 /**
1166 * Add body click event delegation for elements that will be later added to the DOM.
1167 */
1168 if (!VBOCore.wasEventDelegated('dac.body')) {
1169 document.body.addEventListener('click', vbo_w_dac_click_delegation);
1170 VBOCore.setEventDelegated('dac.body');
1171 }
1172
1173 <?php
1174 if ($js_intvals_id) {
1175 // widget can be dismissed through the modal
1176 ?>
1177 document.addEventListener(VBOCore.widget_modal_dismissed + '<?php echo $js_intvals_id; ?>', (e) => {
1178 // remove body click events delegation for this widget
1179 document.body.removeEventListener('click', vbo_w_dac_click_delegation);
1180 VBOCore.unsetEventDelegated('dac.body');
1181 });
1182 <?php
1183 }
1184
1185 if (!empty($defaultProvider)) {
1186 // trigger instant loading upon document ready
1187 ?>
1188 vboWidgetDACLoadProvider('<?php echo $wrapper_id; ?>', '<?php echo $defaultProvider; ?>', '<?php echo $defaultProfile; ?>', '<?php echo $defaultTab; ?>', '<?php echo $defaultDevice; ?>');
1189 <?php
1190 }
1191 ?>
1192 });
1193 </script>
1194 <?php
1195 }
1196
1197 /**
1198 * Custom method for this widget only.
1199 * Loads the settings for the given provider alias.
1200 *
1201 * @return array
1202 */
1203 public function loadProviderSettings()
1204 {
1205 $app = JFactory::getApplication();
1206
1207 $wrapper = $app->input->getString('wrapper', '');
1208 $provider = $app->input->getString('provider', '');
1209 $profile = $app->input->getUInt('profile', 0);
1210 $id_device = $app->input->getString('id_device', '');
1211 $tab = $app->input->getString('tab', '');
1212
1213 if (empty($provider)) {
1214 VBOHttpDocument::getInstance($app)->close(400, 'Missing door access control provider identifier.');
1215 }
1216
1217 // access the door access factory object
1218 $factory = VBOFactory::getDoorAccessControl();
1219
1220 // get the requested integration provider
1221 $integration = $factory->getIntegrationProvider($provider);
1222
1223 if (!$integration) {
1224 VBOHttpDocument::getInstance($app)->close(404, 'Invalid door access control provider identifier.');
1225 }
1226
1227 if (empty($this->widgetSettings['id_provider']) || $this->widgetSettings['id_provider'] != $integration->getAlias()) {
1228 // set provider alias in widget settings
1229 $this->widgetSettings['id_provider'] = $integration->getAlias();
1230 // set no active profile in widget settings
1231 $this->widgetSettings['id_profile'] = null;
1232 // set no active device in widget settings
1233 $this->widgetSettings['id_device'] = null;
1234 }
1235
1236 // determine the optional profile ID to get, if any
1237 $idProfile = (int) ($profile ?: $this->widgetSettings['id_profile'] ?? 0);
1238
1239 // load the active integration profiles, if any
1240 $profiles = $factory->loadIntegrationRecords($integration->getAlias(), $idProfile);
1241
1242 if ($profiles) {
1243 if ($profiles[0]['id'] != ($this->widgetSettings['id_profile'] ?? null)) {
1244 // set no active device in widget settings
1245 $this->widgetSettings['id_device'] = null;
1246 }
1247 // set active profile in widget settings
1248 $this->widgetSettings['id_profile'] = $profiles[0]['id'];
1249
1250 // inject first profile record within the integration
1251 $integration->setProfileRecord($profiles[0]);
1252 }
1253
1254 // determine the optional device ID to get, if any
1255 $loadIdDevice = (string) ($id_device ?: $this->widgetSettings['id_device'] ?? '');
1256
1257 // ensure the device ID exists within the current profile record
1258 if ($loadIdDevice && !$integration->deviceExists($loadIdDevice)) {
1259 // invalid device ID
1260 $loadIdDevice = '';
1261 }
1262
1263 if ($loadIdDevice) {
1264 // set active device in widget settings
1265 $this->widgetSettings['id_device'] = $loadIdDevice;
1266 }
1267
1268 // update widget settings
1269 $this->updateSettings(json_encode($this->widgetSettings));
1270
1271 // access the integration icon
1272 $integrationIcon = $integration->getIcon() ?: '<i class="' . VikBookingIcons::i('lock') . '"></i>';
1273
1274 // determine the active tab to display
1275 $hasParams = (bool) count($integration->getParams());
1276 $hasSettings = (bool) count($integration->getSettings());
1277 $hasDevices = (bool) count($integration->getDevices());
1278 if ($hasParams && !$hasSettings) {
1279 // default to settings tab
1280 $activeTab = 'settings';
1281 } elseif ($hasSettings && !$hasDevices) {
1282 // default to devices tab
1283 $activeTab = 'devices';
1284 } else {
1285 // default to dashboard tab
1286 $activeTab = 'dashboard';
1287 }
1288 $activeTab = $tab ?: $activeTab;
1289
1290 // load all listing names and related IDs
1291 $listingsPool = VikBooking::getAvailabilityInstance(true)->loadRooms([], 0, true);
1292 $listingsPool = array_combine(array_column($listingsPool, 'id'), array_column($listingsPool, 'name'));
1293
1294 // start output buffering
1295 ob_start();
1296
1297 ?>
1298 <div class="vbo-widget-door-access-control-content">
1299 <div class="vbo-widget-door-access-control-tabs">
1300 <?php
1301 if ($hasSettings && $hasDevices) {
1302 ?>
1303 <div class="vbo-widget-door-access-control-tab<?php echo $activeTab == 'dashboard' ? ' vbo-widget-tab-active' : ''; ?>" data-type="dashboard">
1304 <span><?php VikBookingIcons::e('home'); ?> <?php echo JText::translate('VBMENUDASHBOARD'); ?></span>
1305 </div>
1306 <?php
1307 }
1308 if ($hasSettings) {
1309 ?>
1310 <div class="vbo-widget-door-access-control-tab<?php echo $activeTab == 'devices' ? ' vbo-widget-tab-active' : ''; ?>" data-type="devices">
1311 <span><?php VikBookingIcons::e('microchip'); ?> <?php echo JText::translate('VBO_DEVICES'); ?></span>
1312 </div>
1313 <?php
1314 }
1315 ?>
1316 <div class="vbo-widget-door-access-control-tab<?php echo $activeTab == 'settings' ? ' vbo-widget-tab-active' : ''; ?>" data-type="settings">
1317 <span><?php VikBookingIcons::e('cogs'); ?> <?php echo JText::translate('VBMENUTWELVE'); ?></span>
1318 </div>
1319 </div>
1320 <div class="vbo-widget-door-access-control-panels">
1321 <div class="vbo-widget-door-access-control-panel" data-type="dashboard" style="<?php echo $activeTab != 'dashboard' ? 'display: none;' : ''; ?>">
1322 <div class="vbo-dac-integration-head">
1323 <span class="vbo-dac-integration-icon">
1324 <?php
1325 if (preg_match('/^http/', (string) $integrationIcon)) {
1326 // image URI is expected
1327 ?>
1328 <img src="<?php echo JHtml::fetch('esc_attr', $integrationIcon); ?>" />
1329 <?php
1330 } else {
1331 // icon HTML is expected
1332 echo $integrationIcon;
1333 }
1334 ?>
1335 </span>
1336 <span><?php echo $profiles[0]['name'] ?? ''; ?></span>
1337 </div>
1338 <div class="vbo-dac-integration-body">
1339 <?php
1340 if ($hasDevices) {
1341 // build device elements
1342 $deviceElements = array_map(function($device) {
1343 $icon = $device->getIcon();
1344 $icon = $icon && !preg_match('/^http/', (string) $icon) ? $icon : '';
1345 return [
1346 'id' => $device->getID(),
1347 'name' => sprintf('%s (%s)', $device->getName(), $device->getID()),
1348 'html' => $icon,
1349 ];
1350 }, $integration->getDevices());
1351 ?>
1352 <div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact vbo-dac-devices-info">
1353 <div class="vbo-params-wrap">
1354 <div class="vbo-params-container">
1355 <div class="vbo-params-block">
1356 <div class="vbo-param-container">
1357 <div class="vbo-param-label"><?php echo JText::translate('VBTRKDEVICE'); ?></div>
1358 <div class="vbo-param-setting">
1359 <?php
1360 echo VikBooking::getVboApplication()->renderElementsDropDown([
1361 'placeholder' => JText::translate('VBTRKDEVICE'),
1362 'attributes' => [
1363 'class' => 'vbo-door-access-control-device-id',
1364 'onchange' => 'vboWidgetDACSwitchDeviceID("' . $wrapper . '")',
1365 ],
1366 'selected_value' => $loadIdDevice,
1367 'style_selection' => true,
1368 'default_selection_icon' => VikBookingIcons::i('fingerprint'),
1369 ], $deviceElements);
1370 ?>
1371 </div>
1372 </div>
1373 </div>
1374 </div>
1375 </div>
1376 </div>
1377 <div class="vbo-dac-device-capabilities">
1378 <div class="vbo-dac-device-capability-sections">
1379 <div class="vbo-dac-capabilities-list vbo-dac-cap-section-active">
1380 <?php
1381 if ($loadIdDevice) {
1382 // render device dashboard
1383 $integrationDevice = $integration->getDeviceById($loadIdDevice);
1384
1385 // iterate all device capabilities
1386 foreach ($integrationDevice->getCapabilities() as $cap) {
1387 $capIcon = (string) $cap->getIcon();
1388 $isHtmlIcon = false;
1389 ?>
1390 <div class="vbo-dac-device-capability">
1391 <div class="vbo-dac-device-capability-head">
1392 <div class="vbo-dac-device-capability-icn">
1393 <?php
1394 if (preg_match('/^http/', (string) $capIcon)) {
1395 // image URI is expected
1396 ?>
1397 <img src="<?php echo JHtml::fetch('esc_attr', $capIcon); ?>" />
1398 <?php
1399 } else {
1400 // icon HTML is expected
1401 echo $capIcon;
1402 $isHtmlIcon = true;
1403 }
1404 ?>
1405 </div>
1406 <div class="vbo-dac-device-capability-info">
1407 <div class="vbo-dac-device-capability-name">
1408 <span><?php echo $cap->getTitle(); ?></span>
1409 </div>
1410 <div class="vbo-dac-device-capability-descr">
1411 <?php echo $cap->getDescription(); ?>
1412 </div>
1413 </div>
1414 </div>
1415 <?php
1416 if ($cap->getCallback()) {
1417 ?>
1418 <div class="vbo-dac-device-capability-action">
1419 <button type="button" class="btn vbo-dac-device-capability-execute-btn" data-cap-id="<?php echo $cap->getID(); ?>" data-callback="<?php echo is_string($cap->getCallback()) ? $cap->getCallback() : ''; ?>"><?php echo $isHtmlIcon ? $capIcon : ''; ?><span class="vbo-dac-device-capability-btn-name"><?php echo $cap->getTitle(); ?></span></button>
1420 </div>
1421 <?php
1422 }
1423 ?>
1424 </div>
1425 <?php
1426 }
1427 } else {
1428 ?>
1429 <p class="info"><?php echo JText::translate('VBO_CHOOSE_DEVICE'); ?></p>
1430 <?php
1431 }
1432 ?>
1433 </div>
1434 <div class="vbo-dac-capability-result-wrap">
1435 <div class="vbo-dac-capability-result-head">
1436 <span class="vbo-dac-capability-result-back" data-provider-id="<?php echo JHtml::fetch('esc_attr', $integration->getAlias()); ?>" data-profile-id="<?php echo JHtml::fetch('esc_attr', $integration->getProfileID()); ?>" data-device-id="<?php echo JHtml::fetch('esc_attr', (($integrationDevice ?? null) ? $integrationDevice->getID() : '')); ?>"><?php VikBookingIcons::e('arrow-left'); ?> <?php echo JText::translate('VBBACK'); ?></span>
1437 <span class="vbo-dac-capability-result-name"></span>
1438 </div>
1439 <div class="vbo-dac-capability-result-body"></div>
1440 </div>
1441 </div>
1442 </div>
1443 <?php
1444 } else {
1445 ?>
1446 <p class="warn"><?php echo JText::translate('VBO_DEVICES'); ?>: 0</p>
1447 <?php
1448 }
1449 ?>
1450 </div>
1451 </div>
1452 <div class="vbo-widget-door-access-control-panel" data-type="devices" style="<?php echo $activeTab != 'devices' ? 'display: none;' : ''; ?>">
1453 <div class="vbo-dac-integration-head">
1454 <span class="vbo-dac-integration-icon">
1455 <?php
1456 if (preg_match('/^http/', (string) $integrationIcon)) {
1457 // image URI is expected
1458 ?>
1459 <img src="<?php echo JHtml::fetch('esc_attr', $integrationIcon); ?>" />
1460 <?php
1461 } else {
1462 // icon HTML is expected
1463 echo $integrationIcon;
1464 }
1465 ?>
1466 </span>
1467 <span><?php echo $profiles[0]['name'] ?? ''; ?></span>
1468 </div>
1469 <div class="vbo-dac-integration-body">
1470 <div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact vbo-dac-devices-info">
1471 <div class="vbo-params-wrap">
1472 <div class="vbo-params-container">
1473 <div class="vbo-params-block">
1474 <div class="vbo-param-container">
1475 <div class="vbo-param-label"><?php echo JText::translate('VBO_DEVICES'); ?></div>
1476 <div class="vbo-param-setting">
1477 <span class="badge badge-<?php echo $hasDevices ? 'info' : 'error'; ?>"><?php echo count($integration->getDevices()); ?></span>
1478 <button type="button" class="btn vbo-config-btn vbo-widget-dac-update-devices"><?php VikBookingIcons::e('sync'); ?> <?php echo JText::translate('VBO_SYNC'); ?></button>
1479 </div>
1480 </div>
1481 </div>
1482 </div>
1483 </div>
1484 </div>
1485
1486 <?php
1487 if ($hasDevices) {
1488 // display the current devices
1489 ?>
1490 <div class="vbo-dac-devices-wrap">
1491 <?php
1492 foreach ($integration->getDevices() as $device) {
1493 $deviceIcon = $device->getIcon();
1494 ?>
1495 <div class="vbo-dac-device-wrap">
1496 <div class="vbo-dac-device-head">
1497 <div class="vbo-dac-device-info">
1498 <div class="vbo-dac-device-data">
1499 <span class="vbo-dac-device-icn"><?php
1500 if (preg_match('/^http/', (string) $deviceIcon)) {
1501 // image URI is expected
1502 ?>
1503 <img src="<?php echo JHtml::fetch('esc_attr', $deviceIcon); ?>" />
1504 <?php
1505 } else {
1506 // icon HTML is expected
1507 echo $deviceIcon ?: '<i class="' . VikBookingIcons::i('microchip') . '"></i>';
1508 }
1509 ?></span>
1510 <div class="vbo-dac-device-subinfo">
1511 <div class="vbo-dac-device-name"><?php echo $device->getName(); ?></div>
1512 <span class="vbo-dac-device-id"><?php echo $device->getID(); ?></span>
1513 <?php
1514 if ($device->getModel() || $device->getDescription()) {
1515 ?>
1516 <span class="vbo-dac-device-model"><?php echo implode(' - ', array_filter([$device->getModel(), $device->getDescription()])); ?></span>
1517 <?php
1518 }
1519 ?>
1520 </div>
1521 </div>
1522 </div>
1523 <?php
1524 if ($device->getBatteryLevel() !== null) {
1525 if ($device->getBatteryLevel() > 65) {
1526 $battery_class = 'success';
1527 } elseif ($device->getBatteryLevel() > 35) {
1528 $battery_class = 'warning';
1529 } else {
1530 $battery_class = 'error';
1531 }
1532 ?>
1533 <div class="vbo-dac-device-battery">
1534 <span class="vbo-dac-device-battery-level vbo-dac-level-<?php echo $battery_class; ?>"><?php VikBookingIcons::e('battery-full'); ?> <?php echo floor($device->getBatteryLevel()); ?>%</span>
1535 </div>
1536 <?php
1537 }
1538 ?>
1539 </div>
1540 <div class="vbo-dac-device-content vbo-widget-dac-device-listings-data" data-device-id="<?php echo $device->getID(); ?>" data-device-name="<?php echo JHtml::fetch('esc_attr', $device->getName()); ?>">
1541 <div class="vbo-dac-device-listings">
1542 <div class="vbo-dac-device-listings-count">
1543 <span><?php echo JText::translate('VBO_CONNECTED_LISTINGS'); ?> (<?php echo $device->countConnectedListings(); ?>)</span>
1544 </div>
1545 <div class="vbo-dac-device-listings-list">
1546 <?php
1547 foreach ($device->getConnectedListings() as $listingId) {
1548 // check if the listing was connected at subunit-level
1549 $listingSubunitIds = $device->getConnectedListingSubunits($listingId);
1550 if ($listingSubunitIds) {
1551 // display only the listing subunit connections
1552 foreach ($listingSubunitIds as $subunitId) {
1553 ?>
1554 <span class="vbo-widget-dac-device-cn-listing" data-listing-id="<?php echo $listingId; ?>" data-subunit-id="<?php echo $subunitId; ?>"><?php VikBookingIcons::e('home'); ?><?php echo sprintf('%s #%d', ($listingsPool[$listingId] ?? $listingId), $subunitId); ?></span>
1555 <?php
1556 }
1557 } else {
1558 // display the listing connection
1559 ?>
1560 <span class="vbo-widget-dac-device-cn-listing" data-listing-id="<?php echo $listingId; ?>"><?php VikBookingIcons::e('home'); ?><?php echo $listingsPool[$listingId] ?? $listingId; ?></span>
1561 <?php
1562 }
1563 }
1564 ?>
1565 </div>
1566 </div>
1567 <div class="vbo-dac-device-actions">
1568 <div class="vbo-dac-device-action">
1569 <button type="button" class="btn vbo-widget-dac-device-add-listing"><?php VikBookingIcons::e('plus-circle'); ?> <?php echo JText::translate('VBCONFIGCLOSINGDATEADD'); ?></button>
1570 </div>
1571 </div>
1572 </div>
1573
1574 </div>
1575 <?php
1576 }
1577 ?>
1578 </div>
1579 <?php
1580 }
1581 ?>
1582 </div>
1583 </div>
1584 <div class="vbo-widget-door-access-control-panel" data-type="settings" style="<?php echo $activeTab != 'settings' ? 'display: none;' : ''; ?>">
1585 <div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact">
1586 <div class="vbo-params-wrap">
1587 <div class="vbo-params-container">
1588 <div class="vbo-params-block">
1589 <?php
1590 if ($profiles) {
1591 // some profile records exist
1592 ?>
1593 <div class="vbo-param-container">
1594 <div class="vbo-param-label"><?php echo JText::translate('VBO_PROFILE_SETTINGS'); ?></div>
1595 <div class="vbo-param-setting">
1596 <select class="vbo-door-access-control-setting" data-setting="id_profile" onchange="vboWidgetDACSwitchProfileSettings('<?php echo $wrapper; ?>');">
1597 <?php
1598 foreach ($profiles as $profile) {
1599 ?>
1600 <option value="<?php echo $profile['id']; ?>"<?php echo $profile['id'] == $this->widgetSettings['id_profile'] ? ' selected="selected"' : ''; ?>><?php echo $profile['name']; ?></option>
1601 <?php
1602 }
1603 ?>
1604 <option value="-1">- <?php echo JText::translate('VBO_PROFILE_NEW'); ?></option>
1605 </select>
1606 <?php
1607 if (!empty($this->widgetSettings['id_profile'])) {
1608 ?>
1609 <span class="vbo-dac-delete-profile-wrap">
1610 <button class="btn btn-small btn-danger vbo-dac-delete-profile-btn"><?php VikBookingIcons::e('trash', 'icn-nomargin'); ?></button>
1611 </span>
1612 <?php
1613 }
1614 ?>
1615 </div>
1616 </div>
1617 <?php
1618 }
1619 ?>
1620 <div class="vbo-param-container">
1621 <div class="vbo-param-label"><?php echo JText::translate('VBO_PROFILE_NAME'); ?></div>
1622 <div class="vbo-param-setting">
1623 <input class="vbo-door-access-control-setting" type="text" value="<?php echo JHtml::fetch('esc_attr', $integration->getProfileName() ?: ''); ?>" data-setting="profile_name" maxlength="64" />
1624 </div>
1625 </div>
1626 <div class="vbo-param-container">
1627 <div class="vbo-param-label"><?php echo JText::translate('VBO_PASSCODE_GENERATION'); ?></div>
1628 <div class="vbo-param-setting">
1629 <div class="vbo-dac-gen-type-period">
1630 <select class="vbo-door-access-control-setting" data-setting="gentype" onchange="vboWidgetDACSwitchGenType('<?php echo $wrapper; ?>');">
1631 <option value=""></option>
1632 <option value="booking"<?php echo $integration->getProfileGenerationType() == 'booking' && $integration->hasProfileRecord() ? ' selected="selected"' : ''; ?>><?php echo JText::translate('VBO_AT_TIME_BOOKING'); ?></option>
1633 <option value="precheckin"<?php echo $integration->getProfileGenerationType() == 'precheckin' ? ' selected="selected"' : ''; ?>><?php echo JText::translate('VBO_PRECHECKIN_COMPLETED'); ?></option>
1634 <option value="checkin"<?php echo $integration->getProfileGenerationType() == 'checkin' ? ' selected="selected"' : ''; ?>><?php echo JText::translate('VBO_BEFORE_CHECKIN'); ?></option>
1635 <option value="disabled"<?php echo $integration->getProfileGenerationType() == 'disabled' ? ' selected="selected"' : ''; ?>><?php echo JText::translate('VBPARAMPRICECALENDARDISABLED'); ?></option>
1636 </select>
1637 <select class="vbo-door-access-control-setting" data-setting="genperiod" style="<?php echo $integration->getProfileGenerationType() != 'checkin' ? 'display: none;' : ''; ?>">
1638 <?php
1639 $genPeriodHoursSingular = JText::translate('VBO_HOUR');
1640 $genPeriodHoursPlural = JText::translate('VBCONFIGONETENEIGHT');
1641 $hoursIntvals = array_merge(range(0, 6), [12, 24]);
1642 foreach ($hoursIntvals as $h) {
1643 $curGenPeriodVal = sprintf('%dH', $h);
1644 $curGenPeriodTxt = sprintf('%d %s', $h, ($h === 1 ? $genPeriodHoursSingular : $genPeriodHoursPlural));
1645 ?>
1646 <option value="<?php echo $curGenPeriodVal; ?>"<?php echo $integration->getProfileGenerationPeriod() == $curGenPeriodVal ? ' selected="selected"' : ''; ?>><?php echo $curGenPeriodTxt; ?></option>
1647 <?php
1648 }
1649 ?>
1650 </select>
1651 </div>
1652 </div>
1653 </div>
1654 </div>
1655 <div class="vbo-params-block">
1656 <?php
1657 echo VBOParamsRendering::getInstance(
1658 $integration->getParams(),
1659 $integration->getSettings()
1660 )->setInputName('integration[' . $integration->getAlias() . ']')->getHtml();
1661 ?>
1662 <div class="vbo-param-container">
1663 <div class="vbo-param-label">&nbsp;</div>
1664 <div class="vbo-param-setting">
1665 <button type="button" class="btn btn-success vbo-btn-wide" onclick="vboWidgetDACSaveProfileSettings('<?php echo $wrapper; ?>');"><?php echo JText::translate('VBSAVE'); ?></button>
1666 </div>
1667 </div>
1668 </div>
1669 </div>
1670 </div>
1671 </div>
1672 </div>
1673 </div>
1674 </div>
1675 <?php
1676
1677 // get the HTML buffer
1678 $html_content = ob_get_contents();
1679 ob_end_clean();
1680
1681 // return an associative array of values
1682 return [
1683 'html' => $html_content,
1684 'tot_profiles' => count($profiles),
1685 ];
1686 }
1687
1688 /**
1689 * Custom method for this widget only.
1690 * Saves or updates the settings for a given provider and profile.
1691 *
1692 * @return array
1693 */
1694 public function saveProviderSettings()
1695 {
1696 $app = JFactory::getApplication();
1697
1698 $wrapper = $app->input->getString('wrapper', '');
1699 $profile = $app->input->get('profile', [], 'array');
1700 $settings = $app->input->get('settings', [], 'array');
1701
1702 if (empty($profile['id_provider'])) {
1703 VBOHttpDocument::getInstance($app)->close(400, 'Missing door access control provider identifier for saving the settings.');
1704 }
1705
1706 // access the door access factory object
1707 $factory = VBOFactory::getDoorAccessControl();
1708
1709 // get the requested integration provider
1710 $integration = $factory->getIntegrationProvider($profile['id_provider']);
1711
1712 if (!$integration) {
1713 VBOHttpDocument::getInstance($app)->close(404, 'Invalid door access control provider identifier.');
1714 }
1715
1716 // determine if we are updating an existing profile ID or if we are creating a new one (0 or -1)
1717 $idProfile = (int) ($profile['id_profile'] ?? 0);
1718 $idProfile = $idProfile > 0 ? $idProfile : 0;
1719
1720 // normalize default provider settings
1721 $defaultSettings = array_combine(array_keys($integration->getParams()), array_map(function($paramName) {
1722 // param settings will get an empty string by default to support checkbox or other param types
1723 return '';
1724 }, array_keys($integration->getParams())));
1725
1726 // build record options
1727 $recordOptions = [
1728 'id_profile' => $idProfile,
1729 'profile_name' => $profile['profile_name'] ?? '',
1730 'gentype' => $profile['gentype'] ?? null,
1731 'genperiod' => $profile['genperiod'] ?? null,
1732 'settings' => array_merge($defaultSettings, $settings),
1733 ];
1734
1735 try {
1736 // save or update the integration record name and settings
1737 $factory->saveIntegrationRecord($integration, $recordOptions);
1738 } catch (Exception $e) {
1739 VBOHttpDocument::getInstance($app)->close($e->getCode() ?: 500, $e->getMessage() ?: 'Error saving provider settings.');
1740 }
1741
1742 return [
1743 'id_profile' => $integration->getProfileID(),
1744 ];
1745 }
1746
1747 /**
1748 * Custom method for this widget only.
1749 * Fetches the remote provider devices and saves them internally.
1750 *
1751 * @return array
1752 */
1753 public function updateProviderDevices()
1754 {
1755 $app = JFactory::getApplication();
1756
1757 $wrapper = $app->input->getString('wrapper', '');
1758 $provider = $app->input->getString('provider', '');
1759 $idProfile = $app->input->getUInt('profile', 0);
1760
1761 if (empty($provider)) {
1762 VBOHttpDocument::getInstance($app)->close(400, 'Missing door access control provider identifier.');
1763 }
1764
1765 // access the door access factory object
1766 $factory = VBOFactory::getDoorAccessControl();
1767
1768 // get the requested integration provider
1769 $integration = $factory->getIntegrationProvider($provider);
1770
1771 if (!$integration) {
1772 VBOHttpDocument::getInstance($app)->close(404, 'Invalid door access control provider identifier.');
1773 }
1774
1775 // load the requested integration profile
1776 $profile = $factory->loadIntegrationRecord($idProfile);
1777
1778 if (!$profile) {
1779 VBOHttpDocument::getInstance($app)->close(404, 'Invalid door access control profile identifier.');
1780 }
1781
1782 // inject profile record within the integration
1783 $integration->setProfileRecord($profile);
1784
1785 try {
1786 // fetch and update the provider devices
1787 $tot_devices = $factory->updateProviderDevices($integration);
1788 } catch (Exception $e) {
1789 // raise an error
1790 VBOHttpDocument::getInstance($app)->close($e->getCode() ?: 500, $e->getMessage());
1791 }
1792
1793 return [
1794 'tot_devices' => $tot_devices,
1795 ];
1796 }
1797
1798 /**
1799 * Custom method for this widget only.
1800 * Adds a new listing ID connection with a device.
1801 *
1802 * @return array
1803 */
1804 public function setDeviceListingConnection()
1805 {
1806 $app = JFactory::getApplication();
1807
1808 $wrapper = $app->input->getString('wrapper', '');
1809 $provider = $app->input->getString('provider', '');
1810 $idProfile = $app->input->getUInt('profile', 0);
1811 $idDevice = $app->input->getString('device', '');
1812 $idListing = $app->input->getUInt('listing', 0);
1813 $idSubunit = $app->input->getUInt('subunit', 0);
1814
1815 if (empty($provider)) {
1816 VBOHttpDocument::getInstance($app)->close(400, 'Missing door access control provider identifier.');
1817 }
1818
1819 if (empty($idDevice)) {
1820 VBOHttpDocument::getInstance($app)->close(400, 'Missing door access control device identifier.');
1821 }
1822
1823 if (empty($idListing)) {
1824 VBOHttpDocument::getInstance($app)->close(400, 'Missing listing ID.');
1825 }
1826
1827 // access the door access factory object
1828 $factory = VBOFactory::getDoorAccessControl();
1829
1830 // get the requested integration provider
1831 $integration = $factory->getIntegrationProvider($provider);
1832
1833 if (!$integration) {
1834 VBOHttpDocument::getInstance($app)->close(404, 'Invalid door access control provider identifier.');
1835 }
1836
1837 // load the requested integration profile
1838 $profile = $factory->loadIntegrationRecord($idProfile);
1839
1840 if (!$profile) {
1841 VBOHttpDocument::getInstance($app)->close(404, 'Invalid door access control profile identifier.');
1842 }
1843
1844 // inject profile record within the integration
1845 $integration->setProfileRecord($profile);
1846
1847 // attempt to find the requested device
1848 $deviceFound = null;
1849 foreach ($integration->getDevices() as $device) {
1850 if ($device->getID() === $idDevice) {
1851 // device ID found, add listing relation
1852 $device->addConnectedListing($idListing, $idSubunit);
1853
1854 // turn flag on and abort
1855 $deviceFound = $device;
1856 break;
1857 }
1858 }
1859
1860 if (!$deviceFound) {
1861 VBOHttpDocument::getInstance($app)->close(400, 'Invalid door access control device identifier.');
1862 }
1863
1864 try {
1865 // update the integration record devices
1866 $integration->setProfileRecordProp('devices', $integration->getDevices());
1867
1868 // ensure the required device-listing relation is saved on E4jConnect first
1869 if (!class_exists('VCMDacDevicesRequestor')) {
1870 throw new Exception('Please update the Channel Manager.', 426);
1871 }
1872 (new VCMDacDevicesRequestor)->add($deviceFound);
1873
1874 // update integration devices onto the current profile record
1875 $factory->saveIntegrationRecord($integration, ['devices' => $integration->getDevices()]);
1876 } catch (Exception $e) {
1877 // raise an error
1878 VBOHttpDocument::getInstance($app)->close($e->getCode() ?: 500, $e->getMessage());
1879 }
1880
1881 return [
1882 'device_name' => $deviceFound->getName(),
1883 ];
1884 }
1885
1886 /**
1887 * Custom method for this widget only.
1888 * Removes an existing listing ID connection with a device.
1889 *
1890 * @return array
1891 */
1892 public function unsetDeviceListingConnection()
1893 {
1894 $app = JFactory::getApplication();
1895
1896 $wrapper = $app->input->getString('wrapper', '');
1897 $provider = $app->input->getString('provider', '');
1898 $idProfile = $app->input->getUInt('profile', 0);
1899 $idDevice = $app->input->getString('device', '');
1900 $idListing = $app->input->getUInt('listing', 0);
1901 $idSubunit = $app->input->getUInt('subunit', 0);
1902
1903 if (empty($provider)) {
1904 VBOHttpDocument::getInstance($app)->close(400, 'Missing door access control provider identifier.');
1905 }
1906
1907 if (empty($idDevice)) {
1908 VBOHttpDocument::getInstance($app)->close(400, 'Missing door access control device identifier.');
1909 }
1910
1911 if (empty($idListing)) {
1912 VBOHttpDocument::getInstance($app)->close(400, 'Missing listing ID.');
1913 }
1914
1915 // access the door access factory object
1916 $factory = VBOFactory::getDoorAccessControl();
1917
1918 // get the requested integration provider
1919 $integration = $factory->getIntegrationProvider($provider);
1920
1921 if (!$integration) {
1922 VBOHttpDocument::getInstance($app)->close(404, 'Invalid door access control provider identifier.');
1923 }
1924
1925 // load the requested integration profile
1926 $profile = $factory->loadIntegrationRecord($idProfile);
1927
1928 if (!$profile) {
1929 VBOHttpDocument::getInstance($app)->close(404, 'Invalid door access control profile identifier.');
1930 }
1931
1932 // inject profile record within the integration
1933 $integration->setProfileRecord($profile);
1934
1935 // attempt to find the requested device ID
1936 $deviceFound = null;
1937 foreach ($integration->getDevices() as $device) {
1938 if ($device->getID() === $idDevice) {
1939 // device ID found, remove listing relation
1940 $device->removeConnectedListing($idListing, $idSubunit);
1941
1942 // turn flag on and abort
1943 $deviceFound = $device;
1944 break;
1945 }
1946 }
1947
1948 if (!$deviceFound) {
1949 VBOHttpDocument::getInstance($app)->close(400, 'Invalid door access control device identifier.');
1950 }
1951
1952 try {
1953 // update the integration record devices
1954 $integration->setProfileRecordProp('devices', $integration->getDevices());
1955
1956 if (!$deviceFound->getConnectedListings()) {
1957 // ensure the device is deleted from E4jConnect when no more listings are connected
1958 if (!class_exists('VCMDacDevicesRequestor')) {
1959 throw new Exception('Please update the Channel Manager.', 426);
1960 }
1961 (new VCMDacDevicesRequestor)->delete($deviceFound);
1962 }
1963
1964 // update integration devices onto the current profile record
1965 $factory->saveIntegrationRecord($integration, ['devices' => $integration->getDevices()]);
1966 } catch (Exception $e) {
1967 // raise an error
1968 VBOHttpDocument::getInstance($app)->close($e->getCode() ?: 500, $e->getMessage());
1969 }
1970
1971 return [
1972 'device_name' => $deviceFound->getName(),
1973 ];
1974 }
1975
1976 /**
1977 * Custom method for this widget only.
1978 * Deletes a specific integration profile record.
1979 *
1980 * @return array
1981 */
1982 public function deleteProfileRecord()
1983 {
1984 $app = JFactory::getApplication();
1985
1986 $wrapper = $app->input->getString('wrapper', '');
1987 $provider = $app->input->getString('provider', '');
1988 $idProfile = $app->input->getUInt('profile', 0);
1989
1990 if (empty($provider)) {
1991 VBOHttpDocument::getInstance($app)->close(400, 'Missing door access control provider identifier.');
1992 }
1993
1994 // access the door access factory object
1995 $factory = VBOFactory::getDoorAccessControl();
1996
1997 // get the requested integration provider
1998 $integration = $factory->getIntegrationProvider($provider);
1999
2000 if (!$integration) {
2001 VBOHttpDocument::getInstance($app)->close(404, 'Invalid door access control provider identifier.');
2002 }
2003
2004 // load the requested integration profile
2005 $profile = $factory->loadIntegrationRecord($idProfile);
2006
2007 if (!$profile) {
2008 VBOHttpDocument::getInstance($app)->close(404, 'Invalid door access control profile identifier.');
2009 }
2010
2011 // inject profile record within the integration
2012 $integration->setProfileRecord($profile);
2013
2014 try {
2015 // ensure all devices are deleted from E4jConnect first
2016 if (!class_exists('VCMDacDevicesRequestor')) {
2017 throw new Exception('Please update the Channel Manager.', 426);
2018 }
2019 (new VCMDacDevicesRequestor)->delete($integration->getDevices());
2020
2021 // destroy (delete) the current profile record
2022 $integration->destroyProfileRecord();
2023
2024 // set no active profile in widget settings
2025 $this->widgetSettings['id_profile'] = null;
2026 // set no active device in widget settings
2027 $this->widgetSettings['id_device'] = null;
2028
2029 // update widget settings
2030 $this->updateSettings(json_encode($this->widgetSettings));
2031 } catch (Exception $e) {
2032 VBOHttpDocument::getInstance($app)->close($e->getCode() ?: 500, $e->getMessage());
2033 }
2034
2035 // process successfully completed
2036 return [
2037 'success' => 1,
2038 ];
2039 }
2040
2041 /**
2042 * Custom method for this widget only.
2043 * Executes a capability for a specific device.
2044 *
2045 * @return array
2046 */
2047 public function executeDeviceCapability()
2048 {
2049 $app = JFactory::getApplication();
2050
2051 $wrapper = $app->input->getString('wrapper', '');
2052 $provider = $app->input->getString('provider', '');
2053 $idProfile = $app->input->getUInt('profile', 0);
2054 $idDevice = $app->input->getString('device', '');
2055 $idCapability = $app->input->getString('capability', '');
2056 $noParams = $app->input->getBool('noparams', false);
2057 $capabilitySettings = $app->input->get('capability_settings', [], 'array');
2058 $retryDataOptions = $app->input->get('retry_data', [], 'array');
2059
2060 if (empty($provider)) {
2061 VBOHttpDocument::getInstance($app)->close(400, 'Missing door access control provider identifier.');
2062 }
2063
2064 if (empty($idDevice)) {
2065 VBOHttpDocument::getInstance($app)->close(400, 'Missing door access control device identifier.');
2066 }
2067
2068 // access the door access factory object
2069 $factory = VBOFactory::getDoorAccessControl();
2070
2071 // get the requested integration provider
2072 $integration = $factory->getIntegrationProvider($provider);
2073
2074 if (!$integration) {
2075 VBOHttpDocument::getInstance($app)->close(404, 'Invalid door access control provider identifier.');
2076 }
2077
2078 // load the requested integration profile
2079 $profile = $factory->loadIntegrationRecord($idProfile);
2080
2081 if (!$profile) {
2082 VBOHttpDocument::getInstance($app)->close(404, 'Invalid door access control profile identifier.');
2083 }
2084
2085 // inject profile record within the integration
2086 $integration->setProfileRecord($profile);
2087
2088 try {
2089 // access the requested device
2090 $device = $integration->getDeviceById($idDevice);
2091
2092 // access the requested device capability
2093 $capability = $device->getCapabilityById($idCapability);
2094 } catch (Exception $e) {
2095 VBOHttpDocument::getInstance($app)->close($e->getCode() ?: 500, $e->getMessage());
2096 }
2097
2098 if ($capability->providesParams() && $noParams && !$capabilitySettings) {
2099 // capability provides parameters that require settings for its execution
2100 return [
2101 'capability_params' => VBOParamsRendering::getInstance($capability->getParams(), $retryDataOptions)->setInputName('capability_settings[' . $capability->getID() . ']')->getHtml(),
2102 ];
2103 }
2104
2105 try {
2106 // execute the device capability
2107 $capabilityResult = $capability->execute($integration, $device, $capabilitySettings);
2108 } catch (Exception $e) {
2109 VBOHttpDocument::getInstance($app)->close($e->getCode() ?: 500, $e->getMessage());
2110 }
2111
2112 if ($capabilityResult->getOutput()) {
2113 // display the capability execution result output (HTML)
2114 return [
2115 'result_html' => (string) $capabilityResult,
2116 'device_updated' => (int) $device->getDataChanged(),
2117 ];
2118 }
2119
2120 if ($capabilityResult->getText()) {
2121 // display the capability execution result text
2122 return [
2123 'result_text' => (string) $capabilityResult,
2124 'device_updated' => (int) $device->getDataChanged(),
2125 ];
2126 }
2127
2128 // no result information, yet successful
2129 return [
2130 'success' => 1,
2131 'device_updated' => (int) $device->getDataChanged(),
2132 ];
2133 }
2134 }
2135