<?php
/**
* Very simple tooltip implementation for admin pages.
*
* Example usage:
* <code>
* if (!class_exists('WpmuDev_HelpTooltips')) require_once YOUR_PLUGIN_BASE_DIR . '/lib/external/class_wd_help_tooltips.php';
* $tips = new WpmuDev_HelpTooltips();
* $tips->set_icon_url("URL_TO_YOUR_ICON");
* echo $tips->add_tip("Tip 1 text here");
* // ...
* echo $tips->add_tip("Tip 2 text here");
* </code>
* This is a basic usage scenario.
*
* Alternative usage example:
* <code>
* if (!class_exists('WpmuDev_HelpTooltips')) require_once YOUR_PLUGIN_BASE_DIR . '/lib/external/class_wd_help_tooltips.php';
* $tips = new WpmuDev_HelpTooltips();
* $tips->set_icon_url("URL_TO_YOUR_ICON");
* $tips->bind_tip('My tip text here', '.icon32:first ~h2');
* // Note that you don't echo anything in this usage example - the tip will be
* // added automatically next to the supplied selector (second argument)
* </code>
* This scenario may be useful for e.g. adding our tips to UI elements created by WP or other plugins.
* You can freely alternate between add_tip() and bind_tip() methods,
* just remember to echo add_tip() and *not* echo bind_tip().
*
* Another alternative usage example, setting tips for multiple pages in one place:
* <code>
* if (!class_exists('WpmuDev_HelpTooltips')) require_once YOUR_PLUGIN_BASE_DIR . '/lib/external/class_wd_help_tooltips.php';
* // Tips added to $tips1 object will only show on Social Marketing add/edit advert page
* $tips1 = new WpmuDev_HelpTooltips();
* $tips1->set_icon_url("URL_TO_YOUR_ICON");
* $tips1->set_screen_id("social_marketing_ad");
* $tips1->bind_tip('My tip 1 text here', '.icon32:first ~h2');
* // ...
* // Tips added to $tips2 object will only show on Social Marketing getting started page
* $tips2 = new WpmuDev_HelpTooltips();
* $tips2->set_icon_url("URL_TO_YOUR_ICON");
* $tips2->set_screen_id("social_marketing_ad_page_wdsm-get_started");
* $tips2->bind_tip('My tip 2 text here', '.icon32:first ~h2');
* </code>
* Using add_tip() method will do nothing in this last scenario, as it doesn't make sense in that context.
* This scenario may be useful for adding tooltips to all pages in one central location.
* E.g. for adding tooltips in a plugin add-on.
*/

class WpmuDev_HelpTooltips {

	/**
	* Holds an array of inline tips: used as dependency inclusion switch.
	*/
	private $_inline_tips = array();

	/**
	* Holds an array of bound tips: used as dependency inclusion switch and bound tips buffer.
	*/
	private $_bound_tips = array();

	/**
	* Holds an array of bound tips selectors: used as bound tips selectors buffer.
	*/
	private $_bound_selectors = array();

	/**
	* Full URL to help icon, which is used as tip anchor
	* and as notice background image.
	*/
	private $_icon_url;

	/**
	* Flag that determines do we want to use notices
	* (tips expanded on click).
	* Defaults to true.
	*/
	private $_use_notice = true;

	/**
	* Limits tip output to a screen (page).
	* Optional.
	* Works best with bind_tip() method.
	*/
	private $_screen_id = false;

	/**
	* Bind to footer hooks when instantiated.
	*/
	public function __construct () {
		global $wp_version;
		$version = preg_replace('/-.*$/', '', $wp_version);

		if (version_compare($version, '3.3', '>=')) {
			add_action('admin_footer', array($this, 'add_bound_tips'), 999);
			add_action('admin_print_footer_scripts', array($this, 'initialize'));
		}
	}

	/**
	* Sets icon URL.
	* @param string $icon_url Full URL to help anchor icon
	*/
	public function set_icon_url ($icon_url) {
		$this->_icon_url = $icon_url;
	}

	/**
	* Set show notices (tips expanded on click) flag.
	* @param bool $use_notice True to use notices (default), false otherwise.
	*/
	public function set_use_notice ($use_notice=true) {
		$this->_use_notice = $use_notice;
	}

	/**
	* Set screen limiting flag.
	* @param $screen_id Screen ID that tips in this object apply to.
	*/
	public function set_screen_id ($screen_id) {
		$this->_screen_id = $screen_id;
	}

	/**
	* Returns inline tip markup.
	* Scenario: for echoing inline tips next to elements on the page.
	* Usage example:
	* <code>
	* echo $tips->add_tip('My tip text here');
	* </code>
	* @param string $tip Tip text
	* @return string Inline tip markup
	*/
	public function add_tip ($tip) {
		if (!$this->_check_screen()) return false;
		$this->_inline_tips[] = $tip;
		return $this->_get_tip_markup($tip);
	}

