<?php if (!defined('PmWiki')) exit();
/*	Copyright 2009 Hans Bracker. 
	This file is toggle.php; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published
	by the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.
	
	(:toggle id=divname :) creates a toggle link, which can show or hide 
	a division or other object on the page, for instance a div created with
	
	>>id=divisionname<< 
	text can be hidden/shown 
	>><< 
	
	Necessary parameters:
	
	(:toggle id=divname:) 
	Alternative: (:toggle divname:)
	Alternative with options: 
	(:toggle hide divname:)			initial hide
	(:toggle hide divname button:)	initial hide, button
	(:toggle name1 name2:)			toggle between name1 and name2
	
	Optional parameters:
	
	id2=objname			second object (div), for toggling betwen first and second object
	init=hide			hides the division initially (default is show)
	show=labelname		label of link or button when div is hidden (default is Show)
	hide=labelname		label of link or button when div is shown (default is Hide)
	label=labelname		label of link or button for both toggle states
	set=1				sets a cookie to remember toggle state
	button=1			display a button instead of a link
	group=classname		on clicking show show div with associated id= (standard behaviour), 
							but hide all other divs with class classname. 
	ttshow=tooltiptext	text that appears when the user hovers over the "show" link 
							(default is Show) 
	tthide=tooltiptext	text that appears when the user hovers over the "hide" link 
							(default is Hide) 
	nojs=integer		set to 1 or 2 will show toggle links/buttons if browser 
							does not support javascript. Set to 2 will hide hidden 
							divs via style in page head and not via javascript, 
							so that for non-js browser initially hidden divs stay hidden. 
	display=value		what the display property of the specified div should be set to, 
							when it's shown (block, inline-block, etc.;
							see https://www.w3schools.com/CSSref/pr_class_display.asp 
							for details) 
*/
# Version date
$RecipeInfo['Toggle']['Version'] = '2021-11-18';

# declare $Toggle for (:if enabled Toggle:) recipe installation check
global $Toggle; $Toggle = 1;

# retrieve cookie
global $CookiePrefix, $pagename;
$current_page_group = PageVar($pagename, '$Group');
$current_page_name = PageVar($pagename, '$Name');
$toggle_cookie_name = "{$CookiePrefix}_toggle_{$current_page_group}_{$current_page_name}";
$toggle_cookie = json_decode($_COOKIE[$toggle_cookie_name],true);

Markup('toggle', 'directives', '/\\(:toggle\\s*(.*?):\\)/i', "ToggleMarkup");
	
