File "sticky_notes.php"

Full Path: /home/romayxjt/public_html/wp-content/plugins/vikbooking/admin/helpers/widgets/sticky_notes.php
File size: 26.27 KB
MIME-type: text/x-php
Charset: utf-8

<?php
/**
 * @package     VikBooking
 * @subpackage  com_vikbooking
 * @author      Alessio Gaggii - e4j - Extensionsforjoomla.com
 * @copyright   Copyright (C) 2018 e4j - Extensionsforjoomla.com. 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!');

/**
 * Class handler for admin widget "sticky notes". This widget has settings.
 * 
 * @since 	1.14.0 (J) - 1.4.0 (WP)
 */
class VikBookingAdminWidgetStickyNotes extends VikBookingAdminWidget
{
	/**
	 * The instance counter of this widget. Since we do not load individual parameters
	 * for each widget's instance, we use a static counter to determine its settings.
	 *
	 * @var 	int
	 */
	protected static $instance_counter = -1;

	/**
	 * We detect the Operating System through the browser useer agent
	 * to display different information about the shortcuts to use.
	 * 
	 * @var 	string
	 */
	protected $isMac = false;

	/**
	 * Class constructor will define the widget name and identifier.
	 */
	public function __construct()
	{
		// call parent constructor
		parent::__construct();

		// declare name, description and identifier
		$this->widgetName = JText::translate('VBO_W_STICKYN_TITLE');
		$this->widgetDescr = JText::translate('VBO_W_STICKYN_DESCR');
		$this->widgetId = basename(__FILE__, '.php');

		/**
		 * Define widget and icon and style name.
		 * 
		 * @since 	1.15.0 (J) - 1.5.0 (WP)
		 */
		$this->widgetIcon = '<i class="' . VikBookingIcons::i('thumbtack') . '"></i>';
		$this->widgetStyleName = 'yellow';

		// load widget's settings
		$this->widgetSettings = $this->loadSettings();

		// determine if the operating system is MacOS or iOS
		$ua = JFactory::getApplication()->input->server->getString('HTTP_USER_AGENT', '');
		if (stripos($ua, 'windows') === false && (stripos($ua, 'mac') !== false || stripos($ua, 'iphone') !== false || stripos($ua, 'ipad') !== false)) {
			$this->isMac = true;
		}
	}

	/**
	 * Custom method for this widget only to update one sticky note.
	 * The method is called by the admin controller through an AJAX request.
	 * The visibility should be public, it should not exit the process, and
	 * any content sent to output will be returned to the AJAX response.
	 * 
	 * @param 	int 	$force_note_index 	the new index can be forced to insert a new note.
	 * @param 	int 	$force_instance 	the widget's instance to force.
	 * @param 	string 	$force_txt 			the text of the sticky note to force.
	 * 
	 * @return 	void 	it may output a log with the new widget instance assigned.
	 */
	public function updateStickyNote($force_note_index = null, $force_instance = null, $force_txt = null)
	{
		$note_instance = VikRequest::getInt('note_instance', -1, 'request');
		$note_txt = VikRequest::getString('note_txt', '', 'request', VIKREQUEST_ALLOWRAW);
		$note_index = VikRequest::getInt('note_index', 0, 'request');

		if ($force_note_index !== null && $force_note_index >= 0) {
			// use the forced index instead
			$note_index = $force_note_index;
		}

		if ($force_instance !== null && $force_instance >= 0) {
			// use the forced instance instead
			$note_instance = $force_instance;
		}

		if (is_string($force_txt) && strlen($force_txt)) {
			// use the forced text instead
			$note_txt = $force_txt;
		}

		// make sure the settings of the widget are an array
		if (!is_array($this->widgetSettings)) {
			$this->widgetSettings = array();
		}

		// create the new sticky note object
		$username = $this->getLoggedUserName();
		$sticky_note = new stdClass;
		$sticky_note->html = $note_txt;
		$sticky_note->ts = time();
		if (!empty($username)) {
			$sticky_note->user = $username;
		}

		// eventually append debugging result
		$result_log = '';

		// eventually append the new widget instance assigned for newly added widgets via AJAX
		$new_instance_response = '';

		if (!count($this->widgetSettings)) {
			// push the first sticky note as the first instance of this widget
			$this->widgetSettings = array(
				array($sticky_note)
			);
			$result_log = 'pushing the first sticky note as the first instance of the widget';
			$new_instance_response = '[instance=0]';
		} elseif (isset($this->widgetSettings[$note_instance])) {
			// this instance was saved already
			if (isset($this->widgetSettings[$note_instance][$note_index])) {
				// we already have this exact sticky note index, so we replace it
				$this->widgetSettings[$note_instance][$note_index] = $sticky_note;
				$result_log = 'replacing requested note index in existing widget instance';
			} else {
				// we push a new sticky note
				array_push($this->widgetSettings[$note_instance], $sticky_note);
				$result_log = 'adding a new note to the existing widget instance';
			}
		} else {
			// this is a new instance of the widget, which has settings already - push the new instance and the new sticky note
			array_push($this->widgetSettings, array($sticky_note));
			$result_log = 'pushing a new instance and note to the settings';
			$new_instance_response = '[instance=' . (count($this->widgetSettings) - 1) . ']';
		}

		// update widget's settings
		$this->updateSettings(json_encode($this->widgetSettings));

		echo 'e4j.ok' . (!empty($result_log) ? '(' . $result_log . ')' : '') . $new_instance_response;
	}

