PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.21
VikAppointments Services Booking Calendar v1.2.21
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / site / layouts / blocks / options.php
vikappointments / site / layouts / blocks Last commit date
calendar 1 day ago login 1 day ago payment 1 day ago alert.php 1 day ago carttotals.php 1 day ago checkout.php 1 day ago couponform.php 1 day ago index.html 1 day ago login.php 1 day ago options.php 1 day ago paymentmethods.php 1 day ago quickcontact.php 1 day ago reviews.php 1 day ago timeline.php 1 day ago waitlist.php 1 day ago
options.php
452 lines
1 <?php
2 /**
3 * @package VikAppointments
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2021 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 JHtml::fetch('bootstrap.tooltip', '.vapseroptname .opt-label-wrapper');
15
16 // the options are always grouped by category
17 $groups = isset($displayData['options']) ? $displayData['options'] : array();
18
19 $vik = VAPApplication::getInstance();
20
21 $currency = VAPFactory::getCurrency();
22
23 JText::script('VAP_OPTGROUP_SHARED_QTY_EXCEEDED');
24
25 ?>
26
27 <div class="vapseroptionscont <?php echo $vik->getThemeClass('background'); ?>" style="display: none;">
28
29 <?php
30 $count = count($groups);
31
32 foreach ($groups as $i => $group)
33 {
34 ?>
35 <div class="vapseroptiongroup">
36 <div class="vapseroptionsheader<?php echo $count > 1 ? ' toggle-link' : ''; ?>">
37 <?php
38 if ($count > 1)
39 {
40 ?>
41 <i class="fas fa-chevron-<?php echo $i == 0 ? 'down' : 'right'; ?>"></i>
42 <?php
43 }
44
45 if ($group->name)
46 {
47 // use the category name
48 echo $group->name;
49 }
50 else
51 {
52 // use default category name (available options)
53 echo JText::translate('VAPSERAVAILOPTIONSTITLE');
54 }
55 ?>
56 </div>
57
58 <div class="vapseroptionsdiv" style="<?php echo $i == 0 ? '' : 'display:none;'; ?>">
59
60 <?php
61
62 if ($group->description)
63 {
64 ?>
65 <div class="vapseroptiongroupdesc"><?php echo $group->description; ?></div>
66 <?php
67 }
68
69 foreach ($group->options as $o)
70 {
71 $name = $o->name;
72 $price = $o->price;
73 $duration = $o->duration;
74
75 if (count($o->variations))
76 {
77 // include price of the first available variation
78 $price += $o->variations[0]->price;
79 // include extra duration of the first available variation
80 $duration += $o->variations[0]->duration;
81 }
82
83 if ($o->required)
84 {
85 // flag as required
86 $name .= '*';
87 }
88 ?>
89 <div class="vapsersingoption" data-id="<?php echo $o->id; ?>">
90
91 <div class="vapseroptrow">
92
93 <?php
94 if ($o->displaymode == 2 && $o->image)
95 {
96 // display option image before the name
97 ?>
98 <span class="vapseroptimage left-side">
99 <a href="javascript:void(0)" class="vapmodal" onClick="vapOpenModalImage('<?php echo VAPMEDIA_URI . $o->image; ?>', this);">
100 <?php
101 // render image tag
102 echo JHtml::fetch('vaphtml.media.display', $o->image, [
103 'loading' => 'lazy',
104 'alt' => $o->name,
105 'small' => true,
106 ]);
107 ?>
108 </a>
109 </span>
110 <?php
111 }
112 ?>
113
114 <span class="vapseroptname">
115 <span class="opt-label-wrapper" title="<?php echo $this->escape($o->description); ?>">
116 <?php
117 if ($o->displaymode == 1 && $o->image)
118 {
119 // open the image in a modal box after clicking the option name
120 ?>
121 <a href="javascript:void(0)" class="vapmodal <?php echo ($o->required ? 'option-required' : ''); ?>"
122 <?php echo ($o->required ? 'id="vapreqopt' . $o->id . '"' : ''); ?>
123 onClick="vapOpenModalImage('<?php echo VAPMEDIA_URI . $o->image; ?>', this);">
124 <?php
125 echo $name;
126
127 // render image tag
128 echo JHtml::fetch('vaphtml.media.display', $o->image, [
129 'loading' => 'lazy',
130 'alt' => $o->name,
131 'small' => true,
132 'style' => 'display: none;',
133 ]);
134 ?>
135 </a>
136 <?php
137 }
138 else
139 {
140 // just use a label to toggle the checkbox
141 ?>
142 <label
143 class="<?php echo ($o->required ? 'option-required' : ''); ?>"
144 <?php echo ($o->required ? 'id="vapreqopt' . $o->id . '"' : ''); ?>
145 for="vapoptchbox<?php echo $o->id; ?>"><?php echo $name; ?></label>
146 <?php
147 }
148 ?>
149 </span>
150 </span>
151
152 <?php
153 if ($o->variations)
154 {
155 ?>
156 <div class="vapseropt-variations">
157 <select id="vapoptvar<?php echo $o->id; ?>" class="vap-optvar-sel" onChange="vapOptionVarValueChanged(<?php echo $o->id; ?>);">
158 <?php
159 foreach ($o->variations as $var)
160 {
161 $var_price = $o->price + $var->price;
162
163 if ($var_price != 0)
164 {
165 // display option price
166 $tot_price_label = $currency->format($var_price);
167 }
168 else
169 {
170 // do not display price
171 $tot_price_label = '';
172 }
173
174 $var_duration = $o->duration + $var->duration;
175
176 if ($var_duration > 0)
177 {
178 // display option duration
179 $tot_duration_label = '+' . VikAppointments::formatMinutesToTime($var_duration);
180 }
181 else
182 {
183 // do not display duration
184 $tot_duration_label = '';
185 }
186 ?>
187 <option value="<?php echo $var->id; ?>" data-price="<?php echo $this->escape($tot_price_label); ?>" data-duration="<?php echo $this->escape($tot_duration_label); ?>">
188 <?php
189 echo $var->name;
190
191 if ($var->price != 0)
192 {
193 echo ' ' . $currency->format($var->price);
194 }
195 ?>
196 </option>
197 <?php
198 }
199 ?>
200 </select>
201 </div>
202 <?php
203 }
204 ?>
205
206 <?php
207 if ($o->duration > 0)
208 {
209 ?>
210 <span id="vapseroptduration<?php echo $o->id; ?>" class="vapseroptduration">
211 <?php echo '+' . VikAppointments::formatMinutesToTime($duration); ?>
212 </span>
213 <?php
214 }
215 ?>
216
217 <span id="vapseroptprice<?php echo $o->id; ?>" class="vapseroptprice">
218 <?php echo ($price != 0 ? $currency->format($price) : ''); ?>
219 </span>
220
221 </div>
222
223 <div class="vapseroptact">
224 <?php
225 // The name represents the opposite of its purpose.
226 // When single is TRUE, the option supports the quantity selection.
227 if ($o->single)
228 {
229 ?>
230 <input type="number" value="1" size="4" min="1" max="<?php echo $o->maxqpeople ? 1 : $o->maxq; ?>"
231 class="option-quantity<?php echo $o->maxqpeople ? ' people-variable' : ''; ?><?php echo $o->maxqpeople == 2 ? ' same-as-people' : ''; ?><?php echo $o->maxqpeople == 1 && $o->shared ? ' shared-units' : ''; ?>"
232 id="vapoptmaxq<?php echo $o->id; ?>" onChange="vapQuantityValueChanged(<?php echo $o->id; ?>);" style="max-width: 80px;" />
233 <?php
234 }
235 else
236 {
237 ?>
238 <input type="hidden" value="1" class="option-quantity" id="vapoptmaxq<?php echo $o->id; ?>" />
239 <?php
240 }
241 ?>
242 <input type="checkbox" value="1" id="vapoptchbox<?php echo $o->id; ?>" class="option-checkbox <?php echo $o->required ? 'required' : ''; ?>" data-id="<?php echo $o->id; ?>" />
243 <input type="hidden" value="<?php echo $o->id; ?>" id="vapoptid<?php echo $o->id; ?>" />
244 </div>
245
246 </div>
247 <?php
248 }
249 ?>
250
251 </div>
252 </div>
253 <?php
254 }
255 ?>
256
257 </div>
258
259 <script>
260 (function($) {
261 'use strict';
262
263 $(function() {
264 $('.vap-optvar-sel').select2({
265 minimumResultsForSearch: -1,
266 allowClear: false,
267 width: 200,
268 });
269
270 $('#vapserpeopleselect').on('change', function() {
271 let people = parseInt($(this).val());
272 people = isNaN(people) || people <= 0 ? 1 : people;
273
274 $('.option-quantity.people-variable').each(function() {
275 $(this).attr('max', people);
276
277 if ($(this).hasClass('same-as-people')) {
278 // option equals to the number of participants
279 $(this).attr('min', people).val(people);
280 } else {
281 // option depending on the number of participants
282 let val = parseInt($(this).val());
283
284 if (isNaN(val) || val > people) {
285 // auto-adjust the currently selected value
286 $(this).val(people);
287 }
288 }
289 });
290 }).trigger('change');
291
292 <?php
293 if ($count > 1)
294 {
295 ?>
296 $('.vapseroptionsheader.toggle-link').on('click', function() {
297 const body = $(this).next();
298
299 if (body.is(':visible')) {
300 $(this).find('i').removeClass('fa-chevron-down').addClass('fa-chevron-right');
301 body.slideUp('fast');
302 } else {
303 $(this).find('i').removeClass('fa-chevron-right').addClass('fa-chevron-down');
304 body.slideDown('fast');
305 }
306 });
307 <?php
308 }
309 ?>
310
311 $(window).on('resize', function() {
312 if ($(this).width() <= 680) {
313 // move the variations box for a better visibility on mobile phones
314 $('.details-options-wrapper .vapseroptrow').find('.vapseropt-variations').each(function() {
315 $(this).appendTo($(this).closest('.vapsersingoption'));
316 });
317 } else {
318 // re-add the variation before the price on larger devices
319 $('.details-options-wrapper').find('.vapseropt-variations').each(function() {
320 const target = $(this).prevAll('.vapseroptrow').find('.vapseroptprice');
321 if (target.length) {
322 $(this).insertBefore(target);
323 }
324 });
325 }
326 });
327
328 setTimeout(() => {
329 $(window).trigger('resize');
330 }, 256);
331 });
332
333 window.vapGetSelectedOptions = (people) => {
334 var options = [];
335
336 if (!people) {
337 people = Math.max(1, parseInt($('#vapserpeopleselect').val()) || 0);
338 }
339
340 // iterate by group
341 $('.vapseroptiongroup').each(function() {
342 let optionsGroup = this;
343 let totalSharedCount = 0;
344
345 $(this).find('.vapsersingoption').each(function() {
346 const checkbox = $(this).find('.option-checkbox');
347
348 // check whether the options was selected
349 if (checkbox.is(':checked')) {
350 let id_opt = parseInt($(this).data('id'));
351 let id_var = parseInt($('#vapoptvar' + id_opt).val());
352 let units = Math.max(1, parseInt($(this).find('.option-quantity').val()));
353
354 if (checkbox.prev('.option-quantity').hasClass('shared-units')) {
355 totalSharedCount += units;
356 }
357
358 options.push({
359 id: id_opt,
360 quantity: units,
361 variation: id_var ? id_var : null,
362 });
363 }
364 // nope, make sure it was not required
365 else if (checkbox.hasClass('required')) {
366 vapMarkRequiredOptions(true);
367 throw "MissingRequiredOptionException";
368 }
369 });
370
371 if (totalSharedCount > people) {
372 vapMarkSharedOptions(optionsGroup, true);
373
374 /**
375 * Reuse the same exception thrown in case of missing required selection.
376 * This way we can prevent the booking process without having to edit the
377 * code that uses this function.
378 *
379 * Consider to use a different exception in case we want to display an
380 * error message along with the highlighted options.
381 */
382 throw "MissingRequiredOptionException";
383 } else {
384 vapMarkSharedOptions(optionsGroup, false);
385 }
386 });
387
388 // all options seem valid
389 vapMarkRequiredOptions(false);
390
391 return options;
392 }
393
394 window.vapOptionVarValueChanged = (id) => {
395 $('#vapseroptprice' + id).html($('#vapoptvar' + id + ' :selected').attr('data-price'));
396 $('#vapseroptduration' + id).html($('#vapoptvar' + id + ' :selected').attr('data-duration'));
397 $('#vapoptchbox' + id).prop('checked', true);
398 }
399
400 window.vapQuantityValueChanged = (id) => {
401 $('#vapoptchbox' + id).prop('checked', true);
402 }
403
404 const vapMarkRequiredOptions = (s) => {
405 $('.option-checkbox.required').each(function() {
406 var id = $(this).data('id');
407 var label = $('#vapreqopt' + id);
408
409 if (s) {
410 label.addClass('vapoptred');
411
412 let wrapper = label.closest('.vapseroptionsdiv');
413 if (!wrapper.is(':visible')) {
414 wrapper.prev().trigger('click');
415 }
416 } else {
417 label.removeClass('vapoptred');
418 }
419 });
420 }
421
422 const vapMarkSharedOptions = (container, s) => {
423 $(container).find('.vapsersingoption').each(function() {
424 const checkbox = $(this).find('.option-checkbox');
425
426 if (!checkbox.prev().hasClass('shared-units')) {
427 return;
428 }
429
430 const label = checkbox.closest('.vapsersingoption').find('.vapseroptname label');
431
432 if (s && checkbox.is(':checked')) {
433 label.addClass('vapoptred');
434
435 if (!$(container).find('.vapseroptionsdiv').is(':visible')) {
436 $(container).find('.toggle-link').trigger('click');
437 }
438 } else {
439 label.removeClass('vapoptred');
440 }
441 });
442
443 $(container).find('.options-group-error').remove();
444
445 if (s) {
446 $(container).find('.vapseroptionsheader').append(
447 $('<div class="options-group-error"></div>').text(Joomla.JText._('VAP_OPTGROUP_SHARED_QTY_EXCEEDED'))
448 );
449 }
450 }
451 })(jQuery);
452 </script>