<?php
/*
**	Copyright (C) 1995-2007 by the ITools Authors.
**	This file is part of ITools - the Internet Tools Library
**
**	ITools is free software; 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 3 of the License, or
**	(at your option) any later version.
**
**	ITools is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
**
**	itjs.class - functions to handle javascript inclusion
*/

class itjs
{
static $charset;

/**
 * Send HTTP headers (content-type) to transmit javascript code
 */
static function send_headers($charset = null)
{
	if (!$charset)
		$charset = ini_get('default_charset') ?: 'iso-8859-1';

	self::$charset = $charset;

	@header("Content-Type: application/x-javascript; charset=$charset");
	@header('Expires: ' . gmdate('D, d M Y H:i:s', time()+10) . ' GMT');	# prevent broken data on IE reloads
}

/**
 * json_encode the result with options suitable for using as javascript source code
 * @param $values Array with values to be serialized
 * @return String with javascript code to be sent to client
 */
static function serialize($values)
{
	return json_encode($values, JSON_UNESCAPED_UNICODE | (it::is_devel() ? JSON_PRETTY_PRINT : 0));
}

/**
 * Convert UNTRUSTED comma separated filelist string to trusted local filenames. Missing files are ignored.
 */
static function filenames($filelist)
{
	$result = array();
	$local = $GLOBALS['ULTRAHOME'] . "/itjs";
	$itjs = "/www/server/phpinclude/itools/itjs";
	$libsearch = strlen($GLOBALS['debug_lib']) > 1 ? "/www/lib-" . $GLOBALS['debug_lib'] . ".search.ch" : "/www/lib.search.ch";
	$deprecated = array(
		"itools" => "$itjs/it.js,$itjs/http.js,$itjs/loader.js,$itjs/state.js,$itjs/timer.js",
		"boot.js" => "$itjs/boot.js",
	);
	$special = array(
		"error.gif" => "$itjs/error.gif",
		"search.css" => "$libsearch/doc/search.css",
		"prettyprint.css" => "$libsearch/doc/prettyprint.css",
	);

	foreach (it::match("[-\w.=?&]+", basename($filelist), array('all' => true)) as $file) # split by comma but ignore illegal chars
	{
		if ($deprecated[$file])
			continue;

		$filenames = $special[$file] ?: (file_exists("$local/" . it::match('^[^?]*', $file)) ? "$local/$file" : "$libsearch/itjs/$file");

		foreach (explode(",", $filenames) as $filename)
			if (!$seen[$filename]++ && file_exists(($fn = it::match('^[^?]*', $filename))) && is_file($fn))
				$result[] = $filename;
	}

	return $result;
}

/**
 * Return content of (php-interpreted by default) TRUSTED filenames that will be sent to client. Files must exist.
 */
static function filecontents($filenames)
{
	foreach ($filenames as $filename)
	{
		ob_start(); # Needs to capture inside loop to guarantee file order
		if (!($_GET['w3c'] && it::match('jquery-ui.css|owl.carousel.css|mobiscroll.custom|hopscotch.css|leaflet.css|-svg.css', $filename))) # BLACKLIST
		{
			$origget = $_GET;
			list($filename, $paramstr) = explode("?", $filename);
			if ($paramstr)
				parse_str($paramstr, $_GET);
			$result .= it::replace(array('^1$' => ""), it::match('\.(js|css|htc|html)$', $filename) ? include_once($filename) : (file_exists($filename) ? it::file_get_contents($filename) : it_url::get($filename)), array('utf8' => false));
			$_GET = $origget;
		}
		$result .= ob_get_clean();
	}

	return $result;
}

/**
 * Strip comments and trim lines
 * @param $code String containing javascript code to be stripped
 * @return Naked code
 */
static function strip($code)
{
	if (!it::is_devel())
		$code = preg_replace(array(
			'|\s//.*$|m',
			'|\s+/\*.*?\*/|s', # MUST require at least one space before /* (jquery.js) but MUST NOT require space before */ (searchmap.js)
			'|^\s+|m'
		), array(), $code);
	if (EDC('print'))
		$code = it::replace(array('@media\s+print' => '@media screen, print'), $code);

	return $code;
}

/**
 * Compute checksum for list of files
 * @param $fnlist Either comma separated UNTRUSTED url (will check itjs/ and lib.search.ch/itjs/) or array of TRUSTED filenames
 * @param $p['short_expire']     Check filemtime and generate short expires for new files
 * @param $p['additional_data']  Additional data to be included in checksum
 * @return Checksum for given files
 */
static function checksum($fnlist, $p = array())
{
	$p += array('short_expire' => true);

	$filenames = array();
	foreach (is_array($fnlist) ? $fnlist : itjs::filenames($fnlist) as $filename)
		$filenames[] = !file_exists($filename) && file_exists($t = it::replace(array('^/www/[^/]*' => "/www/lib.search.ch"), $filename)) ? $t : $filename;

	$additional = json_encode($p['additional_data']);
	$key = "itjs_" . md5(join("", it::map('"$v" . @filemtime("$v")', $filenames)) . $additional);

	if ($filenames && $p['short_expire'] && (time() - max(@array_map('filemtime', $filenames)) < 60))
		return "-"; # trigger short expire, our file may not yet be up to date on other slaves
	else if ($filenames)
		return it_cache::get($key) ?: it_cache::put($key, substr(md5("cloun" . self::filecontents($filenames) . $additional), 0, 10), array('ttl' => 60));
}

/**
 * Convert url or TRUSTED local path to url that triggers far future expire by appending c=checksum
 */
static function crcurl($url, $p = array())
{
	if (it::match('^(http|//)', $url)) # remote url, must fetch to crc
		list($fn, $short_expire) = array(it_url::get_cache(array('url' => $url, 'maxage' => 3600, 'id' => "itjs_crcurl") + $p), false);
	else
		list($fn, $short_expire) = array(($m = it::match("^//(\w+)(/.*)", $url)) ? "/www/$m[0].search.ch" . $m[1] : $GLOBALS['ULTRAHOME'] . "/doc$url", true);;

	return it::match('#', $url) ? U(trim($url, "#")) : U($url, array('c' => self::checksum(array($fn), array('short_expire' => $short_expire))));
}

# $p['nocrc'] means no $_REQUEST['c'] is needed for far future expire
static function far_future_headers($p = array())
{
	$crc = $_REQUEST['c'] ?: $_REQUEST['s'];
	if ($crc != "-" && !$_SERVER['HTTP_CACHE_CONTROL'] && $_SERVER['HTTP_IF_NONE_MATCH'] && !it::is_devel() && !$_REQUEST['retry'])
	{
		header("HTTP/1.0 304 Not Modified"); # client should always keep the component that fits the page it has
		exit;
	}

	if ($crc != "-")
		@header("Etag: alwaysvalid"); # we have checksums in the url. client should always keep the version he downloaded along with the html

	if (it::is_live() && !$_REQUEST['retry'])
	{
		$keeptime = $crc == "-" ? 0 : ($crc || $p['nocrc'] ? 30*86400 : 900); # long expire if checksum present
		header("Cache-Control: max-age=$keeptime, private"); # proxies should not cache since contents of same url can differ between browsers
		header("Expires: " . gmdate("D, d M Y H:i:s", time() + $keeptime). " GMT");
	}
}

}

?>