	/**
	 * Custom method for this widget only to delete one sticky note.
	 * The method is called by the admin controller through an AJAX request.
	 * The visibility should be public, it should not exit the process, and
	 * any content sent to output will be returned to the AJAX response.
	 */
	public function deleteStickyNote()
	{
		$note_instance = VikRequest::getInt('note_instance', -1, 'request');
		$note_index = VikRequest::getInt('note_index', 0, 'request');

		// make sure the settings of the widget are an array
		if (!is_array($this->widgetSettings)) {
			echo 'e4j.error.no settings found';
			return;
		}

		// make sure the instance of the widget exists
		if (!isset($this->widgetSettings[$note_instance]) || !is_array($this->widgetSettings[$note_instance])) {
			echo 'e4j.error.instance not found';
			return;
		}

		// make sure the index of the note exists
		if (!isset($this->widgetSettings[$note_instance][$note_index])) {
			echo 'e4j.error.note index not found, maybe it was never updated before';
			return;
		}

		// splice the array to remove the requested note
		array_splice($this->widgetSettings[$note_instance], $note_index, 1);

		// update widget's settings
		$this->updateSettings(json_encode($this->widgetSettings));

		echo 'e4j.ok';
	}

	/**
	 * Custom method for this widget only to sort one sticky note.
	 * The method is called by the admin controller through an AJAX request.
	 * The visibility should be public, it should not exit the process, and
	 * any content sent to output will be returned to the AJAX response.
	 */
	public function sortStickyNote()
	{
		$note_instance = VikRequest::getInt('note_instance', -1, 'request');
		$note_index_new = VikRequest::getInt('note_index_new', 0, 'request');
		$note_index_old = VikRequest::getInt('note_index_old', 0, 'request');

		if ($note_index_new == $note_index_old) {
			// nothing to do, as notes can be sorted only within the same instance
			echo 'e4j.error.same position given for sticky note';
			return;
		}

		// make sure the settings of the widget are an array
		if (!is_array($this->widgetSettings)) {
			echo 'e4j.error.no settings found';
			return;
		}

		// make sure the instance of the widget exists
		if (!isset($this->widgetSettings[$note_instance]) || !is_array($this->widgetSettings[$note_instance])) {
			echo 'e4j.error.instance not found';
			return;
		}

		// make sure the old index of the note exists
		if (!isset($this->widgetSettings[$note_instance][$note_index_old])) {
			// check if this note was moved before getting saved, so as soon as it was added
			if (count($this->widgetSettings[$note_instance]) == $note_index_old) {
				// append the newly created (empty) note before moving it
				$this->updateStickyNote($note_index_old, $note_instance);
				// reload the widget's settings after they have been updated with the new note
				$this->widgetSettings = $this->loadSettings();
				// proceed below with moving the sticky note
			} else {
				// unable to proceed
				echo 'e4j.error.original note index not found, maybe it was never updated before';
				return;
			}
		}

		// make sure the new index can fit
		if ($note_index_new > (count($this->widgetSettings[$note_instance]) - 1)) {
			echo 'e4j.error.new index exceeds the highest position available';
			return;
		}

		// move the sticky note from the old index to the new index
		$extracted = array_splice($this->widgetSettings[$note_instance], $note_index_old, 1);
		array_splice($this->widgetSettings[$note_instance], $note_index_new, 0, $extracted);

		// update widget's settings
		$this->updateSettings(json_encode($this->widgetSettings));

		echo 'e4j.ok';
	}

