<?php /** * @package VikBooking * @subpackage com_vikbooking * @author Alessio Gaggii - E4J srl * @copyright Copyright (C) 2024 E4J srl. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE * @link https://vikwp.com */ defined('ABSPATH') or die('No script kiddies please!'); /** * Helper class to render specific param types. * * @since 1.16.9 (J) - 1.6.9 (WP) */ final class VBOParamsRendering { /** * @var array */ private $params = []; /** * @var array */ private $settings = []; /** * @var array */ private $scripts = []; /** * @var array */ private $assets = []; /** * @var string */ private $inputName = 'vboparams'; /** * @var int */ private static $instance_counter = -1; /** * Class constructor is protected. * * @param array $params The form params to bind. * @param array $settings The form settings to bind. * * @see getInstance() */ private function __construct(array $params, array $settings) { // bind values $this->params = $params; $this->settings = $settings; // increase instance counter static::$instance_counter++; } /** * Proxy for immediately accessing the object and bind data. * * @param array $params The form params to bind. * @param array $settings The form settings to bind. * * @return VBOParamsRendering */ public static function getInstance(array $params = [], array $settings = []) { return new static($params, $settings); } /** * Sets the name to be used for rendering the param fields. * * @param string $name The name to use. * * @return self */ public function setInputName($name) { $this->inputName = (string) $name; return $this; } /** * Renders the injected form params and returns the HTML code. * * @param bool $load_assets Whether to load the necessary assets. * * @return string */ public function getHtml($load_assets = true) { if (!$this->params) { return ''; } $html = ''; foreach ($this->params as $param_name => $param_config) { if (empty($param_name)) { continue; } $labelparts = explode('//', (isset($param_config['label']) ? $param_config['label'] : '')); $label = $labelparts[0]; $labelhelp = isset($labelparts[1]) ? $labelparts[1] : ''; if (!empty($param_config['help'])) { $labelhelp = $param_config['help']; } $nested_style = (isset($param_config['nested']) && $param_config['nested']); $hidden_wrapper = $param_config['type'] === 'hidden'; if (isset($param_config['conditional']) && isset($this->params[$param_config['conditional']])) { $check_cond = isset($this->params[$param_config['conditional']]['default']) ? $this->params[$param_config['conditional']]['default'] : null; $check_cond = isset($this->settings[$param_config['conditional']]) ? $this->settings[$param_config['conditional']] : $check_cond; if (!is_null($check_cond) && (!$check_cond || !strcasecmp($check_cond, 'off'))) { // hide current field because the field to who this is dependant is "off" or disabled (i.e. 0) $hidden_wrapper = true; } } $html .= '<div class="vbo-param-container' . (in_array($param_config['type'], ['textarea', 'visual_html']) ? ' vbo-param-container-full' : '') . ($nested_style ? ' vbo-param-nested' : '') . '"' . ($hidden_wrapper ? ' style="display: none;"' : '') . '>'; if (strlen($label) && (!isset($param_config['hidden']) || $param_config['hidden'] != true)) { $html .= '<div class="vbo-param-label">' . $label . '</div>'; } $html .= '<div class="vbo-param-setting">'; // render field $html .= $this->getField($param_name, $param_config); // check for assets to be loaded, only once to obtain individual setups if ($load_assets) { if ((VBOPlatformDetection::isWordPress() && wp_doing_ajax()) || (!VBOPlatformDetection::isWordPress() && !strcasecmp((string) JFactory::getApplication()->input->server->get('HTTP_X_REQUESTED_WITH', ''), 'xmlhttprequest'))) { // concatenate script(s) to HTML string when doing an AJAX request $html .= "\n" . '<script>' . implode("\n", $this->buildScriptAssets($load_once = true)) . '</script>'; } else { // add script declaration(s) to document $this->loadAssets($load_once = true); } } if ($labelhelp) { $html .= '<span class="vbo-param-setting-comment">' . $labelhelp . '</span>'; } $html .= '</div>'; $html .= '</div>'; } // JS helper functions $html .= $this->getScripts(); return $html; } /** * Builds the requested script assets, if any. * * @param bool $load_once True to unset the assets after loading. * * @return array * * @since 1.18.0 (J) - 1.8.0 (WP) */ public function buildScriptAssets($load_once = false) { $scripts = []; foreach ($this->assets as $asset_type => $asset_elements) { if ($asset_type === 'select2') { // build list of selectors $ids_list = implode(', ', array_map(function($el) { return "#{$el}"; }, $asset_elements)); // check for asset options $asset_options = $this->assets['select2_options'] ?? null; $asset_options_str = $asset_options ? json_encode($asset_options) : ''; // always attempt to load assets VikBooking::getVboApplication()->loadSelect2(); // build and push script $scripts[] = <<<JAVASCRIPT jQuery(function() { jQuery('$ids_list').select2($asset_options_str); }); JAVASCRIPT; } if ($load_once) { unset($this->assets[$asset_type]); } } return $scripts; } /** * Loads the requested assets, if any. * * @param bool $load_once True to unset the assets after loading. * * @return void */ public function loadAssets($load_once = false) { $doc = JFactory::getDocument(); foreach ($this->buildScriptAssets($load_once) as $script) { $doc->addScriptDeclaration($script); } } /** * Gets the necessary script tags. * * @return string */ public function getScripts() { $html = ''; if (in_array('password', $this->scripts)) { // toggle the password fields $html .= "\n" . '<script>' . "\n"; $html .= 'function vboParamTogglePwd(elem) {' . "\n"; $html .= ' var btn = jQuery(elem), inp = btn.parent().find("input").first();' . "\n"; $html .= ' if (!inp || !inp.length) {return false;}' . "\n"; $html .= ' var inp_type = inp.attr("type");' . "\n"; $html .= ' inp.attr("type", (inp_type == "password" ? "text" : "password"));' . "\n"; $html .= '}' . "\n"; $html .= "\n" . '</script>' . "\n"; } return $html; } /** * Renders the given param name according to config. * Eventually populates the assets and scripts to be loaded. * * @param string $param_name The param name. * @param array $param_config The param configuration. * * @return string */ public function getField($param_name, $param_config) { $html = ''; $inp_attr = ''; if (isset($param_config['attributes']) && is_array($param_config['attributes'])) { foreach ($param_config['attributes'] as $inpk => $inpv) { $inp_attr .= $inpk . '="' . $inpv . '" '; } $inp_attr = ' ' . rtrim($inp_attr); } $default_paramv = $param_config['default'] ?? null; switch ($param_config['type']) { case 'custom': $html .= $param_config['html']; break; case 'select': $options = isset($param_config['options']) && is_array($param_config['options']) ? $param_config['options'] : []; $is_assoc = (array_keys($options) !== range(0, count($options) - 1)); $element_id = 'vik-select-' . static::$instance_counter . '-' . preg_replace("/[^A-Z0-9]+/i", '', $param_name); $set_attr = true; if (isset($param_config['attributes']) && is_array($param_config['attributes']) && isset($param_config['attributes']['id'])) { $element_id = $param_config['attributes']['id']; $set_attr = false; } if (isset($param_config['assets']) && $param_config['assets']) { if (!isset($this->assets['select2'])) { $this->assets['select2'] = []; } $this->assets['select2'][] = $element_id; $this->assets['select2_options'] = $param_config['asset_options'] ?? null; } if (isset($param_config['multiple']) && $param_config['multiple']) { $html .= '<select name="' . $this->inputName . '[' . $param_name . '][]" multiple="multiple"' . $inp_attr . ($set_attr ? ' id="' . $element_id . '"' : '') . '>' . "\n"; } else { $html .= '<select name="' . $this->inputName . '[' . $param_name . ']"' . $inp_attr . ($set_attr ? ' id="' . $element_id . '"' : '') . '>' . "\n"; } foreach ($options as $optind => $optval) { // support nested array values for the option-group tags $group = null; $sel_opts = [$optind => $optval]; if (is_array($optval)) { $group = $optind; $sel_opts = $optval; } if ($group) { $html .= '<optgroup label="' . JHtml::fetch('esc_attr', JText::translate($group)) . '">' . "\n"; } foreach ($sel_opts as $optkey => $poption) { $checkval = $is_assoc ? $optkey : $poption; $selected = false; if (isset($this->settings[$param_name])) { if (is_array($this->settings[$param_name])) { $selected = in_array($checkval, $this->settings[$param_name]); } else { $selected = ($checkval == $this->settings[$param_name]); } } elseif (isset($default_paramv)) { if (is_array($default_paramv)) { $selected = in_array($checkval, $default_paramv); } else { $selected = ($checkval == $default_paramv); } } $html .= '<option value="' . ($is_assoc ? $optkey : $poption) . '"'.($selected ? ' selected="selected"' : '').'>'.$poption.'</option>' . "\n"; } if ($group) { $html .= '</optgroup>' . "\n"; } } $html .= '</select>' . "\n"; break; case 'listings': // build attributes list $element_id = 'vik-select-' . static::$instance_counter . '-' . preg_replace("/[^A-Z0-9]+/i", '', $param_name); $elements_attr = [ 'name' => $this->inputName . '[' . $param_name . ']', ]; if ($param_config['multiple'] ?? null) { $elements_attr['multiple'] = 'multiple'; $elements_attr['name'] .= '[]'; } $custom_attr = (array) ($param_config['attributes'] ?? []); unset($custom_attr['id'], $custom_attr['name']); $elements_attr = array_merge($elements_attr, $custom_attr); $wrapped = false; $style_selection = false; if ($param_config['inline'] ?? true) { // wrap the select within an additional div $html .= '<div class="' . (($param_config['multiple'] ?? null) ? 'vbo-multiselect-inline-elems-wrap' : 'vbo-singleselect-inline-elems-wrap') . '">'; $wrapped = true; $style_selection = (bool) ($param_config['multiple'] ?? null); } elseif ($param_config['wrapdivcls'] ?? null) { // wrap the select within a custom div $html .= '<div class="' . $param_config['wrapdivcls'] . '">'; $wrapped = true; } // obtain the necessary HTML code for rendering $html .= VikBooking::getVboApplication()->renderElementsDropDown([ 'id' => $element_id, 'elements' => 'listings', 'placeholder' => ($param_config['asset_options']['placeholder'] ?? null), 'allow_clear' => ($param_config['asset_options']['allowClear'] ?? $param_config['asset_options']['allow_clear'] ?? null), 'attributes' => $elements_attr, 'selected_value' => (is_scalar($this->settings[$param_name] ?? null) ? $this->settings[$param_name] : (is_scalar($default_paramv ?? null) ? $default_paramv : null)), 'selected_values' => (is_array($this->settings[$param_name] ?? null) ? $this->settings[$param_name] : (is_array($default_paramv ?? null) ? $default_paramv : null)), 'style_selection' => $style_selection, ]); if ($wrapped) { // close the select div wrapper $html .= '</div>'; } break; case 'elements': // build attributes list $element_id = 'vik-select-' . static::$instance_counter . '-' . preg_replace("/[^A-Z0-9]+/i", '', $param_name); $elements_attr = [ 'name' => $this->inputName . '[' . $param_name . ']', ]; if ($param_config['multiple'] ?? null) { $elements_attr['multiple'] = 'multiple'; $elements_attr['name'] .= '[]'; } $custom_attr = (array) ($param_config['attributes'] ?? []); unset($custom_attr['id'], $custom_attr['name']); $elements_attr = array_merge($elements_attr, $custom_attr); $wrapped = false; $style_selection = false; if ($param_config['inline'] ?? true) { // wrap the select within an additional div $html .= '<div class="' . (($param_config['multiple'] ?? null) ? 'vbo-multiselect-inline-elems-wrap' : 'vbo-singleselect-inline-elems-wrap') . '">'; $wrapped = true; $style_selection = (bool) ($param_config['multiple'] ?? null); } elseif ($param_config['wrapdivcls'] ?? null) { // wrap the select within a custom div $html .= '<div class="' . $param_config['wrapdivcls'] . '">'; $wrapped = true; } // obtain the necessary HTML code for rendering $html .= VikBooking::getVboApplication()->renderElementsDropDown([ 'id' => $element_id, 'placeholder' => ($param_config['asset_options']['placeholder'] ?? null), 'allow_clear' => ($param_config['asset_options']['allowClear'] ?? $param_config['asset_options']['allow_clear'] ?? null), 'attributes' => $elements_attr, 'element_def_img_uri' => ($param_config['element_def_img_uri'] ?? ''), 'style_selection' => ($param_config['style_selection'] ?? $style_selection), 'selected_value' => (is_scalar($this->settings[$param_name] ?? null) ? $this->settings[$param_name] : (is_scalar($default_paramv ?? null) ? $default_paramv : null)), 'selected_values' => (is_array($this->settings[$param_name] ?? null) ? $this->settings[$param_name] : (is_array($default_paramv ?? null) ? $default_paramv : null)), ], (array) ($param_config['elements'] ?? []), (array) ($param_config['groups'] ?? [])); if ($wrapped) { // close the select div wrapper $html .= '</div>'; } break; case 'tags': // build attributes list $element_id = 'vik-select-' . static::$instance_counter . '-' . preg_replace("/[^A-Z0-9]+/i", '', $param_name); $elements_attr = [ 'name' => $this->inputName . '[' . $param_name . ']', ]; if ($param_config['multiple'] ?? null) { $elements_attr['multiple'] = 'multiple'; $elements_attr['name'] .= '[]'; } $custom_attr = (array) ($param_config['attributes'] ?? []); unset($custom_attr['id'], $custom_attr['name']); $elements_attr = array_merge($elements_attr, $custom_attr); $wrapped = false; $style_selection = (bool) ($param_config['style_selection'] ?? null); if ($param_config['inline'] ?? true) { // wrap the select within an additional div $html .= '<div class="' . (($param_config['multiple'] ?? null) ? 'vbo-multiselect-inline-elems-wrap' : 'vbo-singleselect-inline-elems-wrap') . '">'; $wrapped = true; $style_selection = (bool) ($param_config['multiple'] ?? null); } elseif ($param_config['wrapdivcls'] ?? null) { // wrap the select within a custom div $html .= '<div class="' . $param_config['wrapdivcls'] . '">'; $wrapped = true; } // obtain the necessary HTML code for rendering $html .= VikBooking::getVboApplication()->renderTagsDropDown([ 'id' => $element_id, 'placeholder' => ($param_config['asset_options']['placeholder'] ?? null), 'allow_clear' => ($param_config['asset_options']['allowClear'] ?? $param_config['asset_options']['allow_clear'] ?? null), 'attributes' => $elements_attr, 'selected_value' => (is_scalar($this->settings[$param_name] ?? null) ? $this->settings[$param_name] : (is_scalar($default_paramv ?? null) ? $default_paramv : null)), 'selected_values' => (is_array($this->settings[$param_name] ?? null) ? $this->settings[$param_name] : (is_array($default_paramv ?? null) ? $default_paramv : null)), 'style_selection' => $style_selection, ], (array) ($param_config['tags'] ?? []), (array) ($param_config['groups'] ?? [])); if ($wrapped) { // close the select div wrapper $html .= '</div>'; } break; case 'datetime': // build attributes list $element_id = 'vik-dtp-' . static::$instance_counter . '-' . preg_replace("/[^A-Z0-9]+/i", '', $param_name); $elements_attr = [ 'name' => $this->inputName . '[' . $param_name . ']', ]; $custom_attr = (array) ($param_config['attributes'] ?? []); unset($custom_attr['id'], $custom_attr['name']); $elements_attr = array_merge($elements_attr, $custom_attr); // obtain the necessary HTML code for rendering $html .= VikBooking::getVboApplication()->renderDateTimePicker([ 'id' => $element_id, 'attributes' => $elements_attr, ]); break; case 'password': $html .= '<div class="btn-wrapper input-append">'; $html .= '<input type="password" name="' . $this->inputName . '[' . $param_name . ']" value="'.(isset($this->settings[$param_name]) ? JHtml::fetch('esc_attr', $this->settings[$param_name]) : JHtml::fetch('esc_attr', $default_paramv)).'" size="20"' . $inp_attr . '/>'; $html .= '<button type="button" class="btn btn-primary" onclick="vboParamTogglePwd(this);"><i class="' . VikBookingIcons::i('eye') . '"></i></button>'; $html .= '</div>'; // set flag for JS helper $this->scripts[] = $param_config['type']; break; case 'number': $number_attr = []; if (isset($param_config['min'])) { $number_attr[] = 'min="' . JHtml::fetch('esc_attr', $param_config['min']) . '"'; } if (isset($param_config['max'])) { $number_attr[] = 'max="' . JHtml::fetch('esc_attr', $param_config['max']) . '"'; } if (isset($param_config['step'])) { $number_attr[] = 'step="' . JHtml::fetch('esc_attr', $param_config['step']) . '"'; } $html .= '<input type="number" name="' . $this->inputName . '[' . $param_name . ']" value="'.(isset($this->settings[$param_name]) ? JHtml::fetch('esc_attr', $this->settings[$param_name]) : JHtml::fetch('esc_attr', $default_paramv)).'" ' . implode(' ', $number_attr) . $inp_attr . '/>'; break; case 'textarea': $html .= '<textarea name="' . $this->inputName . '[' . $param_name . ']"' . $inp_attr . '>'.(isset($this->settings[$param_name]) ? JHtml::fetch('esc_textarea', $this->settings[$param_name]) : JHtml::fetch('esc_textarea', $default_paramv)).'</textarea>'; break; case 'visual_html': $tarea_cont = isset($this->settings[$param_name]) ? JHtml::fetch('esc_textarea', $this->settings[$param_name]) : JHtml::fetch('esc_textarea', $default_paramv); $tarea_attr = isset($param_config['attributes']) && is_array($param_config['attributes']) ? $param_config['attributes'] : []; $editor_opts = isset($param_config['editor_opts']) && is_array($param_config['editor_opts']) ? $param_config['editor_opts'] : []; $editor_btns = isset($param_config['editor_btns']) && is_array($param_config['editor_btns']) ? $param_config['editor_btns'] : []; $html .= VikBooking::getVboApplication()->renderVisualEditor($this->inputName . '[' . $param_name . ']', $tarea_cont, $tarea_attr, $editor_opts, $editor_btns); break; case 'codemirror': $editor = JEditor::getInstance('codemirror'); $e_options = isset($param_config['options']) && is_array($param_config['options']) ? $param_config['options'] : []; $e_name = $this->inputName . '[' . $param_name . ']'; $e_value = isset($this->settings[$param_name]) ? $this->settings[$param_name] : $default_paramv; $e_width = isset($e_options['width']) ? $e_options['width'] : '100%'; $e_height = isset($e_options['height']) ? $e_options['height'] : 300; $e_col = isset($e_options['col']) ? $e_options['col'] : 70; $e_row = isset($e_options['row']) ? $e_options['row'] : 20; $e_buttons = isset($e_options['buttons']) ? (bool)$e_options['buttons'] : true; $e_id = isset($e_options['id']) ? $e_options['id'] : $this->inputName . '_' . $param_name; $e_params = isset($e_options['params']) && is_array($e_options['params']) ? $e_options['params'] : []; if (interface_exists('Throwable')) { /** * With PHP >= 7 supporting throwable exceptions for Fatal Errors * we try to avoid issues with third party plugins that make use * of the WP native function get_current_screen(). * * @wponly */ try { $html .= $editor->display($e_name, $e_value, $e_width, $e_height, $e_col, $e_row, $e_buttons, $e_id, $e_asset = null, $e_autor = null, $e_params); } catch (Throwable $t) { $html .= $t->getMessage() . ' in ' . $t->getFile() . ':' . $t->getLine() . '<br/>'; $html .= '<textarea name="' . $this->inputName . '[' . $param_name . ']"' . $inp_attr . '>' . (isset($this->settings[$param_name]) ? JHtml::fetch('esc_textarea', $this->settings[$param_name]) : JHtml::fetch('esc_textarea', $default_paramv)) . '</textarea>'; } } else { $html .= $editor->display($e_name, $e_value, $e_width, $e_height, $e_col, $e_row, $e_buttons, $e_id, $e_asset = null, $e_autor = null, $e_params); } break; case 'hidden': $html .= '<input type="hidden" name="' . $this->inputName . '[' . $param_name . ']" value="'.(isset($this->settings[$param_name]) ? JHtml::fetch('esc_attr', $this->settings[$param_name]) : JHtml::fetch('esc_attr', $default_paramv)).'"' . $inp_attr . '/>'; break; case 'checkbox': // always display a hidden input value turned off before the actual checkbox to support the "off" (0) status $html .= '<input type="hidden" name="' . $this->inputName . '[' . $param_name . ']" value="0" />'; $html .= VikBooking::getVboApplication()->printYesNoButtons($this->inputName . '['.$param_name.']', JText::translate('VBYES'), JText::translate('VBNO'), (isset($this->settings[$param_name]) ? (int)$this->settings[$param_name] : (int)$default_paramv), 1, 0); break; case 'calendar': $e_options = isset($param_config['options']) && is_array($param_config['options']) ? $param_config['options'] : []; $e_id = isset($e_options['id']) ? $e_options['id'] : $this->inputName . '_' . $param_name; $html .= VikBooking::getVboApplication()->getCalendar($this->settings[$param_name] ?? $default_paramv, $this->inputName . '['.$param_name.']', $e_id, $e_options['df'] ?? null, $e_options['attributes'] ?? []); break; default: $html .= '<input type="text" name="' . $this->inputName . '[' . $param_name . ']" value="'.(isset($this->settings[$param_name]) ? JHtml::fetch('esc_attr', $this->settings[$param_name]) : JHtml::fetch('esc_attr', $default_paramv)).'" size="20"' . $inp_attr . '/>'; break; } return $html; } }