PluginProbe
VikBooking Hotel Booking Engine & PMS / trunk
VikBooking Hotel Booking Engine & PMS vtrunk
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 1.7.3 All 36 releases
vikbooking / admin / helpers / widgets / door_access_control.php

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

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