	/**
	 * Preload the necessary assets.
	 * 
	 * @return 	void
	 */
	public function preload()
	{
		// JS lang def
		JText::script('VBO_STICKYN_TITLE');
		JText::script('VBO_STICKYN_TEXT');
		JText::script('VBO_STICKYN_TEXT2');
		JText::script('VBO_STICKYN_CUSTOMURI');
		JText::script('VBO_WIDGETS_CONFRMELEM');
	}

	public function render(?VBOMultitaskData $data = null)
	{
		// increase widget's instance counter
		static::$instance_counter++;

		// check whether the widget is being rendered via AJAX when adding it through the customizer
		$is_ajax = $this->isAjaxRendering();

		// check whether we are in the multitask panel
		$is_multitask = $this->isMultitaskRendering();

		// check whether the widget requires settings
		$needs_settings = !$is_ajax;
		$data_instance  = !$is_ajax ? static::$instance_counter : '-1';
		if ($is_multitask && $is_ajax) {
			$guess_inst_counter = $this->guessMultitaskStickyInstance($data);
			if ($guess_inst_counter !== false) {
				// force the loading of the guessed widget instance's settings
				static::$instance_counter = $guess_inst_counter;
				$data_instance = $guess_inst_counter;
				$needs_settings = true;
			}
		}

		// generate a unique ID for the sticky notes wrapper instance
		$wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
		$wrapper_id = 'vbo-widget-sticky-' . $wrapper_instance;

		// load the settings for this specific instance of the widget
		$instance_settings = array();
		if ($needs_settings && is_array($this->widgetSettings) && isset($this->widgetSettings[static::$instance_counter])) {
			$instance_settings = is_array($this->widgetSettings[static::$instance_counter]) ? $this->widgetSettings[static::$instance_counter] : array();
		}

		?>
		<div class="vbo-admin-widget-wrapper">
			<div class="vbo-admin-widget-head">
				<h4><?php echo $this->widgetIcon; ?> <span><?php echo $this->widgetName; ?></span></h4>
				<div class="btn-toolbar pull-right vbo-btn-toolbar-hastext">
					<span class="vbo-sticky-shortcuts-help"<?php echo count($instance_settings) ? ' style="display: none;"' : ''; ?>>
						<?php
						echo $this->vbo_app->createPopover(array(
							'title' => JText::translate('VBO_W_STICKYN_HELP_TITLE'),
							'content' => JText::translate(($this->isMac ? 'VBO_W_STICKYN_HELP_DESCR_MAC' : 'VBO_W_STICKYN_HELP_DESCR')),
							'icon_class' => VikBookingIcons::i('keyboard'),
							'placement' => 'left'
						));
						?>
					</span>
				</div>
			</div>
			<div id="<?php echo $wrapper_id; ?>" class="vbo-admin-widget-sticky-notes-wrap" data-instance="<?php echo $data_instance; ?>">
				<ul class="vbo-admin-widget-sticky-notes-list">
				<?php
				foreach ($instance_settings as $k => $sticky_note) {
					?>
					<li class="vbo-sticky-note">
						<div class="vbo-sticky-note-cmds">
							<span class="vbo-sticky-note-cmd-drag"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
							<span class="vbo-sticky-note-cmd-trash" onclick="vboWidgetStickyNoteDelete(this);"><?php VikBookingIcons::e('trash'); ?></span>
						</div>
						<div contenteditable="true" spellcheck="false" class="vbo-widget-sticky-canvas">
							<?php echo $sticky_note->html; ?>
						</div>
						<div class="vbo-sticky-note-sign">
							<span class="vbo-sticky-note-sign-dt"><?php echo date(str_replace("/", $this->datesep, $this->df).' H:i', $sticky_note->ts); ?></span>
						<?php
						if (!empty($sticky_note->user)) {
							?>
							<span class="vbo-sticky-note-sign-user"><?php echo $sticky_note->user; ?></span>
							<?php
						}
						?>
						</div>
					</li>
					<?php
				}
				?>
					<li class="vbo-sticky-note-add">
						<div class="vbo-sticky-note-add-inner" onclick="vboWidgetStickyNoteAdd(this);">
							<span><?php VikBookingIcons::e('plus-circle'); ?></span>
						</div>
					</li>
				</ul>
			</div>
		</div>

		<script type="text/javascript">
			// declare global variables for notes sorting
			var vbo_stickynote_initial_pos = null;

			jQuery(function() {

				// register input event listener for each sticky note
				var stickies<?php echo $wrapper_instance; ?> = document.querySelectorAll('#<?php echo $wrapper_id; ?> .vbo-widget-sticky-canvas');
				for (var i = 0; i < stickies<?php echo $wrapper_instance; ?>.length; i++) {
					stickies<?php echo $wrapper_instance; ?>[i].addEventListener('input', VBOCore.debounceEvent(vboWidgetStickyNoteUpdateTxt, 750));
				}

	 			if (typeof jQuery.fn.sortable !== 'undefined') {
		 			/**
		 			 * Make all sticky notes sortable. Do not use .disableSelection() or this
		 			 * will break all [contenteditable] elements and their focus/selection events.
		 			 */
					jQuery('#<?php echo $wrapper_id; ?> .vbo-admin-widget-sticky-notes-list').sortable({
						cursor: 'move',
						handle: '.vbo-sticky-note-cmd-drag',
						items: 'li.vbo-sticky-note',
						revert: false,
						start: function(event, ui) {
							// update sticky note initial position
							vbo_stickynote_initial_pos = jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-sticky-note').index(jQuery(ui.item));
						},
						update: function(event, ui) {
							// get sticky note new position
							var now_note = jQuery(ui.item);
							var new_note_index = jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-sticky-note').index(now_note);
							var widget_instance = now_note.closest('.vbo-admin-widget-sticky-notes-wrap').attr('data-instance');
							
							if (vbo_stickynote_initial_pos === null) {
								return;
							}

							// the widget method to call
							var call_method = 'sortStickyNote';

							// make a silent request to remove the sticky note
							VBOCore.doAjax(
								"<?php echo $this->getExecWidgetAjaxUri(); ?>",
								{
									widget_id: "<?php echo $this->getIdentifier(); ?>",
									call: call_method,
									note_index_old: vbo_stickynote_initial_pos,
									note_index_new: new_note_index,
									note_instance: widget_instance,
									tmpl: "component"
								},
								function(response) {
									// unset global note position var
									vbo_stickynote_initial_pos = null;
									try {
										var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
										if (!obj_res.hasOwnProperty(call_method)) {
											console.error('Unexpected JSON response', obj_res);
										}
									} catch(err) {
										console.error('could not parse JSON response', err, response);
									}
								},
								function(error) {
									// unset global note position var
									vbo_stickynote_initial_pos = null;
									console.error(error);
								}
							);
						}
					});
				} else {
					// hide sortable handler
					jQuery('#<?php echo $wrapper_id; ?> .vbo-admin-widget-sticky-notes-list').find('.vbo-sticky-note-cmd-drag').hide();
				}

			});
		</script>

		<?php
		if (static::$instance_counter === 0 || $is_ajax) {
		?>
		<script type="text/javascript">
			jQuery(function() {

				/**
				 * Add event listener to keydown for shortcuts during typing on contenteditable elements.
				 */
				document.onkeydown = function(e) {
					e = e || window.event;
					var active_el = document.activeElement;
					var exec_cmd = null;
					var exec_val = null;
					if ((!e.metaKey && !e.ctrlKey) || !active_el || !active_el.hasAttribute('contenteditable')) {
						return;
					}

					if (e.keyCode == 66) {
						// CMD + B detected
						exec_cmd = 'bold';
					} else if (e.keyCode == 85) {
						// CMD + U detected
						exec_cmd = 'underline';
					} else if (e.keyCode == 73) {
						// CMD + I detected
						exec_cmd = 'italic';
					} else if (e.keyCode == 83) {
						// CMD + S
						exec_cmd = 'strikeThrough';
					} else if (e.keyCode == 72 || e.keyCode == 84) {
						// CMD + H || CMD + T detected
						exec_cmd = 'formatBlock';
						exec_val = 'h2';
					} else if (e.keyCode == 80) {
						// CMD + P
						exec_cmd = 'formatBlock';
						exec_val = 'p';
					} else if (e.keyCode == 79 || e.keyCode == 78) {
						// CMD + O || CMD + N detected
						exec_cmd = 'insertOrderedList';
					} else if (e.keyCode == 76) {
						// CMD + L
						if (window.getSelection()) {
							var range = window.getSelection().getRangeAt(0);
							exec_val = range.toString();
							if (exec_val && exec_val.length) {
								// some text is selected
								if (exec_val.match(/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i)) {
									// URI selected, set command to create a link
									exec_cmd = 'createLink';
								} else {
									// just some plain text was selected, ask for a custom URI
									e.preventDefault();
									var link_uri = prompt(Joomla.JText._('VBO_STICKYN_CUSTOMURI'), '<?php echo JUri::root(); ?>');
									if (link_uri != null && link_uri != '' && link_uri.indexOf('http') >= 0) {
										exec_val = '<a href="' + link_uri + '">' + exec_val + '</a>';
										exec_cmd = 'insertHTML';
									}
								}
							} else {
								// no text selected, create an unordered list
								exec_val = null;
								exec_cmd = 'insertUnorderedList';
							}
						} else {
							// cannot access text selection, create an unordered list also in this case
							exec_val = null;
							exec_cmd = 'insertUnorderedList';
						}
					} else if (e.keyCode == 77 && window.getSelection()) {
						// CMD + M
						var txtsel = window.getSelection().getRangeAt(0).toString();
						if (txtsel && txtsel.length && txtsel.indexOf('<') === 0 && txtsel.substr(-1, 1) == '>') {
							// HTML tag selected, convert it from plain text to HTML code
							if (txtsel.indexOf('><') >= 0) {
								// hack for converting icons, where empty tags are not parsed correctly
								txtsel = txtsel.replace('><', '> <');
							}
							exec_val = txtsel + ' ';
							exec_cmd = 'insertHTML';
						}
					}

					if (exec_cmd !== null) {
						e.preventDefault();
						document.execCommand(exec_cmd, false, exec_val);
						active_el.focus();

						return false;
					}
				}

				/**
				 * Listen to mousedown event for clicks on links inside the elements
				 * with contenteditable, otherwise links in sticky notes won't open.
				 */
				jQuery(document.body).on('mousedown', '.vbo-widget-sticky-canvas[contenteditable]', function(e) {
					var elem = jQuery(e.target);
					if (elem.is('a')) {
						var goto = elem.attr('href');
						if (goto && goto.length && goto.indexOf('http') >= 0) {
							e.preventDefault();
							window.open(goto, '_blank');
							return false;
						}
					}
				});
			});
		</script>

		<script type="text/javascript">
			function vboWidgetStickyNoteAdd(elem) {
				// display the help for the shortcuts instructions
				jQuery(elem).closest('.vbo-admin-widget-wrapper').find('.vbo-sticky-shortcuts-help').fadeIn();
				// sticky note default HTML placeholder
				var sticky_placeholder = '<h2>' + Joomla.JText._('VBO_STICKYN_TITLE') + '</h2>' + "\n";
				sticky_placeholder += '<p>' + Joomla.JText._('VBO_STICKYN_TEXT') + '</p>' + "\n";
				sticky_placeholder += '<p>' + Joomla.JText._('VBO_STICKYN_TEXT2') + '</p>' + "\n";
				// build new sticky note HTML
				var html_sticky_new = '<div class="vbo-sticky-note-cmds">';
				html_sticky_new += '	<span class="vbo-sticky-note-cmd-drag"><?php VikBookingIcons::e('ellipsis-v'); ?></span>';
				html_sticky_new += '	<span class="vbo-sticky-note-cmd-trash" onclick="vboWidgetStickyNoteDelete(this);"><?php VikBookingIcons::e('trash'); ?></span>';
				html_sticky_new += '</div>';
				html_sticky_new += '<div contenteditable="true" spellcheck="false" class="vbo-widget-sticky-canvas">';
				html_sticky_new += sticky_placeholder;
				html_sticky_new += '</div>';
				
				// build new element and add HTML to it
				var sticky_new = document.createElement('li');
				sticky_new.setAttribute('class', 'vbo-sticky-note');
				sticky_new.innerHTML = html_sticky_new;

				// attach listener for input event
				sticky_new.addEventListener('input', VBOCore.debounceEvent(vboWidgetStickyNoteUpdateTxt, 750));
				
				// add new element to the document, before the button to add new sticky notes
				var append_to = jQuery(elem).closest('.vbo-admin-widget-sticky-notes-list').find('.vbo-sticky-note-add');
				append_to.before(jQuery(sticky_new));

				/**
				 * The input event is immediately triggered so that the newly added note will be saved to avoid
				 * problems when like adding two notes before even typing some text, and then moving/removing them.
				 */
				sticky_new.dispatchEvent(new Event('input'));
			}

			function vboWidgetStickyNoteDelete(elem) {
				var note_elem = jQuery(elem);
				var note_index = note_elem.closest('.vbo-admin-widget-sticky-notes-list').find('.vbo-sticky-note').index(note_elem.closest('.vbo-sticky-note'));
				var widget_instance = note_elem.closest('.vbo-admin-widget-sticky-notes-wrap').attr('data-instance');
				
				var confirm_lbl = Joomla.JText._('VBO_WIDGETS_CONFRMELEM');
				confirm_lbl = confirm_lbl.length ? confirm_lbl : 'Continue?';
				if (confirm(confirm_lbl)) {
					// the widget method to call
					var call_method = 'deleteStickyNote';

					// make a silent request to remove the sticky note
					VBOCore.doAjax(
						"<?php echo $this->getExecWidgetAjaxUri(); ?>",
						{
							widget_id: "<?php echo $this->getIdentifier(); ?>",
							call: call_method,
							note_index: note_index,
							note_instance: widget_instance,
							tmpl: "component"
						},
						function(response) {
							try {
								var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
								if (!obj_res.hasOwnProperty(call_method)) {
									console.error('Unexpected JSON response', obj_res);
								}
							} catch(err) {
								console.error('could not parse JSON response', err, response);
							}
						},
						function(error) {
							console.error(error);
						}
					);

					// remove the sticky note from the document
					note_elem.closest('li.vbo-sticky-note').remove();
				}
			}

			function vboWidgetStickyNoteUpdateTxt(event) {
				var note_elem = jQuery(this);
				// display the help for the shortcuts instructions
				note_elem.closest('.vbo-admin-widget-wrapper').find('.vbo-sticky-shortcuts-help').fadeIn();
				// element "this" may be different depending on how the event was triggered
				if (note_elem.find('.vbo-widget-sticky-canvas').length) {
					var note_txt = note_elem.find('.vbo-widget-sticky-canvas').html();
				} else {
					var note_txt = note_elem.html();
				}
				var note_index = note_elem.closest('.vbo-admin-widget-sticky-notes-list').find('.vbo-sticky-note').index(note_elem.closest('.vbo-sticky-note'));
				var widget_instance = note_elem.closest('.vbo-admin-widget-sticky-notes-wrap').attr('data-instance');

				// the widget method to call
				var call_method = 'updateStickyNote';

				// make a silent request to update the sticky note details
				VBOCore.doAjax(
					"<?php echo $this->getExecWidgetAjaxUri(); ?>",
					{
						widget_id: "<?php echo $this->getIdentifier(); ?>",
						call: call_method,
						note_txt: note_txt,
						note_index: note_index,
						note_instance: widget_instance,
						tmpl: "component"
					},
					function(response) {
						try {
							var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
							if (!obj_res.hasOwnProperty(call_method)) {
								console.error('Unexpected JSON response', obj_res);
							} else {
								// response for the method updateStickyNote() may contain the new instance given to the widget
								if (widget_instance < 0 && obj_res[call_method].indexOf('[instance=') >= 0) {
									// extract the new instance assigned
									var resp_left = obj_res[call_method].split('[instance=');
									var widget_instance_new = resp_left[1].split(']')[0];
									if (widget_instance_new && widget_instance_new.length) {
										// update widget's instance
										note_elem.closest('.vbo-admin-widget-sticky-notes-wrap').attr('data-instance', widget_instance_new);
									}
								}
							}
						} catch(err) {
							console.error('could not parse JSON response', err, response);
						}
					},
					function(error) {
						console.error(error);
					}
				);
			}
		</script>
		<?php
		}
	}

