| 1 |
<?php |
| 2 |
/** |
| 3 |
* @package VikBooking |
| 4 |
* @subpackage com_vikbooking |
| 5 |
* @author Alessio Gaggii - E4J srl |
| 6 |
* @copyright Copyright (C) 2024 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 |
* Helper class to render specific param types. |
| 15 |
* |
| 16 |
* @since 1.16.9 (J) - 1.6.9 (WP) |
| 17 |
*/ |
| 18 |
final class VBOParamsRendering |
| 19 |
{ |
| 20 |
/** |
| 21 |
* @var array |
| 22 |
*/ |
| 23 |
private $params = []; |
| 24 |
|
| 25 |
/** |
| 26 |
* @var array |
| 27 |
*/ |
| 28 |
private $settings = []; |
| 29 |
|
| 30 |
/** |
| 31 |
* @var array |
| 32 |
*/ |
| 33 |
private $scripts = []; |
| 34 |
|
| 35 |
/** |
| 36 |
* @var array |
| 37 |
*/ |
| 38 |
private $assets = []; |
| 39 |
|
| 40 |
/** |
| 41 |
* @var string |
| 42 |
*/ |
| 43 |
private $inputName = 'vboparams'; |
| 44 |
|
| 45 |
/** |
| 46 |
* @var int |
| 47 |
*/ |
| 48 |
private static $instance_counter = -1; |
| 49 |
|
| 50 |
/** |
| 51 |
* Class constructor is protected. |
| 52 |
* |
| 53 |
* @param array $params The form params to bind. |
| 54 |
* @param array $settings The form settings to bind. |
| 55 |
* |
| 56 |
* @see getInstance() |
| 57 |
*/ |
| 58 |
private function __construct(array $params, array $settings) |
| 59 |
{ |
| 60 |
// bind values |
| 61 |
$this->params = $params; |
| 62 |
$this->settings = $settings; |
| 63 |
|
| 64 |
// increase instance counter |
| 65 |
static::$instance_counter++; |
| 66 |
} |
| 67 |
|
| 68 |
/** |
| 69 |
* Proxy for immediately accessing the object and bind data. |
| 70 |
* |
| 71 |
* @param array $params The form params to bind. |
| 72 |
* @param array $settings The form settings to bind. |
| 73 |
* |
| 74 |
* @return VBOParamsRendering |
| 75 |
*/ |
| 76 |
public static function getInstance(array $params = [], array $settings = []) |
| 77 |
{ |
| 78 |
return new static($params, $settings); |
| 79 |
} |
| 80 |
|
| 81 |
/** |
| 82 |
* Sets the name to be used for rendering the param fields. |
| 83 |
* |
| 84 |
* @param string $name The name to use. |
| 85 |
* |
| 86 |
* @return self |
| 87 |
*/ |
| 88 |
public function setInputName($name) |
| 89 |
{ |
| 90 |
$this->inputName = (string) $name; |
| 91 |
|
| 92 |
return $this; |
| 93 |
} |
| 94 |
|
| 95 |
/** |
| 96 |
* Renders the injected form params and returns the HTML code. |
| 97 |
* |
| 98 |
* @param bool $load_assets Whether to load the necessary assets. |
| 99 |
* |
| 100 |
* @return string |
| 101 |
*/ |
| 102 |
public function getHtml($load_assets = true) |
| 103 |
{ |
| 104 |
if (!$this->params) { |
| 105 |
return ''; |
| 106 |
} |
| 107 |
|
| 108 |
$html = ''; |
| 109 |
|
| 110 |
foreach ($this->params as $param_name => $param_config) { |
| 111 |
if (empty($param_name)) { |
| 112 |
continue; |
| 113 |
} |
| 114 |
|
| 115 |
$labelparts = explode('//', (isset($param_config['label']) ? $param_config['label'] : '')); |
| 116 |
$label = $labelparts[0]; |
| 117 |
$labelhelp = isset($labelparts[1]) ? $labelparts[1] : ''; |
| 118 |
if (!empty($param_config['help'])) { |
| 119 |
$labelhelp = $param_config['help']; |
| 120 |
} |
| 121 |
|
| 122 |
$nested_style = (isset($param_config['nested']) && $param_config['nested']); |
| 123 |
$hidden_wrapper = $param_config['type'] === 'hidden'; |
| 124 |
if (isset($param_config['conditional']) && isset($this->params[$param_config['conditional']])) { |
| 125 |
$check_cond = isset($this->params[$param_config['conditional']]['default']) ? $this->params[$param_config['conditional']]['default'] : null; |
| 126 |
$check_cond = isset($this->settings[$param_config['conditional']]) ? $this->settings[$param_config['conditional']] : $check_cond; |
| 127 |
if (!is_null($check_cond) && (!$check_cond || !strcasecmp($check_cond, 'off'))) { |
| 128 |
// hide current field because the field to who this is dependant is "off" or disabled (i.e. 0) |
| 129 |
$hidden_wrapper = true; |
| 130 |
} |
| 131 |
} |
| 132 |
|
| 133 |
$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;"' : '') . '>'; |
| 134 |
if (strlen($label) && (!isset($param_config['hidden']) || $param_config['hidden'] != true)) { |
| 135 |
$html .= '<div class="vbo-param-label">' . $label . '</div>'; |
| 136 |
} |
| 137 |
$html .= '<div class="vbo-param-setting">'; |
| 138 |
|
| 139 |
// render field |
| 140 |
$html .= $this->getField($param_name, $param_config); |
| 141 |
|
| 142 |
// check for assets to be loaded, only once to obtain individual setups |
| 143 |
if ($load_assets) { |
| 144 |
$this->loadAssets($load_once = true); |
| 145 |
} |
| 146 |
|
| 147 |
if ($labelhelp) { |
| 148 |
$html .= '<span class="vbo-param-setting-comment">' . $labelhelp . '</span>'; |
| 149 |
} |
| 150 |
|
| 151 |
$html .= '</div>'; |
| 152 |
$html .= '</div>'; |
| 153 |
} |
| 154 |
|
| 155 |
// JS helper functions |
| 156 |
$html .= $this->getScripts(); |
| 157 |
|
| 158 |
return $html; |
| 159 |
} |
| 160 |
|
| 161 |
/** |
| 162 |
* Loads the requested assets, if any. |
| 163 |
* |
| 164 |
* @param bool $load_once True to unset the assets after loading. |
| 165 |
* |
| 166 |
* @return void |
| 167 |
*/ |
| 168 |
public function loadAssets($load_once = false) |
| 169 |
{ |
| 170 |
$doc = JFactory::getDocument(); |
| 171 |
foreach ($this->assets as $asset_type => $asset_elements) { |
| 172 |
if ($asset_type === 'select2') { |
| 173 |
// build list of selectors |
| 174 |
$ids_list = implode(', ', array_map(function($el) { |
| 175 |
return "#{$el}"; |
| 176 |
}, $asset_elements)); |
| 177 |
|
| 178 |
// check for asset options |
| 179 |
$asset_options = $this->assets['select2_options'] ?? null; |
| 180 |
$asset_options_str = $asset_options ? json_encode($asset_options) : ''; |
| 181 |
|
| 182 |
// add script declaration |
| 183 |
VikBooking::getVboApplication()->loadSelect2(); |
| 184 |
$doc->addScriptDeclaration( |
| 185 |
<<<JS |
| 186 |
jQuery(function() { |
| 187 |
jQuery('$ids_list').select2($asset_options_str); |
| 188 |
}); |
| 189 |
JS |
| 190 |
); |
| 191 |
} |
| 192 |
|
| 193 |
if ($load_once) { |
| 194 |
unset($this->assets[$asset_type]); |
| 195 |
} |
| 196 |
} |
| 197 |
} |
| 198 |
|
| 199 |
/** |
| 200 |
* Gets the necessary script tags. |
| 201 |
* |
| 202 |
* @return string |
| 203 |
*/ |
| 204 |
public function getScripts() |
| 205 |
{ |
| 206 |
$html = ''; |
| 207 |
|
| 208 |
if (in_array('password', $this->scripts)) { |
| 209 |
// toggle the password fields |
| 210 |
$html .= "\n" . '<script>' . "\n"; |
| 211 |
$html .= 'function vboParamTogglePwd(elem) {' . "\n"; |
| 212 |
$html .= ' var btn = jQuery(elem), inp = btn.parent().find("input").first();' . "\n"; |
| 213 |
$html .= ' if (!inp || !inp.length) {return false;}' . "\n"; |
| 214 |
$html .= ' var inp_type = inp.attr("type");' . "\n"; |
| 215 |
$html .= ' inp.attr("type", (inp_type == "password" ? "text" : "password"));' . "\n"; |
| 216 |
$html .= '}' . "\n"; |
| 217 |
$html .= "\n" . '</script>' . "\n"; |
| 218 |
} |
| 219 |
|
| 220 |
return $html; |
| 221 |
} |
| 222 |
|
| 223 |
/** |
| 224 |
* Renders the given param name according to config. |
| 225 |
* Eventually populates the assets and scripts to be loaded. |
| 226 |
* |
| 227 |
* @param string $param_name The param name. |
| 228 |
* @param array $param_config The param configuration. |
| 229 |
* |
| 230 |
* @return string |
| 231 |
*/ |
| 232 |
public function getField($param_name, $param_config) |
| 233 |
{ |
| 234 |
$html = ''; |
| 235 |
|
| 236 |
$inp_attr = ''; |
| 237 |
if (isset($param_config['attributes']) && is_array($param_config['attributes'])) { |
| 238 |
foreach ($param_config['attributes'] as $inpk => $inpv) { |
| 239 |
$inp_attr .= $inpk . '="' . $inpv . '" '; |
| 240 |
} |
| 241 |
$inp_attr = ' ' . rtrim($inp_attr); |
| 242 |
} |
| 243 |
|
| 244 |
$default_paramv = $param_config['default'] ?? null; |
| 245 |
|
| 246 |
switch ($param_config['type']) { |
| 247 |
case 'custom': |
| 248 |
$html .= $param_config['html']; |
| 249 |
break; |
| 250 |
case 'select': |
| 251 |
$options = isset($param_config['options']) && is_array($param_config['options']) ? $param_config['options'] : []; |
| 252 |
$is_assoc = (array_keys($options) !== range(0, count($options) - 1)); |
| 253 |
$element_id = 'vik-select-' . static::$instance_counter . '-' . preg_replace("/[^A-Z0-9]+/i", '', $param_name); |
| 254 |
$set_attr = true; |
| 255 |
if (isset($param_config['attributes']) && is_array($param_config['attributes']) && isset($param_config['attributes']['id'])) { |
| 256 |
$element_id = $param_config['attributes']['id']; |
| 257 |
$set_attr = false; |
| 258 |
} |
| 259 |
if (isset($param_config['assets']) && $param_config['assets']) { |
| 260 |
if (!isset($this->assets['select2'])) { |
| 261 |
$this->assets['select2'] = []; |
| 262 |
} |
| 263 |
$this->assets['select2'][] = $element_id; |
| 264 |
$this->assets['select2_options'] = $param_config['asset_options'] ?? null; |
| 265 |
} |
| 266 |
if (isset($param_config['multiple']) && $param_config['multiple']) { |
| 267 |
$html .= '<select name="' . $this->inputName . '[' . $param_name . '][]" multiple="multiple"' . $inp_attr . ($set_attr ? ' id="' . $element_id . '"' : '') . '>' . "\n"; |
| 268 |
} else { |
| 269 |
$html .= '<select name="' . $this->inputName . '[' . $param_name . ']"' . $inp_attr . ($set_attr ? ' id="' . $element_id . '"' : '') . '>' . "\n"; |
| 270 |
} |
| 271 |
foreach ($options as $optind => $optval) { |
| 272 |
// support nested array values for the option-group tags |
| 273 |
$group = null; |
| 274 |
$sel_opts = [$optind => $optval]; |
| 275 |
if (is_array($optval)) { |
| 276 |
$group = $optind; |
| 277 |
$sel_opts = $optval; |
| 278 |
} |
| 279 |
if ($group) { |
| 280 |
$html .= '<optgroup label="' . JHtml::fetch('esc_attr', JText::translate($group)) . '">' . "\n"; |
| 281 |
} |
| 282 |
foreach ($sel_opts as $optkey => $poption) { |
| 283 |
$checkval = $is_assoc ? $optkey : $poption; |
| 284 |
$selected = false; |
| 285 |
if (isset($this->settings[$param_name])) { |
| 286 |
if (is_array($this->settings[$param_name])) { |
| 287 |
$selected = in_array($checkval, $this->settings[$param_name]); |
| 288 |
} else { |
| 289 |
$selected = ($checkval == $this->settings[$param_name]); |
| 290 |
} |
| 291 |
} elseif (isset($default_paramv)) { |
| 292 |
if (is_array($default_paramv)) { |
| 293 |
$selected = in_array($checkval, $default_paramv); |
| 294 |
} else { |
| 295 |
$selected = ($checkval == $default_paramv); |
| 296 |
} |
| 297 |
} |
| 298 |
$html .= '<option value="' . ($is_assoc ? $optkey : $poption) . '"'.($selected ? ' selected="selected"' : '').'>'.$poption.'</option>' . "\n"; |
| 299 |
} |
| 300 |
if ($group) { |
| 301 |
$html .= '</optgroup>' . "\n"; |
| 302 |
} |
| 303 |
} |
| 304 |
$html .= '</select>' . "\n"; |
| 305 |
break; |
| 306 |
case 'password': |
| 307 |
$html .= '<div class="btn-wrapper input-append">'; |
| 308 |
$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 . '/>'; |
| 309 |
$html .= '<button type="button" class="btn btn-primary" onclick="vboParamTogglePwd(this);"><i class="' . VikBookingIcons::i('eye') . '"></i></button>'; |
| 310 |
$html .= '</div>'; |
| 311 |
// set flag for JS helper |
| 312 |
$this->scripts[] = $param_config['type']; |
| 313 |
break; |
| 314 |
case 'number': |
| 315 |
$number_attr = []; |
| 316 |
if (isset($param_config['min'])) { |
| 317 |
$number_attr[] = 'min="' . JHtml::fetch('esc_attr', $param_config['min']) . '"'; |
| 318 |
} |
| 319 |
if (isset($param_config['max'])) { |
| 320 |
$number_attr[] = 'max="' . JHtml::fetch('esc_attr', $param_config['max']) . '"'; |
| 321 |
} |
| 322 |
if (isset($param_config['step'])) { |
| 323 |
$number_attr[] = 'step="' . JHtml::fetch('esc_attr', $param_config['step']) . '"'; |
| 324 |
} |
| 325 |
$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 . '/>'; |
| 326 |
break; |
| 327 |
case 'textarea': |
| 328 |
$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>'; |
| 329 |
break; |
| 330 |
case 'visual_html': |
| 331 |
$tarea_cont = isset($this->settings[$param_name]) ? JHtml::fetch('esc_textarea', $this->settings[$param_name]) : JHtml::fetch('esc_textarea', $default_paramv); |
| 332 |
$tarea_attr = isset($param_config['attributes']) && is_array($param_config['attributes']) ? $param_config['attributes'] : []; |
| 333 |
$editor_opts = isset($param_config['editor_opts']) && is_array($param_config['editor_opts']) ? $param_config['editor_opts'] : []; |
| 334 |
$editor_btns = isset($param_config['editor_btns']) && is_array($param_config['editor_btns']) ? $param_config['editor_btns'] : []; |
| 335 |
$html .= VikBooking::getVboApplication()->renderVisualEditor($this->inputName . '[' . $param_name . ']', $tarea_cont, $tarea_attr, $editor_opts, $editor_btns); |
| 336 |
break; |
| 337 |
case 'codemirror': |
| 338 |
$editor = JEditor::getInstance('codemirror'); |
| 339 |
$e_options = isset($param_config['options']) && is_array($param_config['options']) ? $param_config['options'] : []; |
| 340 |
$e_name = $this->inputName . '[' . $param_name . ']'; |
| 341 |
$e_value = isset($this->settings[$param_name]) ? $this->settings[$param_name] : $default_paramv; |
| 342 |
$e_width = isset($e_options['width']) ? $e_options['width'] : '100%'; |
| 343 |
$e_height = isset($e_options['height']) ? $e_options['height'] : 300; |
| 344 |
$e_col = isset($e_options['col']) ? $e_options['col'] : 70; |
| 345 |
$e_row = isset($e_options['row']) ? $e_options['row'] : 20; |
| 346 |
$e_buttons = isset($e_options['buttons']) ? (bool)$e_options['buttons'] : true; |
| 347 |
$e_id = isset($e_options['id']) ? $e_options['id'] : $this->inputName . '_' . $param_name; |
| 348 |
$e_params = isset($e_options['params']) && is_array($e_options['params']) ? $e_options['params'] : []; |
| 349 |
if (interface_exists('Throwable')) { |
| 350 |
/** |
| 351 |
* With PHP >= 7 supporting throwable exceptions for Fatal Errors |
| 352 |
* we try to avoid issues with third party plugins that make use |
| 353 |
* of the WP native function get_current_screen(). |
| 354 |
* |
| 355 |
* @wponly |
| 356 |
*/ |
| 357 |
try { |
| 358 |
$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); |
| 359 |
} catch (Throwable $t) { |
| 360 |
$html .= $t->getMessage() . ' in ' . $t->getFile() . ':' . $t->getLine() . '<br/>'; |
| 361 |
$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>'; |
| 362 |
} |
| 363 |
} else { |
| 364 |
$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); |
| 365 |
} |
| 366 |
break; |
| 367 |
case 'hidden': |
| 368 |
$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 . '/>'; |
| 369 |
break; |
| 370 |
case 'checkbox': |
| 371 |
// always display a hidden input value turned off before the actual checkbox to support the "off" (0) status |
| 372 |
$html .= '<input type="hidden" name="' . $this->inputName . '[' . $param_name . ']" value="0" />'; |
| 373 |
$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); |
| 374 |
break; |
| 375 |
case 'calendar': |
| 376 |
$e_options = isset($param_config['options']) && is_array($param_config['options']) ? $param_config['options'] : []; |
| 377 |
$e_id = isset($e_options['id']) ? $e_options['id'] : $this->inputName . '_' . $param_name; |
| 378 |
$html .= VikBooking::getVboApplication()->getCalendar($this->settings[$param_name] ?? $default_paramv, $this->inputName . '['.$param_name.']', $e_id, $e_options['df'] ?? null, $e_options['attributes'] ?? []); |
| 379 |
break; |
| 380 |
default: |
| 381 |
$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 . '/>'; |
| 382 |
break; |
| 383 |
} |
| 384 |
|
| 385 |
return $html; |
| 386 |
} |
| 387 |
} |
| 388 |
|