PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.4
VikBooking Hotel Booking Engine & PMS v1.6.4
1.8.16 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 All 37 releases
vikbooking / libraries / adapter / module / widget.php

widget.php in VikBooking Hotel Booking Engine & PMS 1.6.4, at libraries/adapter/module/widget.php

654 lines 15.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikWP - Libraries
4 * @subpackage adapter.module
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 JLoader::import('adapter.form.form');
15
16 /**
17 * Adapter class to extend WP widget functionalities.
18 *
19 * @see WP_Widget
20 * @since 10.0
21 */
22 class JWidget extends WP_Widget
23 {
24 /**
25 * Absolute module path.
26 *
27 * @var string
28 */
29 protected $_path;
30
31 /**
32 * Internal ID.
33 *
34 * @var string
35 */
36 protected $_id;
37
38 /**
39 * Widget form data.
40 *
41 * @var JForm
42 */
43 protected $_form = null;
44
45 /**
46 * The name of the plugin that owns the module.
47 *
48 * @var string
49 */
50 protected $_option = null;
51
52 /**
53 * Whether or not the widget has been registered yet.
54 *
55 * @var boolean
56 * @since 10.1.38
57 */
58 protected $registered = false;
59
60 /**
61 * Class constructor.
62 *
63 * @param string $path The widget absolute path.
64 *
65 * @uses loadLanguage()
66 * @uses loadXml()
67 */
68 public function __construct($path)
69 {
70 // widget ID
71 $id = basename($path);
72
73 $this->_path = $path;
74 $this->_id = $id;
75
76 /**
77 * Extract component name from path.
78 *
79 * @since 10.1.20
80 */
81 if (preg_match("/plugins[\/\\\\]([a-z0-9_]+)[\/\\\\]modules/i", $this->_path, $match))
82 {
83 $this->_option = end($match);
84 }
85
86 // load text domain
87 $this->loadLanguage($path, $id);
88
89 /**
90 * @note: translations will be available only from here.
91 */
92
93 // load widget data from XML
94 $data = $this->loadXml($path, $id);
95
96 // translate widget name
97 $name = JText::translate((string) $data->name);
98
99 // build arguments
100 $args = array();
101 $args['description'] = JText::translate((string) $data->description);
102 // $args['version'] = $data->version;
103
104 /**
105 * Since the widget description is displayed by escaping HTML tags,
106 * we should strip them in order to display a plain text.
107 *
108 * @since 10.1.21
109 */
110 $args['description'] = strip_tags($args['description']);
111
112 parent::__construct($id, $name, $args);
113
114 /**
115 * Add support for jQuery in page head every time
116 * a widget is instantiated. Proceed only in case the
117 * headers haven't been sent yet.
118 *
119 * @since 10.1.22
120 */
121 if (!headers_sent())
122 {
123 add_filter('wp_enqueue_scripts', function()
124 {
125 wp_enqueue_script('jquery', null, array(), false, false);
126 });
127 }
128 }
129
130 /**
131 * Front-end display of widget.
132 *
133 * @param array $args Widget arguments.
134 * @param array $config Saved values from database.
135 *
136 * @return void
137 */
138 public function widget($args, $config)
139 {
140 // make the module helper accessible
141 JLoader::import('adapter.module.helper');
142 JModuleHelper::setPath($this->_path);
143
144 /**
145 * Include system.js file to support JFormValidator.
146 *
147 * @todo Check whether this callback is actually needed.
148 * The comment below mentions about the support for
149 * the validator, but it shouldn't be used in the
150 * front-end. In case the system.js file is required
151 * we could consider to load Bootstrap only in case
152 * we are in the site section.
153 */
154 JHtml::fetch('system.js');
155
156 /**
157 * Added support for module class suffix.
158 *
159 * @since 10.1.21
160 */
161 if (!empty($config['moduleclass_sfx']))
162 {
163 // extract class from wrapper
164 if (preg_match("/class=\"([a-z0-9_\-\s]*)\"/i", $args['before_widget'], $match))
165 {
166 // replace class attribute with previous classes and the custom suffix
167 $args['before_widget'] = str_replace($match[0], 'class="' . $match[1] . ' ' . $config['moduleclass_sfx'] . '"', $args['before_widget']);
168 }
169 }
170
171 // begin widget
172 echo $args['before_widget'];
173
174 // display the title if set
175 if (!empty($config['title']))
176 {
177 echo $args['before_title'] . apply_filters('widget_title', $config['title']) . $args['after_title'];
178 }
179
180 $layout = $this->_path . DIRECTORY_SEPARATOR . $this->_id . '.php';
181
182 // check if the widget owns a layout
183 if (!is_file($layout))
184 {
185 return;
186 }
187
188 // wrap the $config in a registry
189 $params = new JObject($config);
190
191 /**
192 * Create $module object for accessing the widget ID.
193 *
194 * @since 10.1.30
195 */
196 $module = new stdClass;
197 $module->id = $this->number;
198
199 /**
200 * Plugins can manipulate the configuration of the widget at runtime.
201 * Fires before dispatching the widget in the front-end.
202 *
203 * @param string $id The widget ID (path name).
204 * @param JObject &$params The widget configuration registry.
205 *
206 * @since 10.1.28
207 */
208 do_action_ref_array('vik_widget_before_dispatch_site', array($this->_id, &$params));
209
210 // start buffer
211 ob_start();
212 // include layout file
213 include $layout;
214 // get contents
215 $html = ob_get_contents();
216 // clear buffer
217 ob_end_clean();
218
219 /**
220 * Plugins can manipulate here the fetched HTML of the widget.
221 * Fires before displaying the HTML of the widget in the front-end.
222 *
223 * @param string $id The widget ID (path name).
224 * @param string &$html The HTML of the widget to display.
225 *
226 * @since 10.1.28
227 */
228 do_action_ref_array('vik_widget_after_dispatch_site', array($this->_id, &$html));
229
230 // display the widget HTML
231 echo $html;
232
233 // terminate widget
234 echo $args['after_widget'];
235
236 // print JSON configuration
237 JHtml::fetch('behavior.core');
238
239 // add support for Joomla JS variable
240 JFactory::getDocument()->addScriptDeclaration(
241 <<<JS
242 if (typeof Joomla === 'undefined') {
243 var Joomla = new JoomlaCore();
244 } else {
245 // reload options
246 JoomlaCore.loadOptions();
247 }
248 JS
249 );
250 }
251
252 /**
253 * Loads widget text domain.
254 *
255 * @param string $path The widget path.
256 * @param string $id The domain name.
257 *
258 * @return void
259 */
260 private function loadLanguage($path, $id)
261 {
262 // init language
263 $lang = JFactory::getLanguage();
264
265 // search for a language handler (/language/handler.php)
266 $handler = $path . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR . 'handler.php';
267
268 if (!is_file($handler))
269 {
270 /**
271 * Try also to search within "languages" folder.
272 *
273 * @since 10.1.21
274 */
275 $handler = $path . DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . 'handler.php';
276 }
277
278 if (is_file($handler))
279 {
280 // attach handler
281 $lang->attachHandler($handler, $id);
282 }
283
284 /**
285 * @since 10.0.1 It is no more needed to load the language
286 * file (.mo) of the widget as all the translations
287 * are contained within the main language file
288 * of the plugin.
289 */
290 }
291
292 /**
293 * Loads widget data from the XML installation file.
294 *
295 * @param string $path The widget path.
296 * @param string $id The widget name.
297 *
298 * @return object The XML data.
299 */
300 private function loadXml($path, $id)
301 {
302 $file = $path . DIRECTORY_SEPARATOR . $id . '.xml';
303
304 // make sure the installation file exists
305 if (!is_file($file))
306 {
307 throw new Exception('Missing installation file [' . $id . '.xml].', 404);
308 }
309
310 // load form data
311 $this->_form = JForm::getInstance($id, $file, array('client' => $this->_option));
312
313 // get XML element
314 $xml = $this->_form->getXml();
315
316 $data = new stdClass;
317
318 // iterate the args and assign them to the $data object
319 foreach (array('name', 'description') as $k)
320 {
321 $data->{$k} = (string) $xml->{$k};
322 }
323
324 return $data;
325 }
326
327 /**
328 * Back-end widget form.
329 *
330 * @param array $instance Previously saved values from database.
331 *
332 * @return void
333 */
334 public function form($instance)
335 {
336 // get form fields
337 $fields = $this->_form->getFields();
338
339 /**
340 * Add support for title field by creating a custom XML field,
341 * only if the XML of the module doesn't declare it.
342 *
343 * @since 10.1.21
344 */
345 if (!$this->_form->getField('title'))
346 {
347 // create title field
348 $title = simplexml_load_string('<field name="title" type="text" default="" label="TITLE" />');
349 // push title at the beginning of the list
350 array_unshift($fields, $title);
351 }
352
353 /**
354 * Filter the fields by removing useless settings.
355 *
356 * @since 10.1.31
357 */
358 $fields = array_filter($fields, function($field)
359 {
360 // exclude field in case it starts with "loadjquery"
361 return preg_match("/^loadjquery/", (string) $field->attributes()->name) == false;
362 });
363
364 // create layout file
365 $file = new JLayoutFile('html.widget.fieldset.open');
366
367 if ($this->_option)
368 {
369 // we found an option, add an include path to make sure layouts are accessible
370 $file->addIncludePath(implode(DIRECTORY_SEPARATOR, array(WP_PLUGIN_DIR, $this->_option, 'libraries')));
371 }
372
373 // open fieldset
374 echo $file->render();
375
376 foreach ($fields as $field)
377 {
378 $attrs = $field->attributes();
379 $name = (string) $attrs->name;
380
381 $data = array();
382 $data['id'] = $this->get_field_id($name);
383 $data['label'] = (string) $attrs->label;
384 $data['description'] = (string) $attrs->description;
385 $data['name'] = $this->get_field_name($name);
386 $data['required'] = ((string) $attrs->required) === 'true';
387
388 /**
389 * Open control only in case the input shouldn't be hidden.
390 *
391 * @since 10.1.21
392 */
393 if ($attrs->type != 'hidden' && $attrs->type != 'spacer' && empty($attrs->hidden))
394 {
395 // open control
396 $file->setLayoutId('html.widget.control.open');
397 echo $file->render($data);
398 }
399
400 if (isset($instance[$name]))
401 {
402 $data['value'] = $instance[$name];
403 }
404
405 // attach module path (useful to obtain the available layouts)
406 $data['modpath'] = $this->_path;
407 $data['modowner'] = $this->_option;
408
409 // obtain field class and display input layout
410 echo $this->_form->renderField($field, $data);
411
412 /**
413 * Close control only in case the input shouldn't be hidden.
414 *
415 * @since 10.1.21
416 */
417 if ($attrs->type != 'hidden' && $attrs->type != 'spacer' && empty($attrs->hidden))
418 {
419 // close control
420 $file->setLayoutId('html.widget.control.close');
421 echo $file->render();
422 }
423 }
424
425 // close fieldset
426 $file->setLayoutId('html.widget.fieldset.close');
427 echo $file->render();
428
429 // include form scripts
430 // $this->useScript();
431 }
432
433 /**
434 * Includes the scripts used by the form.
435 *
436 * @return void
437 */
438 protected function useScript()
439 {
440 if (wp_doing_ajax())
441 {
442 return;
443 }
444
445 $document = JFactory::getDocument();
446
447 /**
448 * Include system.js file to support JFormValidator.
449 *
450 * Since WP 5.9, the widgets resources must be loaded through the
451 * _register_one method, which seems to be invoked on every page.
452 * So, we should load them only if we are under widgets.php.
453 */
454 global $pagenow;
455 if ($pagenow === 'widgets.php')
456 {
457 JHtml::fetch('system.js');
458 }
459
460 JHtml::fetch('formbehavior.chosen');
461
462 static $loaded = 0;
463
464 // load only once
465 if (!$loaded)
466 {
467 // override getLabel() method to attach invalid
468 // class to the correct form structure
469 $document->addScriptDeclaration(
470 <<<JS
471 if (typeof JFormValidator !== 'undefined') {
472 JFormValidator.prototype.getLabel = function(input) {
473 var name = jQuery(input).attr('name');
474
475 if (this.labels.hasOwnProperty(name)) {
476 return jQuery(this.labels[name]);
477 }
478
479 return jQuery(input).parent().find('label').first();
480 }
481 }
482 JS
483 );
484 }
485
486 // load form validation
487 $document->addScriptDeclaration(
488 <<<JS
489 if (typeof VIK_WIDGET_SAVE_LOOKUP === 'undefined') {
490 var VIK_WIDGET_SAVE_LOOKUP = {};
491 }
492
493 (function($) {
494 $(document).on('widget-added', function(event, control) {
495 registerWidgetScripts($(control).find('form'));
496 });
497
498 function registerWidgetScripts(form) {
499 if (!form) {
500 // if the form was not provided, find it using the widget ID (before WP 5.8)
501 form = $('div[id$="{$this->id}"] form');
502 }
503
504 if (typeof JFormValidator !== 'undefined') {
505 // init internal validator
506 var validator = new JFormValidator(form);
507
508 // validate fields every time the SAVE button is clicked
509 form.find('input[name="savewidget"]').on('click', function(event) {
510 return validator.validate();
511 });
512 }
513
514 // init select2 on dropdown with multiple selection
515 if (jQuery.fn.select2) {
516 form.find('select[multiple]').select2({
517 width: '100%'
518 });
519 }
520
521 // initialize popover within the form
522 if (jQuery.fn.popover) {
523 form.find('.inline-popover').popover({sanitize: false, container: 'body'});
524 }
525 }
526
527 $(function() {
528 // If the widget is not a template, register the scripts.
529 // A widget template ID always ends with "__i__"
530 if (!"{$this->id}".match(/__i__$/)) {
531 registerWidgetScripts();
532 }
533
534 // Attach event to the "ADD WIDGET" button
535 $('.widgets-chooser-add').on('click', function(e) {
536 // find widget parent of the clicked button
537 var parent = this.closest('div[id$="{$this->id}"]');
538
539 if (!parent) {
540 return;
541 }
542
543 // extract ID from the template parent (exclude "__i__")
544 var id = $(parent).attr('id').match(/(.*?)__i__$/);
545
546 if (!id) {
547 return;
548 }
549
550 // register scripts with a short delay to make sure the
551 // template has been moved on the right side
552 setTimeout(function() {
553 // obtain the box that has been created
554 var createdForm = $('div[id^="' + id.pop() + '"]').last();
555
556 // find form within the box
557 var _form = $(createdForm).find('form');
558
559 // register scripts at runtime
560 registerWidgetScripts(_form);
561 }, 32);
562 });
563
564 // register save callback for this kind of widget only once
565 if (!VIK_WIDGET_SAVE_LOOKUP.hasOwnProperty('{$this->_id}')) {
566 // flag as loaded
567 VIK_WIDGET_SAVE_LOOKUP['{$this->_id}'] = 1;
568
569 // Attach event to SAVE callback
570 $(document).ajaxSuccess(function(event, xhr, settings) {
571 // make sure the request was used to save the widget settings
572 if (!settings.data || typeof settings.data !== 'string' || settings.data.indexOf('action=save-widget') === -1) {
573 // wrong request
574 return;
575 }
576
577 // extract widget ID from request
578 var widget_id = settings.data.match(/widget-id=([a-z0-9_-]+)(?:&|$)/i);
579
580 // make sure this is the widget that was saved
581 if (!widget_id) {
582 // wrong widget
583 return;
584 }
585
586 // get cleansed widget ID
587 widget_id = widget_id.pop();
588
589 // make sure the widget starts with this ID
590 if (widget_id.indexOf('{$this->_id}') !== 0) {
591 // wrong widget
592 return;
593 }
594
595 // obtain the box that has been updated
596 var updatedForm = $('div[id$="' + widget_id + '"]').find('form');
597
598 // register scripts at runtime
599 registerWidgetScripts(updatedForm);
600 });
601 }
602 });
603 })(jQuery);
604 JS
605 );
606 }
607
608 /**
609 * Sanitize widget form values as they are saved.
610 *
611 * @param array $new_instance Values just sent to be saved.
612 * @param array $old_instance Previously saved values from database.
613 *
614 * @return array Updated safe values to be saved.
615 *
616 * @since 10.1.21
617 */
618 public function update($new_instance, $old_instance)
619 {
620 if (!empty($new_instance['moduleclass_sfx']))
621 {
622 // make mod class suffix safe
623 $new_instance['moduleclass_sfx'] = preg_replace("/[^a-zA-Z0-9_\-\s]+/", '', $new_instance['moduleclass_sfx']);
624 }
625
626 return $new_instance;
627 }
628
629 /**
630 * Add hooks for enqueueing assets when registering all widget instances of this widget class.
631 *
632 * @param integer $number Optional. The unique order number of this widget instance
633 * compared to other instances of the same class. Default -1.
634 *
635 * @return void
636 *
637 * @since 10.1.38
638 */
639 public function _register_one($number = -1)
640 {
641 // invoke parent
642 parent::_register_one($number);
643
644 if (!$this->registered)
645 {
646 // load required resources
647 $this->useScript();
648
649 // flag as already registered
650 $this->registered = true;
651 }
652 }
653 }
654