. ** ** UltraHTML 3000 tool layer. Create functions for html tags. ** ** new it_html; ** echo html(head('title' => 'hello'), body(h1('hello'), p('Hello world!'))); **/ class it_html { /** * Create a HTML object and global functions for all methods (exlcluding * methods starting with '_') in this class plus the default tags (see below). * * @param $p Configuration settings. Can be set/overridden in constructor, configure(), html() or head(). * See source code for a list of supported values */ function it_html($p = array()) { # Default configuration of html class $this->p = $p + array( 'charset' => 'iso-8859-1', 'doctype' => null, # Custom doctype (will usually be calculated from htmltype) 'head' => '', # Code to put into head() section 'htmltype' => 'xhtml', # 'html' (=old-style), 'xhtml' or 'xhtml-mobile' 'lang' => 'de', # Language code to use in tag 'ie_png_fix' => false, # To enable, supply URL of a transparent gif (like /images/0.gif) 'moretags' => '', # Comma-separated list of tag-functions to generate additionally to 'tags' 'name' => 'it_html', # Name of global variable $this is assigned to (string), XXX Copy and paste in configure() to keep PHP4 compatibility 'nonewlinetags' => 'a,b,em,img,input,span', # tags that do not like newlines after them 'notexported' => 'configure,sanitize',# Those methods are not exported 'prettyprint' => false, # Should output be prettily indented? 'show_boot_dom' => false, # If true, append invisible
at the end of body 'show_content_type' => true, # If true, add header 'show_favicon' => true, # If true, add tag to /favicon.ico if it exists 'staticallycallable' => 'q,u,select', # Those methods are statically callable (have same arguments as global stubs) but are a bit slower 'tags' => 'a,b,br,button,div,em,fieldset,form,h1,h2,h3,h4,h5,h6,hr,input,label,legend,li,meta,noscript,p,span,style,table,td,textarea,th,tr,ul', 'title' => '', # HTML title (default: no title added) 'use_it_state' => false, # If true, generate code needed by state.js (aka 'history iframe') 'autoquote' => false, # Automatically pass plain strings through Q() 'reportquote' => 0, # Report plain strings with this error level (e.g. E_USER_NOTICE) ); # We know these doctypes. If you need something else, supply 'doctype' in p $this->doctypes = array( 'html' => '', 'xhtml' => '', 'xhtml-mobile' => '' ); $this->hasnonewline = array_flip(explode(',', $this->p['nonewlinetags'])); $notexported = array_flip(explode(',', "dummy," . $this->p['notexported'])); # dummy keeps values > 0 # Create global functions for _tags foreach (array_merge(explode(',', $this->p['tags']), explode(',', $this->p['moretags'])) as $func) { if (!function_exists($func) && $func) $code[$func] = "function $func() { \$args = func_get_args(); return new it_tag('$func', \$args); }"; } # Create global functions for it_html methods foreach (get_class_methods(get_class($this)) as $func) { if (!preg_match('/^_/', $func) && !is_a($this, $func) && $func && !function_exists($func) && !$notexported[$func]) $code[$func] = "function $func() { \$args = func_get_args(); return \$GLOBALS['{$this->p['name']}']->$func(\$args); }"; } # Create global functions for methods that are statically callable (have same arguments as global stubs) foreach (explode(',', $this->p['staticallycallable']) as $func) { if ($func && !function_exists($func)) $code[$func] = "function $func() { \$args = func_get_args(); return call_user_func_array(array(&\$GLOBALS['{$this->p['name']}'], '$func'), \$args); }"; } eval(join('', (array)$code)); # Since name is given as param, it is our duty to store it, not our caller's. $GLOBALS[$this->p['name']] =& $this; } /** * Modify configuration of it_html, e.g. htmltype after it was instantiated. * @param $p Configuration settings. Can be set/overridden in constructor, configure(), html() or head(). * See constructor for a list of supported values */ function configure($p) { $p += array('name' => "it_html"); # XXX Copy and paste from constructor to keep PHP4 compatibility $GLOBALS[$p['name']]->p = $p + (array)$GLOBALS[$p['name']]->p; $GLOBALS[$p['name']]->hasnonewline = array_flip(explode(',', $GLOBALS[$p['name']]->p['nonewlinetags'])); } /** * Return doctype and entire HTML page. * Example application code to render page: * echo html(head(...), body(...)); * * @param any number of text args or array of key => value pairs * Defaults for key => value parameters are inherited from the it_html constructor and should be set there. * The parameters are $p['lang'], $p['htmltype'] and $p['doctype'] */ function html($args) { $result = new it_tag('html'); list($data, $p) = $this->_parse_args($args); $p += $this->p; $result->prefix = EDC('upd') ? "" : (($p['doctype'] ? $p['doctype'] : $this->doctypes[$p['htmltype']]) . "\n"); if ($p['htmltype'] == "xhtml") $result['xmlns'] = "http://www.w3.org/1999/xhtml"; $result[$p['htmltype'] == "xhtml-mobile" ? "xml:lang" : "lang"] = $p['lang']; $result[] = $data; return $result; } /** * Return HTML header on first call or empty string on subsequent calls * * @param args... any number of assoc arrays and strings. strings will be content of * @param $p['content-type'] content type (default: "text/html; charset=iso-8859-1") * @param $p['cssinline'] stylesheet to be put in header * @param $p['description'] data for tag * @param $p['keywords'] data for tag * @param $p['stylesheets'] array mediatype => url of styleshests * @param $p['title'] content of , will be html encoded */ function head($args = array()) { $result = array(); if (!$this->head_sent++) { $result = new it_tag('head'); list($data, $p) = $this->_parse_args($args); $p += $this->p; $this->p = ($p += array('content-type' => "text/html; charset={$p['charset']}")); if ($p['show_content_type']) $result[] = new it_tag('meta', array('http-equiv' => "Content-Type", 'content' => $p['content-type'])); foreach(array('description', 'keywords') as $name) if (!empty($p[$name])) $result[] = new it_tag('meta', array('name' => $name, 'content' => $p[$name])); # Add favicon if file exists if ($p['show_favicon'] && @file_exists($_SERVER['DOCUMENT_ROOT'] . '/favicon.ico')) $result[] = new it_tag('link', array('rel' => "shortcut", 'href' => "/favicon.ico")); foreach((array)$p['stylesheets'] as $type => $url) $result[] = new it_tag('link', array('rel' => "stylesheet", 'type' => "text/css", 'href' => $url) + (is_int($type) ? array() : array('media' => $type))); if (!empty($p['cssinline'])) $result[] = new it_tag('style', array('type' => "text/css", "\n" . preg_replace(array('/\s*\/\*[^\*]+\*\//Um', '/\s*\{\s*/', '/;\s+/'), array('', '{', ';'), $p['cssinline']))); if ($p['head']) $result[] = $p['head']; $result[] = new it_tag('title', new it_q($p['title'])); if($this->p['htmltype'] == "xhtml-mobile" && strpos($_SERVER['HTTP_USER_AGENT'], 'W3C_Validator')) header("Content-Type: application/xhtml+xml; charset={$this->p['charset']}"); # for validation else if (!headers_sent()) # prevent warnings when ED() in use header("Content-Type: " . $p['content-type']); $js = $p['jsenv'] ? "var env = " . itjs::serialize($p['jsenv']) . ";\n" : ''; if ($p['js']) { $checksum = itjs::checksum(itjs::filenames($p['js'])); $js .= $this->_itjs("boot.js", "inline"); $js .= "function it_boot_start(){ " . trim($p['jsboot']) . " }\n"; $js .= "it_boot('/itjs/" . U($p['js'], array('s' => $checksum)) . "');\n"; } $js .= $this->_itjs($p['jsinline'], 'inline'); if ($js) $result[] = $this->js(array($js)); foreach ($data as $value) $result[] = $value; } return $result; } /** * Return HTML head (if not already sent) and body with it_state and it_boot magic code */ function body($args) { $result = $this->head(); if ($this->p['use_it_state']) array_unshift($args, new it_tag('iframe', array('id' => "it_state", 'src' => "/itjs/state.html", 'width' => 1, 'height' => 1, 'frameborder' => 0))); if ($this->p['show_boot_dom']) $args[] = new it_tag('div', array('id' => "it_boot_dom", 'style' => "visibility:hidden")); $result[] = new it_tag('body', $args); return $result; } /** * INTERNAL: Parse an arg array (mixed key=>value pairs and strings) and return it * as array(0 => all numerical args concatenated, 1 => array(key=>value pairs) */ function _parse_args($args) { $p = array(); $data = array(); foreach ($args as $arg) { if (is_array($arg)) { foreach ($arg as $key => $value) { if (is_int($key)) $data[] = $value; else $p[$key] = $value; } } else $data[] = $arg; } return array($data, $p); } /** * function div($args...) * Return a <div>...</div> element * Any strings in the argument list will be content of the <div> * Any associative arrays among the arguments will create attributes for <div>. * Attributes with values false or null will be omitted completely. * Attributes with value true can be used for boolean attributes like 'checked' * Attribute values will be html encoded, content values won't so you may need to use Q(). * @see _tag() */ #:} /** * Return a <tag> containing optional data. * @param $name tag name ('style', etc.) * @param ... any number optional data or array of key => value arguments * @return string containing XML/HTML tag */ function tag($args) { $name = array_shift($args); return new it_tag($name, $args); } /** * Special img() function patches png transparency for IE 5.5-6 if ie_png_fix is set * @param ... any number optional data or array of key => value arguments * @return <img ... /> */ function img($args) { if ($this->p['ie_png_fix'] && preg_match('/MSIE [56]/', $_SERVER['HTTP_USER_AGENT'])) { foreach($args as $id => $arg) { if (preg_match('/\.png(\?.*)?$/', $arg['src'])) { $args[$id]['style'] = "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='{$arg['src']}',sizingMethod='scale');" . $arg['style']; $args[$id]['src'] = $this->p['ie_png_fix']; } } } return new it_tag("img", $args); } /** * Create a dropdown menu object. Warning: encodes html code within options! * @param $tags key => value pairs of <select> tag * @param $options array (value => text) of available options or * string key:val{,key:val} where key will be rawurldecoded so it may contain %2C as comma * supports optgroups as array (value => optgroup => array(value => text)) * @param $selected optional currently selected value, or comma-separated list or array for multi-select * Note: use tag('select') and tag('option') if you want do roll your own */ function select($tags, $options, $selected = null) { $result = array(); # Transmogrify key:val{,key:val} to array(key => val) if (!is_array($options)) { $opts = explode(',', $options); $options = array(); foreach($opts as $opt) { list($key, $value) = explode(':', $opt); $options[rawurldecode($key)] = $value; } } $selected = ($tags['multiple'] && is_string($selected)) ? explode(',', $selected) : (array)$selected; $html = ""; foreach($options as $value => $option) { if (is_array($option)) { $group = new it_tag('optgroup', array('label' => $value)); foreach($option as $optval => $opt) $group[] = new it_tag("option", array(array('value' => $optval, 'selected' => in_array((string)$optval, $selected)), new it_q($opt))); $result[] = $group; } else $result[] = new it_tag("option", array(array('value' => $value, 'selected' => in_array((string)$value, $selected), 'disabled' => $option === ""), (trim($option) === "") ? " " : new it_q($option))); } return new it_tag("select", array($tags, $result)); } /** * Return HTML with all evil things stripped. Allowed are a coupld of simple * tags like div, p, i, b, br without attributes, a with absolute href, * img with absolute src url. Also ensures that tags are balanced. * @param $html HTML string to be sanitized * @return Sanitized HTML */ function sanitize($html) { $result = ""; $html = it::replace(array('[\0\s]+' => " "), $html); # \s also matches \r and \n $urlpattern = 'https?://[^">]+'; if ($tag = it::match("(.*)<(div|p|i|b)[^>]*>(.*?)</\\2>(.*)", $html)) { # Simple tags with content, no attributes kept list($head, $tagname, $content, $tail) = $tag; $tagname = strtolower($tagname); $result .= it_html::sanitize($head) . "<$tagname>" . it_html::sanitize($content) . "</$tagname>" . it_html::sanitize($tail); } else if ($tag = it::match('(.*)<a[^>]+?href="(' . $urlpattern . ')"[^>]*?>(.*?)</a>(.*)', $html)) { # Link tags, keeps only href attribute list($head, $href, $content, $tail) = $tag; $result .= it_html::sanitize($head) . '<a href="' . new it_q(html_entity_decode($href)) . '">' . it_html::sanitize($content) . "</a>" . it_html::sanitize($tail); } else if ($tag = it::match('(.*)<img[^>]+?src="(' . $urlpattern . ')"[^>]*?>(.*)', $html)) { # Image tags, keeps only src attribute list($head, $src, $tail) = $tag; $result .= it_html::sanitize($head) . '<img src="' . new it_q(html_entity_decode($src)) . '" alt="" />' . it_html::sanitize($tail); } else if ($tag = it::match("(.*)<(br)[^>]*>(.*)", $html)) { # Simple tags without content, no attributes kept list($head, $tagname, $tail) = $tag; $tagname = strtolower($tagname); $result .= it_html::sanitize($head) . "<$tagname />" . it_html::sanitize($tail); } else $result = new it_q(it::replace(array('&#\d+;' => ""), html_entity_decode(strip_tags($html)))); return $result; } /** * Shortcut: return htmlspecialchars($string) and encode forbidden characters 80-9f if latin1 is output * @param $args Data to encode with htmlspecialchars() * @return htmlspecialchars($args) */ function q($args) { $args = (array)$args; return new it_q($args[0]); } /** * Mark text as not to be encoded (e.g. html from db) * @param $args Data not to be encoded * @return Data marked as not to be encoded */ function nq($args) { $args = (array)$args; return new it_q($args[0], false); } /** * Build a complete url from base-url and params * @param ... scalar args and numeric indices build base-url, rest as params */ function u(/* ... */) { $args = func_get_args(); list($base, $params) = it_html::_parse_args($args); $base = count($base) == 0 ? $_SERVER['PHP_SELF'] : join("", $base); $base = preg_replace('|\0|', '', $base); $base = preg_replace('|[^\w.+!*(),:?@&=/~$-]|e', 'urlencode("$0")', $base); $base = preg_replace('|^(\w+:)?//[^/]*$|', '$0/', $base); # Add slash if absolute url without a path, e.g. http://gna.ch $queryparams = it_url::params($params); $separator = strpos($base, "?") === false ? "?" : "&"; return $base . ($queryparams ? "$separator$queryparams" : ""); } /** * Insert a javascript script * @param ... any number optional data or array of key => value arguments * @return <script type="text/javascript"...>...</script> */ function js($args) { if (($this->p['htmltype'] != 'html') && $args[0] && ((array)$args[0] === array_values((array)$args[0]))) { array_unshift($args, "<!--//--><![CDATA[//><!--\n"); $args[] = "\n//--><!]]>"; } array_unshift($args, array('type' => 'text/javascript')); return new it_tag('script', $args); } /** * Include javascript code or generate HTML to include it */ function _itjs($files, $mode) { $result = ""; if ($files) { $filenames = itjs::filenames($files); if ($mode == "files") { foreach ($filenames as $file) $result .= new it_tag('script', array('type' => "text/javascript", 'src' => "/itjs/" . basename($file) . "?v=" . itjs::checksum($file))); } else if ($mode == "inline") { $jsfile = ""; foreach ($filenames as $file) $jsfile .= @file_get_contents($file); $result .= itjs::strip($jsfile); } else $result .= new it_tag('script', array('type' => "text/javascript", 'src' => "/itjs/$files?v=" . itjs::checksum($filenames))); } return $result; } } ?>