	/**
	* Binds a tip to selector.
	* This is different from inline tips, as you don't have to output them yourself.
	* Scenario: for adding help tips next to elements determined by the selector on page load time.
	* Usage example:
	* <code>
	* $tips->bind_tip('My tip text here', '.icon32:first ~h2');
	* </code>
	* @param string $tip Tip text
	* @param string $selector jQuery selector of the element that tip is related to.
	*/
	public function bind_tip  ($tip, $bind_to_selector) {
		$tip_id = 'wpmudev-help-tip-for-' . md5($bind_to_selector);
		$this->_bound_tips[$tip_id] = $tip;
		$this->_bound_selectors[$tip_id] = $bind_to_selector;
	}

	/**
	* Bounded tips injection handler.
	* Will queue up the bounded tips.
	*/
	function add_bound_tips () {
		if (!$this->_check_screen()) return false;
		if (!$this->_bound_tips) return false;

		foreach ($this->_bound_tips as $id => $tip) {
			echo $this->_get_tip_markup($tip, 'id="' . $id . '" style=display:none');
		}
	}

	/**
	* Dependency injection handler.
	* Will only add dependencies if there are actual tooltips to show.
	*/
	function initialize () {
		if (!$this->_check_screen()) return false;
		if (!$this->_inline_tips && !$this->_bound_tips) return false;

		$this->_print_styles();
		$this->_print_scripts();
	}

	/**
	* Screen limitation check.
	* @return bool True if we're good to go, false if we're on a wrong screen.
	*/
	private function _check_screen () {
		if (!$this->_screen_id) return true; // No screen dependency

		$screen = get_current_screen();
		if (!is_object($screen)) return false; // Actually older then 3.3
		if ($this->_screen_id != @$screen->id) return false; // Not for this screen

		return true;
	}

	private function _get_tip_markup ($tip, $arg='') {
		return "<span class='wpmudev-help' {$arg}>{$tip}</span>";
	}

