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> |