	/**
	 * Protected method to guess if the widget being rendered via AJAX in the
	 * multitask panel should load specific settings. Should be called when
	 * settings have been loaded, and if AJAX + multitask rendering is detected.
	 * It's assumed that the AJAX rendering of a multitask widget takes place one
	 * second before the AJAX event that updates the multitask map to push the widget.
	 * 
	 * @param 	?VBOMultitaskData 	$data 	the data object injected to the widget.
	 * 
	 * @return 	bool|int 	false on failure, guessed settings index otherwise.
	 */
	protected function guessMultitaskStickyInstance(?VBOMultitaskData $data = null)
	{
		if (!is_array($this->widgetSettings) || !count($this->widgetSettings)) {
			// nothing to guess if this widget has got no saved settings
			return false;
		}

		if (!is_object($data)) {
			// multitask data object must be set
			return false;
		}

		// the page must be set in the multitask object
		$vbo_page = $data->getPage();
		if (empty($vbo_page)) {
			// nothing to guess if no current page set
			return false;
		}

		// get the map for the current page
		$page_map = VikBooking::getAdminWidgetsInstance()->getMultitaskingMap($vbo_page, $whole = false);
		if (!is_array($page_map) || !count($page_map)) {
			// the multitask panel of this page has got no widgets saved, return the first index for settings
			return 0;
		}

		// count how many widgets of this type are already on this page
		$guessed_index = 0;
		foreach ($page_map as $widget_type) {
			if ($widget_type == $this->getIdentifier()) {
				$guessed_index++;
			}
		}

		// return the guessed index, which will load the next hypothetical instance
		return $guessed_index;
	}
}