	/**
	* Private helper method that prints style dependencies.
	*/
	private function _print_styles () {
		// Have we already done this?
		if (!defined('WPMUDEV_TOOLTIPS_CSS_ADDED')) define('WPMUDEV_TOOLTIPS_CSS_ADDED', true);
		else return false;

		?>
		<style type="text/css">
			.wpmudev-help {
				display: block;
				background-color: #ffffe0;
				border: 1px solid #e6db55;
				padding: 1em 1em;
				-moz-border-radius:3px;
				-khtml-border-radius:3px;
				-webkit-border-radius:3px;
				border-radius:3px;
			}
			<?php
			if ($this->_icon_url) {
				?>
				.wpmudev-help {
					background: url(<?php echo $this->_icon_url; ?>) no-repeat scroll 10px center #ffffe0;
					padding-left: 40px;
				}
				.wpmudev-help-trigger span {
					display: block;
					position: absolute;
					left: -12000000px;
				}
				.wpmudev-help-trigger {
					position: relative;
					background: url(<?php echo $this->_icon_url; ?>) no-repeat scroll center bottom transparent;
					padding: 1px 12px;
					text-decoration: none;
				}
				<?php
			}
			?>
			#wpmudev-tooltip-source {
				margin: 0 13px;
				padding: 8px;

				background: #fff;
				border-style: solid;
				border-width: 1px;
				/* Fallback for non-rgba-compliant browsers */
				border-color: #dfdfdf;
				/* Use rgba to look better against non-white backgrounds. */
				border-color: rgba(0,0,0,.125);
				-webkit-border-radius: 3px;
				border-radius: 3px;

				-webkit-box-shadow: 0 2px 4px rgba(0,0,0,.19);
				-moz-box-shadow: 0 2px 4px rgba(0,0,0,.19);
				box-shadow: 0 2px 4px rgba(0,0,0,.19);
			}
			.wpmudev-left_pointer {
				float: left;
				width: 14px;
				height: 30px;
				margin-top: 8px;
				background: url(<?php echo site_url("/wp-includes/images/arrow-pointer-blue.png");?>) 0 -15px no-repeat;
			}
			.wpmudev-right_pointer {
				float: right;
				width: 14px;
				height: 30px;
				margin-top: 8px;
				background: url(<?php echo site_url("/wp-includes/images/arrow-pointer-blue.png");?>) -16px -15px no-repeat;
			}
		</style>
		<?php
	}

	/**
	* Private helper method that prints javascript dependencies.
	*/
	private function _print_scripts () {
		// Have we already done this?
		if (!defined('WPMUDEV_TOOLTIPS_JS_ADDED')) define('WPMUDEV_TOOLTIPS_JS_ADDED', true);
		else return false;

		// Initialize bound selectors
		$selectors = json_encode($this->_bound_selectors);

		?>
		<script type="text/javascript">
			(function ($) {

				/**
				* Converts help text placeholders to tooltip items.
				*/
				function initialize_help_item ($me) {
					var $prev = $me.prev();
					var help = '&nbsp;<a class="wpmudev-help-trigger" href="#help"><span><?php _e('Help');?></span></a>';
					$prev = $prev.length ?
					$prev.after(help)
					:
					$me.before(help)
					;
					$me.hide();
				}

				/**
				* Finds a help block corresponding to trigger.
				*/
				function get_help_block ($me) {
					var $parent = $me.parent();
					return $parent.find('.wpmudev-help');
				}

				/**
				* Handles help block visibility.
				*/
				function show_help_block ($me) {
					var $help = get_help_block($me);
					if (!$help.length) return false;

					if ($("#wpmudev-tooltip").length) $("#wpmudev-tooltip").remove();
					if ($help.is(":visible")) $help.hide('fast');
					else $help.show('fast');
					}

					/**
					* Pops tooltip open.
					*/
					function open_tooltip ($me) {
						var $help = get_help_block($me);
						if ($help.is(":visible")) return false;

						if ($("#wpmudev-tooltip").length) $("#wpmudev-tooltip").remove();
						if (!$("#wpmudev-tooltip").length) $("body").append('<div id="wpmudev-tooltip"><div class="wpmudev-pointer wpmudev-left_pointer"></div><div id="wpmudev-tooltip-source"></div></div>');
						var $tip = $("#wpmudev-tooltip");
						if (!$tip.length) return false;

						var width = 200;
						var margin = 20;
						var src_pos = $me.offset();

						var top_pos = src_pos.top + ($me.height() / 2);
						var left_pos = src_pos.left + margin;
						var $pointer = $tip.find(".wpmudev-pointer");

						// Setup left/right orientation
						<?php if (!is_rtl()) { ?>
							if ((left_pos+width+60) >= $(window).width()) {
								left_pos = src_pos.left - ($me.outerWidth()+width+margin);
								$pointer
								.removeClass("wpmudev-left_pointer")
								.addClass("wpmudev-right_pointer")
								;
							}
						<?php } else { ?>
							var min_left = left_pos - (width+60);
							if (min_left > 0) {
								left_pos = min_left;
								$pointer
								.removeClass("wpmudev-left_pointer")
								.addClass("wpmudev-right_pointer")
								;
							}
						<?php } ?>

						// IE safeguard
						if ($.browser.msie) {
							var $pointer_left = $tip.find(".wpmudev-left_pointer");
							if ($pointer_left.length) $pointer_left.css("position", "absolute");
						}

						$tip
						// Populate tip text
						.find("#wpmudev-tooltip-source")
						.width(width)
						.html($help.html())
						.end()
						// Position tip
						.css({
							"position": "absolute",
						})
						.offset({
							"top": top_pos - ($tip.height()/2),
							"left": left_pos
						})
						// Vertically align pointer
						.find(".wpmudev-pointer")
						.css({
							"margin-top": ($tip.height() - 32) / 2
						})
						.end()
						// Show entire tip
						.show()
						;
					}

					/**
					* Closes tooltip.
					*/
					function close_tooltip () {
						if (!$("#wpmudev-tooltip").length) return false;

						// IE conditional alternate removal
						if ($.browser.msie) {
							$("#wpmudev-tooltip").hide('fast');
						} else {
							// Not IE, do regular transparency animation
							$("#wpmudev-tooltip")
							.animate({
								"opacity": 0
							},
							'fast',
							function () {
								$(this).remove();
							}
							);
						}
					}


					// Init
					$(function () {

						// Populate and place bound tips
						$.each($.parseJSON('<?php echo $selectors; ?>'), function (tip_id, selector) {
							var $tip = $("#" + tip_id);
							if (!$tip.length) return true;

							var $selector = $(selector);
							if (!$selector.length) return true;

							$selector.append($tip);
						});

						// Initialize help and add handles
						$(".wpmudev-help").each(function () {
							initialize_help_item($(this));

						});

						// Handle help requests
						$(".wpmudev-help-trigger")
						.click(function (e) {
							<?php if ($this->_use_notice) { ?>
								show_help_block($(this));
							<?php } ?>
							return false;
						})
						.mouseover(function (e) {
							open_tooltip($(this));

						})
						.mouseout(close_tooltip)
						;


					});
				})(jQuery);
			</script>
			<?php
		}
	}