# all in one function
function ToggleMarkup($m) {
	global $HTMLFooterFmt, $HTMLStylesFmt, $ToggleConfig, $ToggleLinks, $UploadUrlFmt, $UploadPrefixFmt, $toggle_cookie_name, $toggle_cookie;
	extract($GLOBALS['MarkupToHTML']);
	SDVA($ToggleConfig, array(
		'init' => 'show',			// initial state of element (visible)
		'show' => XL("Show"),		// link text 'Show'
		'hide' => XL("Hide"),		// link text 'Hide'
		'ttshow' => XL("Show"),		// tooltip text 'Show'
		'tthide' => XL("Hide"),		// tooltip text 'Hide'
		'id' => '',					// no default div name
		'id2' => '',				// no default div2 name
		'group' => '',				// no default group (class) name
		'display' => 'block',		// default to display:block;
		'set' => false,				// set no cookie to remember toggle state
		'printhidden' => true, 		// hidden divs get printed
		'nojs' => 0,				// in no jsbrowser links are not shown, initial hidden divs are shown
	));	 

	$HTMLStylesFmt['toggle'] = 
		" @media print { .toggle { display: none; } } \n" . 
		".toggle img { border: none; } \n";
	 
	# javascript for toggling and cookie setting
	$HTMLFooterFmt['toggleobj'] = "
<script type='text/javascript'><!--
	window.toggleData = { };
	window.toggleData.toggle_cookie_name = '{$toggle_cookie_name}';
	
	function toggleObj(id_of_element_to_toggle) {
		// Retrieve the Toggle state/data for the specified element.
		var T = window.toggleData[id_of_element_to_toggle];
		
		// If we're *showing* an element that's part of a defined group,
		// hide all the elements of the group first (including the specified
		// element itself, which will be re-shown immediately below).
		if (T.group != '' && T.new_state_to_toggle_to == 'show') {
			// Get all elements of the given class.
			document.querySelectorAll(`.\${T.group}`).forEach(function(element_in_group) { setToggleState(element_in_group, 'hide') });
		}	
		
		// Set the new state of the element.
		setToggleState(document.getElementById(id_of_element_to_toggle), T.new_state_to_toggle_to);
		
		// Toggle the alternate element, if any.
		// (T.new_state_to_toggle_to has now been reversed, by the line above.)
		if (T.id_of_alternate_element != '') setToggleState(document.getElementById(T.id_of_alternate_element), T.new_state_to_toggle_to, T.display);
	}
	
	function setToggleState(element, state, display = null) {
		// Retrieve the Toggle state/data for the specified element (if any).
		var T_e = window.toggleData[element.id];
		
		// Update the element's display.
		element.style.display = (state == 'show') ? (T_e ? T_e.display : display) : 'none';

		// If the element has an entry in the saved data 
		// (i.e. if it has a toggle element of its own),
		// update that saved data, and also update the toggle link/button.
		if (T_e) {
			// Set the new state, and update the saved data for the element.
			T_e.new_state_to_toggle_to = (state == 'show') ? 'hide' : 'show';			

			// Adjust the toggle link for the element.
			var label = (state == 'show') ? T_e.toggle_link_label_in_visible_state : T_e.toggle_link_label_in_hidden_state;
			var tooltip = (state == 'show') ? T_e.toggle_link_tooltip_in_visible_state : T_e.toggle_link_tooltip_in_hidden_state;
			document.getElementById(`\${element.id}-tog`).innerHTML = 
				(T_e.is_button == 1) ?
					`<input type='button' class='inputbutton togglebutton' title='\${tooltip}' value='\${label}' onclick=\"javascript:toggleObj('\${element.id}')\" />` : 
				`<a class='togglelink' title='\${tooltip}' href=\"javascript:toggleObj('\${element.id}')\">\${label}</a>`;
		
			// If cookie setting is enabled, save the new state in a cookie.
			if (T_e.set_cookie == 1) updateToggleCookie(element.id, state);
		}
	}
	
	function updateToggleCookie(element_id, state) {
		// Retrieve...
		var toggleCookieName = window.toggleData.toggle_cookie_name;
		var toggleCookieNameRegex = new RegExp(`\${toggleCookieName}=([^;]+)`);
		var toggleCookieData = document.cookie.match(toggleCookieNameRegex);
		var toggleElementStates = toggleCookieData ? JSON.parse(toggleCookieData[1]) : { };
		
		// Modify...
		toggleElementStates[element_id] = state;
		
		// Store.
 		document.cookie = `\${toggleCookieName}=\${JSON.stringify(toggleElementStates)}; path=/`;
	}	
--></script>\n";
	
	$opt = ParseArgs($m[1]); 

	// Get parameters without keys.
	if (is_array($opt[''])) {
		while (count($opt['']) > 0) {
			$parameter = array_shift($opt['']);
			if($parameter == 'button') $opt['button'] = 1;
			elseif($parameter == 'hide') $opt['init'] = 'hide';
			elseif($parameter == 'show') $opt['init'] = 'show';
			elseif(!isset($opt['id'])) $opt['id'] = $parameter;
			elseif(!isset($opt['id2'])) $opt['id2'] = $parameter;		 
		}
	}
	
	// Fill in un-specified parameters with defaults.
	$opt = array_merge($ToggleConfig, $opt);

	// Retrieve the ids of both the primary and (if specified) alternate elements.
	// (the 'div' options are for backwards compatibility with ShowHide and ToggleLink recipes)
//	The first version of these 2 lines is for PHP 5.3+; the second, for earlier versions
// 	$id = $opt['div'] ?: $opt['id'];
// 	$id2 = $opt['div2'] ?: $opt['id2'];
	$id = $opt['div'] ? $opt['div'] : $opt['id'];
	$id2 = $opt['div2'] ? $opt['div2'] : $opt['id2'];
	if ($id == '') return "//!Error:// no object id specified!"; 

	// Verify that the ids of both elements do not contain special characters
	// which are forbidden in CSS identifiers. (Among other things, this forbids
	// quotation marks, which prevents Javascript injection attacks via ids.)
	$CSS_forbidden_characters_regex = "/[!\\\"#$%&'()*+,\\.\\/\\:;<=>?@[\\\\\]^`{|}~]/";
	if (preg_match($CSS_forbidden_characters_regex, $id) ||
		preg_match($CSS_forbidden_characters_regex, $id2))
		return Keep("<span style='color:red; font-weight:bold;'>Invalid ID specified for element!</span>");
	
	// Value for the 'display' CSS property. Affects both the element and the alternate element.
	$display = $opt['display'];
	
	// Set labels for (both states of) the toggle link/button.
	$labels = array();
//	The first version of these 2 lines is for PHP 5.3+; the second, for earlier versions
// 	$labels['show'] = $opt['label'] ?: ($opt['lshow'] ?: $opt['show']);
// 	$labels['hide'] = $opt['label'] ?: ($opt['lhide'] ?: $opt['hide']);
	$labels['show'] = $opt['label'] ? $opt['label'] : ($opt['lshow'] ? $opt['lshow'] : $opt['show']);
	$labels['hide'] = $opt['label'] ? $opt['label'] : ($opt['lhide'] ? $opt['lhide'] : $opt['hide']);

	// Transform label text into image tags, if appropriate.
	// (since we won't be putting the label text through pmwiki markup 
	//  processing, which would normally process image attach links)
	// (but maybe we should? TODO: investigate this)
	// Also encode apostrophes (for non-images).
	$ipat = "/\.png|\.gif|\.jpg|\.jpeg|\.ico/";
	foreach($labels as $k => $val) {
		if(preg_match($ipat, $val)) {
			// Check for image, make image tag
			$prefix = (strstr($val, '/')) ? '/' : $UploadPrefixFmt; 
			$path = FmtPageName($UploadUrlFmt.$prefix, $pagename);
			$labels[$k] = "<img src=$path/$val title={$opt['tt'.$k]}&nbsp;$id />";
			$opt['button'] = '';
		} else {
			// Apostrophe encoding
			$labels[$k] = str_replace("'","&rsquo;",$val);
		}
	}
	
	// If the element is part of a defined group, then hide it, unless it's explicitly set to be initially-shown.
 	if ($opt['group'] != '' && $opt['init'] != 'show')
 		$opt['init'] = 'hide';

	// If set=1 (i.e. cookie setting enabled), then check if a cookie is set;
	// if it is, read the element's initial state from the cookie.
	if($opt['set'] == 1)
	//	The first version of this line is for PHP 5.3+; the second, for earlier versions
// 		$opt['init'] = $toggle_cookie[$id] ?: $opt['init'];
		$opt['init'] = $toggle_cookie[$id] ? $toggle_cookie[$id] : $opt['init'];
	
	/* OPTION RETRIEVAL ENDS; NOW PROCESSING BEGINS */

	// Set initial state, and update labels and target state 
	// (for when user clicks the toggle/link button).
	$display_property_value = ($opt['init'] == 'show') ? $display : 'none';
	if ($toggle_cookie[$id2] == false) $alternate_element_display_property_value = ($opt['init'] == 'show') ? 'none' : $display;
	$label = ($opt['init'] == 'show') ? $labels['hide'] : $labels['show'];
	$tooltip = ($opt['init'] == 'show') ? $opt['tthide'] : $opt['ttshow'];
	$state_to_toggle_to = ($opt['init'] == 'show') ? 'hide' : 'show';

	// Open script block.
	$HTMLFooterFmt[] = "<script type='text/javascript'><!--\n";

	// Set initial state of element.
	$HTMLFooterFmt[] = "	if (element = document.getElementById('{$id}')) { element.style.display = '{$display_property_value}'; }\n";
	$HTMLStylesFmt[] = "#{$id} { display: {$display_property_value}; } \n";
	
	// Set initial state of alternate element.
	if ($id2) { 
		$HTMLFooterFmt[] = "	if (element = document.getElementById('{$id2}')) { element.style.display = '{$alternate_element_display_property_value}'; }\n";
		$HTMLStylesFmt[] = " #{$id2} { display: {$alternate_element_display_property_value}; } \n";
	}
	
	// Set separate styles for print view, if 'printhidden' option is set.
	if ($opt['printhidden'] == 1) { 
		$HTMLStylesFmt[] = "@media print{ #{$id} { display: {$display}; } } \n";
		if ($id2)
			$HTMLStylesFmt[] = "@media print { #{$id2} { display: {$display}; } } \n";
	}

	// Save the Toggle state/data for this element.
	$HTMLFooterFmt[] = 
	"	window.toggleData['{$id}'] = { 
		'new_state_to_toggle_to': '{$state_to_toggle_to}',
		'toggle_link_label_in_hidden_state': '{$labels['show']}',
		'toggle_link_label_in_visible_state': '{$labels['hide']}',
		'toggle_link_tooltip_in_hidden_state': '{$opt['ttshow']}',
		'toggle_link_tooltip_in_visible_state': '{$opt['tthide']}',
		'id_of_alternate_element': '{$id2}',
		'display': '{$display}',
		'is_button': '{$opt['button']}',
		'group': '{$opt['group']}',
		'set_cookie': '{$opt['set']}'
		};\n";
	
	// Close script block.
	$HTMLFooterFmt[] = "--></script>\n";

	// Construct toggle link or button (later it is modified with javascript).
	$out = "<span id='{$id}-tog' class='toggle'>";
	if ($opt['button'] == 1) {
		$out .= 
			($opt['nojs'] == 0)
			? "<input type='button' class='inputbutton togglebutton' title='{$tooltip}' value='{$label}' onclick=\"javascript:toggleObj('{$id}')\" />" 
			: "<input type='button' class='inputbutton togglebutton' title='{$tooltip}' value='{$label}' />";
	} else {
		$out .= 
			($opt['nojs'] == 0) 
			? "<a class='togglelink' title='{$tooltip}' href=\"javascript:toggleObj('{$id}')\">{$label}</a>" 
			: "<a class='togglelink' title='{$tooltip}'>{$label}</a>";
	}
	$out .= "</span>";
	return Keep($out);
}
#EOF