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 / ai_training_drafts.php

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

482 lines 18.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - E4J srl
6 * @copyright Copyright (C) 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 "AI Training Drafts".
15 *
16 * @since 1.18.6 (J) - 1.8.6 (WP)
17 */
18 class VikBookingAdminWidgetAiTrainingDrafts extends VikBookingAdminWidget
19 {
20 /**
21 * @var int
22 */
23 protected static $instance_counter = -1;
24
25 /**
26 * @inheritDOc
27 */
28 public function __construct()
29 {
30 // call parent constructor
31 parent::__construct();
32
33 $this->widgetName = JText::translate('VBO_W_AITRAININGDRAFTS_TITLE');
34 $this->widgetDescr = JText::translate('VBO_W_AITRAININGDRAFTS_DESCR');
35 $this->widgetId = basename(__FILE__, '.php');
36
37 $this->widgetIcon = '<i class="' . VikBookingIcons::i('book') . '"></i>';
38 $this->widgetStyleName = 'pink';
39 }
40
41 /**
42 * @inheritDoc
43 */
44 public function getPriority()
45 {
46 // give this widget a higher priority
47 return 14;
48 }
49
50 /**
51 * @inheritDoc
52 */
53 public function preflight()
54 {
55 // can be used only if VCM is installed and the AI channel is supported (not necessarily active)
56 return class_exists('VikChannelManager') && defined('VikChannelManagerConfig::AI') && parent::preflight();
57 }
58
59 /**
60 * @inheritDoc
61 */
62 public function preload()
63 {
64 // load assets
65 $this->vbo_app->loadSelect2();
66 }
67
68 /**
69 * @inheritDoc
70 */
71 public function render(?VBOMultitaskData $data = null)
72 {
73 // increase widget's instance counter
74 static::$instance_counter++;
75
76 // check whether the widget is being rendered via AJAX when adding it through the customizer
77 $is_ajax = $this->isAjaxRendering();
78
79 // generate a unique ID for the sticky notes wrapper instance
80 $wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
81 $wrapper_id = 'vbo-widget-bookdets-' . $wrapper_instance;
82
83 // get permissions
84 if (!JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking')) {
85 // display nothing
86 return;
87 }
88
89 // check for multitask data values
90 $js_intvals_id = '';
91 if ($data) {
92 // access Multitask data
93 if ($data->isModalRendering()) {
94 // get modal JS identifier
95 $js_intvals_id = $data->getModalJsIdentifier();
96 }
97 }
98
99 try {
100 if (!class_exists('VCMAiModelTraining')) {
101 throw new Exception('E4jConnect Channel Manager unavailable or unsupported.', 500);
102 }
103
104 // load the draft training sets
105 $response = (new VCMAiModelTraining)->getItems([
106 // needs review
107 'status' => 2,
108 ], [
109 'ordering' => 'created',
110 'direction' => 'asc',
111 'offset' => 0,
112 'limit' => 20,
113 ]);
114 } catch (Exception $e) {
115 // store the exception
116 $response = $e;
117 }
118
119 ?>
120 <div id="<?php echo $wrapper_id; ?>" class="vbo-admin-widget-wrapper" data-instance="<?php echo $wrapper_instance; ?>">
121
122 <div class="vbo-admin-widget-head">
123 <div class="vbo-admin-widget-head-inline">
124 <h4><?php echo $this->getIcon(); ?> <span><?php echo $this->getName(); ?></span></h4>
125 </div>
126 </div>
127
128 <div class="vbo-widget-aitrainingdrafts-wrap">
129 <?php
130 try {
131 // load draft records upon widget rendering
132 echo $this->loadDrafts(true)['html'];
133 } catch (Exception $e) {
134 ?>
135 <p class="err"><?php echo $e->getMessage(); ?></p>
136 <?php
137 }
138 ?>
139 </div>
140
141 </div>
142 <?php
143 if (static::$instance_counter === 0 || $is_ajax) {
144 ?>
145 <script>
146 /**
147 * Register function for loading the draft training sets.
148 */
149 function vbo_w_aitrainingdrafts_load(wrapper_id) {
150 const wrapper = document.getElementById(wrapper_id);
151
152 const output = wrapper.querySelector('.vbo-widget-aitrainingdrafts-wrap');
153
154 // the widget method to call
155 let call_method = 'loadDrafts';
156
157 VBOCore.doAjax(
158 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
159 {
160 widget_id: "<?php echo $this->getIdentifier(); ?>",
161 call: call_method,
162 return: 1,
163 },
164 (response) => {
165 // append HTML response
166 output.innerHTML = response.html;
167 },
168 (error) => {
169 // display error
170 let errorEl = document.createElement('p');
171 errorEl.classList.add('err');
172 errorEl.innerText = error.responseText || 'Unknown error';
173 // set error content
174 output.innerHTML = errorEl;
175 }
176 );
177 }
178
179 /**
180 * Register function to perform a translation of an approved draft ID.
181 */
182 function vbo_w_aitrainingdrafts_translate(draftId) {
183 return new Promise((resolve, reject) => {
184 // make a request to translate the draft
185 VBOCore.doAjax(
186 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikchannelmanager&task=training.translate'); ?>",
187 {
188 id: [draftId],
189 languages: <?php echo json_encode(array_column(VikBooking::getVboApplication()->getKnownLanguages(), 'tag')); ?>,
190 },
191 (response) => {
192 // resolve promise
193 resolve(response);
194 },
195 (error) => {
196 // reject promise
197 reject(error);
198 }
199 );
200 });
201 }
202
203 /**
204 * Register function for handling a global body click event delegation.
205 */
206 function vbo_w_aitrainingdrafts_click_delegation(e) {
207 // draft record click
208 if (e.target.matches('.vbo-ai-training-draft-record') || e.target.closest('.vbo-ai-training-draft-record')) {
209 const draftEl = !e.target.matches('.vbo-ai-training-draft-record') ? e.target.closest('.vbo-ai-training-draft-record') : e.target;
210 const draftId = draftEl.getAttribute('data-item-id');
211
212 const wrapper_id = draftEl.closest('.vbo-admin-widget-wrapper').getAttribute('id');
213
214 // reject button
215 let modalCancelBtn = document.createElement('button');
216 modalCancelBtn.setAttribute('type', 'button');
217 modalCancelBtn.classList.add('btn', 'btn-danger');
218 modalCancelBtn.innerText = <?php echo json_encode(JText::translate('VBO_REJECT')); ?>;
219 modalCancelBtn.addEventListener('click', () => {
220 if (!confirm(<?php echo json_encode(JText::translate('VBDELCONFIRM')); ?>)) {
221 return;
222 }
223
224 // disable button
225 modalCancelBtn.disabled = true;
226
227 // start loading
228 VBOCore.emitEvent('vbo-ai-training-drafts-loading');
229
230 // make a request to delete the draft
231 VBOCore.doAjax(
232 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikchannelmanager&task=training.delete'); ?>",
233 {
234 cid: [draftId],
235 ajax: 1,
236 },
237 (response) => {
238 // dismiss modal
239 VBOCore.emitEvent('vbo-ai-training-drafts-dismiss');
240
241 // reload drafts
242 vbo_w_aitrainingdrafts_load(wrapper_id);
243 },
244 (error) => {
245 // log and display error
246 console.error(error);
247 alert(error.responseText);
248
249 // stop loading
250 VBOCore.emitEvent('vbo-ai-training-drafts-loading');
251
252 // re-enable button
253 modalCancelBtn.disabled = false;
254 }
255 );
256 });
257
258 // approve button
259 let modalApproveBtn = document.createElement('button');
260 modalApproveBtn.setAttribute('type', 'button');
261 modalApproveBtn.classList.add('btn', 'btn-success');
262 modalApproveBtn.innerText = <?php echo json_encode(JText::translate('VBO_APPROVE')); ?>;
263 modalApproveBtn.addEventListener('click', () => {
264 // disable button
265 modalApproveBtn.disabled = true;
266
267 // start loading
268 VBOCore.emitEvent('vbo-ai-training-drafts-loading');
269
270 // get form data
271 let draftForm = new FormData(document.querySelector('#vbo-training-draft-form'));
272
273 // append additional parameters
274 draftForm.append('ajax', 1);
275
276 // make a request to approve the draft
277 VBOCore.doAjax(
278 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikchannelmanager&task=training.save'); ?>",
279 draftForm,
280 async (response) => {
281 // draft approved successfully, attempt to translate it into other known languages
282 try {
283 // hold until the promise resolves
284 await vbo_w_aitrainingdrafts_translate(draftId);
285 } catch(e) {
286 // do nothing
287 console.error(e);
288 }
289
290 // dismiss modal
291 VBOCore.emitEvent('vbo-ai-training-drafts-dismiss');
292
293 // reload drafts
294 vbo_w_aitrainingdrafts_load(wrapper_id);
295 },
296 (error) => {
297 // log and display error
298 console.error(error);
299 alert(error.responseText);
300
301 // re-enable button
302 modalApproveBtn.disabled = false;
303
304 // stop loading
305 VBOCore.emitEvent('vbo-ai-training-drafts-loading');
306 }
307 );
308 });
309
310 // display modal
311 let modalBody = VBOCore.displayModal({
312 suffix: 'ai-training-drafts-review',
313 title: <?php echo json_encode(JText::translate('VBO_REVIEW_TRAINING_DRAFT')); ?>,
314 lock_scroll: true,
315 footer_left: modalCancelBtn,
316 footer_right: modalApproveBtn,
317 extra_class: 'vbo-modal-rounded vbo-modal-tall vbo-modal-taller vbo-modal-fullscreen',
318 loading_event: 'vbo-ai-training-drafts-loading',
319 dismiss_event: 'vbo-ai-training-drafts-dismiss',
320 });
321
322 // start loading
323 VBOCore.emitEvent('vbo-ai-training-drafts-loading');
324
325 // make a request to obtain the template
326 VBOCore.doAjax(
327 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikchannelmanager&task=training.editdraft'); ?>",
328 {
329 cid: [draftId],
330 },
331 (response) => {
332 // append response to body
333 modalBody.append(response?.html);
334
335 // stop loading
336 VBOCore.emitEvent('vbo-ai-training-drafts-loading');
337 },
338 (error) => {
339 // log and display error
340 console.error(error);
341 alert(error.responseText);
342
343 // dismiss modal
344 VBOCore.emitEvent('vbo-ai-training-drafts-dismiss');
345 }
346 );
347
348 // do not proceed
349 return;
350 }
351 }
352 </script>
353 <?php
354 }
355 ?>
356
357 <script>
358 VBOCore.DOMLoaded(() => {
359 /**
360 * Add body click event delegation for elements that will be later added to the DOM.
361 */
362 if (!VBOCore.wasEventDelegated('ai.training.drafts')) {
363 document.body.addEventListener('click', vbo_w_aitrainingdrafts_click_delegation);
364 VBOCore.setEventDelegated('ai.training.drafts');
365 }
366
367 <?php
368 if ($js_intvals_id) {
369 // widget can be dismissed through the modal
370 ?>
371 document.addEventListener(VBOCore.widget_modal_dismissed + '<?php echo $js_intvals_id; ?>', (e) => {
372 // remove body click events delegation for this widget
373 document.body.removeEventListener('click', vbo_w_aitrainingdrafts_click_delegation);
374 VBOCore.unsetEventDelegated('ai.training.drafts');
375 });
376 <?php
377 }
378 ?>
379 });
380 </script>
381 <?php
382 }
383
384 /**
385 * Loads the draft training sets.
386 *
387 * @param bool $return True to return just the HTML template.
388 *
389 * @return mixed
390 *
391 * @throws Exception
392 */
393 public function loadDrafts(bool $return = false)
394 {
395 $app = JFactory::getApplication();
396
397 if (!class_exists('VCMAiModelTraining')) {
398 if ($return) {
399 throw new Exception('E4jConnect Channel Manager unavailable or unsupported.', 500);
400 }
401 VBOHttpDocument::getInstance($app)->close(500, 'E4jConnect Channel Manager unavailable or unsupported.');
402 }
403
404 try {
405 // load the draft training sets
406 $response = (new VCMAiModelTraining)->getItems([
407 // needs review
408 'status' => 2,
409 ], [
410 'ordering' => 'created',
411 'direction' => 'asc',
412 'offset' => 0,
413 'limit' => 20,
414 ]);
415 } catch (Exception $e) {
416 // raise an error
417 if ($return) {
418 throw $e;
419 }
420 VBOHttpDocument::getInstance($app)->close($e->getCode() ?: 500, $e->getMessage());
421 }
422
423 // build HTML response
424 $html = <<<HTML
425 <div class="vbo-widget-latestevents-list vbo-widget-aitrainingdrafts-list">
426 HTML;
427
428 // render drafts list
429 $pendingLabel = JText::translate('VBO_TASK_STATUS_TYPE_PENDING');
430
431 foreach (($response->items ?? []) as $item) {
432 // template variables
433 $draftContent = JHtml::fetch('vikbooking.shorten', $item->content, 200);
434 $draftDate = JHtml::fetch('date.relative', $item->created, null, null, 'Y-m-d H:i:s');
435
436 // append HTML record
437 $html .= <<<HTML
438 <div class="vbo-widget-history-record vbo-ai-training-draft-record" data-item-id="{$item->id}">
439 <div class="vbo-widget-history-content">
440 <div class="vbo-widget-history-content-head">
441 <div class="vbo-widget-history-content-info-details">
442 <span class="label label-warning vbo-status-label">$pendingLabel</span>
443 <h4>{$item->title}</h4>
444 </div>
445 <div class="vbo-widget-history-content-info-booking">
446 <div class="vbo-widget-history-content-info-dates">
447 <span>$draftContent</span>
448 </div>
449 </div>
450 </div>
451 <div class="vbo-widget-history-content-info-msg">
452 <span class="vbo-widget-history-content-info-msg-descr">$draftDate</span>
453 </div>
454 </div>
455 </div>
456 HTML;
457 }
458
459 if (!($response->items ?? [])) {
460 $html .= '<p class="info">' . JText::translate('VBO_NO_RECORDS_FOUND') . '</p>';
461 }
462
463 $html .= <<<HTML
464 </div>
465 HTML;
466
467 // build response data
468 $responseData = [
469 'html' => $html,
470 'response' => $response,
471 ];
472
473 if ($return) {
474 // return just the HTML string
475 return $responseData;
476 }
477
478 // send response to output
479 VBOHttpDocument::getInstance($app)->json($responseData);
480 }
481 }
482