From 2ef85ec182278544ed7a625a319b8b0ee4edbc74 Mon Sep 17 00:00:00 2001 From: Christian Schneider Date: Fri, 7 Nov 2008 15:33:07 +0000 Subject: First version of it_html based on objects --- it_html.class | 157 +++++++++++++++++++++++++++++----------------------------- it_q.class | 52 +++++++++++++++++++ it_tag.class | 113 ++++++++++++++++++++++++++++++++++++++++++ itjs.class | 3 +- itools.lib | 2 + 5 files changed, 247 insertions(+), 80 deletions(-) create mode 100644 it_q.class create mode 100644 it_tag.class diff --git a/it_html.class b/it_html.class index 75ebe9b..657a5f0 100644 --- a/it_html.class +++ b/it_html.class @@ -56,6 +56,8 @@ function it_html($p = array()) '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 @@ -72,7 +74,7 @@ function it_html($p = array()) 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 \$GLOBALS['{$this->p['name']}']->_tag('$func', \$args); }"; + $code[$func] = "function $func() { \$args = func_get_args(); return new it_tag('$func', \$args); }"; } # Create global functions for it_html methods @@ -119,13 +121,20 @@ function configure($p) */ function html($args) { + $result = new it_tag('html'); + list($data, $p) = $this->_parse_args($args); $p += $this->p; - $html = ($p['doctype'] ? $p['doctype'] : $this->doctypes[$p['htmltype']]) . "\n" . - '\n" . $data . ($p['omit_endhtml'] ? '' : "\n"); + $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 EDC('upd') ? it::replace(array('' => ''), $html, array('singleline' => true)) : $html; + return $result; } @@ -142,30 +151,40 @@ function html($args) */ 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']}")); - $header = $p['show_content_type'] ? meta(array('http-equiv' => "Content-Type", 'content' => $p['content-type'])) : ""; + 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])) - $header .= meta(array('name' => $name, 'content' => $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')) - $header .= tag('link', array('rel' => "shortcut icon", 'href' => "/favicon.ico")); + $result[] = new it_tag('link', array('rel' => "shortcut", 'href' => "/favicon.ico")); foreach((array)$p['stylesheets'] as $type => $url) - $header .= tag('link', array('rel' => "stylesheet", 'type' => "text/css", 'href' => $url) + (is_int($type) ? array() : array('media' => $type))); + $result[] = new it_tag('link', array('rel' => "stylesheet", 'type' => "text/css", 'href' => $url) + (is_int($type) ? array() : array('media' => $type))); if (!empty($p['cssinline'])) - $header .= tag('style', array('type' => "text/css", "\n" . preg_replace(array('/\s*\/\*[^\*]+\*\//Um', '/\s*\{\s*/', '/;\s+/'), array('', '{', ';'), $p['cssinline']))); + $result[] = new it_tag('style', array('type' => "text/css", "\n" . preg_replace(array('/\s*\/\*[^\*]+\*\//Um', '/\s*\{\s*/', '/;\s+/'), array('', '{', ';'), $p['cssinline']))); + - $header .= $p['head'] . ($p['title'] ? tag('title', Q($p['title'])) : ""); + 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 @@ -185,10 +204,13 @@ function head($args = array()) $js .= $this->_itjs($p['jsinline'], 'inline'); if ($js) - $header .= $this->js(array($js)); + $result[] = $this->js(array($js)); - return tag('head', $header, $data); + foreach ($data as $value) + $result[] = $value; } + + return $result; } @@ -197,13 +219,17 @@ function head($args = array()) */ function body($args) { + $result = $this->head(); + if ($this->p['use_it_state']) - array_unshift($args, tag('iframe', array('id' => "it_state", 'src' => "/itjs/state.html", 'width' => 1, 'height' => 1, 'frameborder' => 0))); + 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[] = div(array('id' => "it_boot_dom", 'style' => "visibility:hidden")); + $args[] = new it_tag('div', array('id' => "it_boot_dom", 'style' => "visibility:hidden")); + + $result[] = new it_tag('body', $args); - return $this->head() . $this->_tag('body', $args); + return $result; } @@ -214,6 +240,8 @@ function body($args) function _parse_args($args) { $p = array(); + $data = array(); + foreach ($args as $arg) { if (is_array($arg)) @@ -221,13 +249,13 @@ function _parse_args($args) foreach ($arg as $key => $value) { if (is_int($key)) - $data .= it_taintcheck($value); + $data[] = $value; else $p[$key] = $value; } } else - $data .= it_taintcheck($arg); + $data[] = $arg; } return array($data, $p); @@ -245,46 +273,6 @@ function _parse_args($args) * @see _tag() */ #:} -/** - * INTERNAL: Create html tag from name and args array (the arguments of the parent function) - */ -function _tag($name, $args) -{ - list($data, $attr) = $this->_parse_args($args); - - $newline = isset($this->hasnonewline[$name]) ? "" : "\n"; - - # Ultra XML PrettyPrinter 3000 [\] by SCA - if ($this->p['prettyprint'] && $newline && (substr($data, -1, 1) == "\n") && (strpos($data, ' it_debug::backtrace(array('levels' => $levels, 'skipfiles' => "_html\\.class"))) + $attr; - - $result .= "<$name"; - - # add attributes. If $value === true, use key only ( instead of for old html, for xhtml style) - foreach($attr as $key => $value) - { - if (($value === null) || ($value === false)) # null or false: omit whole tag - ; - else if (isset($value) && $value !== true) # normal case: value - $result .= " $key=\"" . (preg_match("/[<>&\"'\n\x80-\x9f]/", $value) ? str_replace("\n", " ", Q($value)) : it_untaint($value)) . '"'; - else # true: tag without value - $result .= ($this->p['htmltype'] == 'html') ? " $key" : " $key=\"$key\""; - } - - # Apply a kind of magic... this needs further investigation - if (isset($data) || preg_match('/^(a|div|iframe|pre|script|span|td|textarea)$/i', $name)) - $result .= ">$data$newline"; - elseif ($this->p['htmltype'] == 'html') - $result .= ">$newline"; - else - $result .= " />$newline"; - - return $result; -} /** @@ -296,7 +284,7 @@ function _tag($name, $args) function tag($args) { $name = array_shift($args); - return $this->_tag($name, $args); + return new it_tag($name, $args); } /** @@ -309,14 +297,16 @@ 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 $this->_tag("img", $args); + return new it_tag("img", $args); } @@ -331,6 +321,8 @@ function img($args) */ function select($tags, $options, $selected = null) { + $result = array(); + # Transmogrify key:val{,key:val} to array(key => val) if (!is_array($options)) { @@ -351,16 +343,16 @@ function select($tags, $options, $selected = null) { if (is_array($option)) { - $grouphtml = ""; + $group = new it_tag('optgroup', array('label' => $value)); foreach($option as $optval => $opt) - $grouphtml .= $this->_tag("option", array(array('value' => $optval, 'selected' => in_array((string)$optval, $selected)), Q($opt))); - $html .= $this->_tag("optgroup", array(array('label' => $value, $grouphtml))); + $group[] = new it_tag("option", array(array('value' => $optval, 'selected' => in_array((string)$optval, $selected)), new it_q($opt))); + $result[] = $group; } else - $html .= $this->_tag("option", array(array('value' => $value, 'selected' => in_array((string)$value, $selected), 'disabled' => $option === ""), (trim($option) === "") ? " " : Q($option))); + $result[] = new it_tag("option", array(array('value' => $value, 'selected' => in_array((string)$value, $selected), 'disabled' => $option === ""), (trim($option) === "") ? " " : new it_q($option))); } - return $this->_tag("select", array($tags, $html)); + return new it_tag("select", array($tags, $result)); } @@ -388,13 +380,13 @@ function sanitize($html) { # Link tags, keeps only href attribute list($head, $href, $content, $tail) = $tag; - $result .= it_html::sanitize($head) . '' . it_html::sanitize($content) . "" . it_html::sanitize($tail); + $result .= it_html::sanitize($head) . '' . it_html::sanitize($content) . "" . it_html::sanitize($tail); } else if ($tag = it::match('(.*)]+?src="(' . $urlpattern . ')"[^>]*?>(.*)', $html)) { # Image tags, keeps only src attribute list($head, $src, $tail) = $tag; - $result .= it_html::sanitize($head) . '' . it_html::sanitize($tail); + $result .= it_html::sanitize($head) . '' . it_html::sanitize($tail); } else if ($tag = it::match("(.*)<(br)[^>]*>(.*)", $html)) { @@ -404,7 +396,7 @@ function sanitize($html) $result .= it_html::sanitize($head) . "<$tagname />" . it_html::sanitize($tail); } else - $result = it_html::Q(it::replace(array('&#\d+;' => ""), html_entity_decode(strip_tags($html)))); + $result = new it_q(it::replace(array('&#\d+;' => ""), html_entity_decode(strip_tags($html)))); return $result; } @@ -412,15 +404,24 @@ function sanitize($html) /** * Shortcut: return htmlspecialchars($string) and encode forbidden characters 80-9f if latin1 is output - * @param $string String to encode with htmlspecialchars() - * @return htmlspecialchars($string) + * @param $args Data to encode with htmlspecialchars() + * @return htmlspecialchars($args) */ -function q($string) +function q($args) { - if ($GLOBALS['it_html']->p['charset'] == "iso-8859-1") - $string = preg_replace('/[\x80-\x9f]/', ' ', strtr($string, array("\x80" => "EUR", "\x82" => "'", "\x84" => "\"", "\x85" => "...", "\x8a" => "S", "\x8c" => "OE", "\x8e" => "Z", "\x91" => "'", "\x92" => "'", "\x93" => "\"", "\x94" => "\"", "\x96" => "-", "\x97" => "-", "\x9a" => "s", "\x9e" => "z"))); + $args = (array)$args; + return new it_q($args[0]); +} - return htmlspecialchars($string); +/** + * 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); } @@ -433,9 +434,7 @@ function u(/* ... */) $args = func_get_args(); list($base, $params) = it_html::_parse_args($args); - if (!isset($base)) - $base = $_SERVER['PHP_SELF']; - + $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 @@ -460,7 +459,7 @@ function js($args) } array_unshift($args, array('type' => 'text/javascript')); - return $this->_tag('script', $args); + return new it_tag('script', $args); } @@ -478,7 +477,7 @@ function _itjs($files, $mode) if ($mode == "files") { foreach ($filenames as $file) - $result .= tag('script', array('type' => "text/javascript", 'src' => "/itjs/" . basename($file) . "?v=" . itjs::checksum($file))); + $result .= new it_tag('script', array('type' => "text/javascript", 'src' => "/itjs/" . basename($file) . "?v=" . itjs::checksum($file))); } else if ($mode == "inline") { @@ -490,7 +489,7 @@ function _itjs($files, $mode) $result .= itjs::strip($jsfile); } else - $result .= tag('script', array('type' => "text/javascript", 'src' => "/itjs/$files?v=" . itjs::checksum($filenames))); + $result .= new it_tag('script', array('type' => "text/javascript", 'src' => "/itjs/$files?v=" . itjs::checksum($filenames))); } return $result; diff --git a/it_q.class b/it_q.class new file mode 100644 index 0000000..3458b10 --- /dev/null +++ b/it_q.class @@ -0,0 +1,52 @@ +. +** +** UltraHTML 3000 tool layer. Texts are now objects, used by it_html +** +**/ + +class it_q +{ + var $quote; + var $value; + +function __construct($value, $quote = true) +{ + $this->quote = $quote; + $this->value = $value; +} + +/** + * Return htmlspecialchars(strval($this)) and encode forbidden characters 80-9f if latin1 is output + * @return Encoded string value of this object + */ +function __toString() +{ + $result = strval($this->value); + + if ($GLOBALS['it_html']->p['charset'] == "iso-8859-1") + $result = preg_replace('/[\x80-\x9f]/', ' ', strtr($result, array("\x80" => "EUR", "\x82" => "'", "\x84" => "\"", "\x85" => "...", "\x8a" => "S", "\x8c" => "OE", "\x8e" => "Z", "\x91" => "'", "\x92" => "'", "\x93" => "\"", "\x94" => "\"", "\x96" => "-", "\x97" => "-", "\x9a" => "s", "\x9e" => "z"))); + + return $this->quote ? htmlspecialchars($result) : $result; +} + +} + +?> diff --git a/it_tag.class b/it_tag.class new file mode 100644 index 0000000..f2b0fdc --- /dev/null +++ b/it_tag.class @@ -0,0 +1,113 @@ +. +** +** UltraHTML 3000 tool layer. Tags are now objects, used by it_html +** +**/ + +class it_tag extends ArrayObject +{ + var $tag; + var $attributes = array(); + var $prefix = ""; + var $suffix = ""; + +function __construct($tag, $values = array()) +{ + $this->tag = $tag; + $this->offsetSet(0, $values); +} + +function offsetSet($index, $value) +{ + if (is_string($index)) + { + $this->attributes[$index] = $value; + } + else if (is_array($value)) + { + foreach ($value as $key => $val) + $this->offsetSet($key, $val); + } + else + { + if (is_string($value)) + { + if ($errortype = $GLOBALS['it_html']->p['reportquote']) + trigger_error("Unquoted '$value' at " . it_debug::backtrace(), $errortype); + if ($GLOBALS['it_html']->p['autoquote']) + $value = new it_q($value); + } + + parent::offsetSet($index, $value); + } +} + +function __toString() +{ + $result = array($this->prefix, "<$this->tag"); + + foreach ($this->attributes as $key => $value) + { + if (($value === null) || ($value === false)) # null or false: omit whole tag + ; + else if (isset($value) && $value !== true) # normal case: value + $result[] = " $key=\"" . (preg_match("/[<>&\"'\n\x80-\x9f]/", $value) ? str_replace("\n", " ", new it_q($value)) : $value) . '"'; + else # true: tag without value + $result[] = ($GLOBALS['it_html']->p['htmltype'] == 'html') ? " $key" : " $key=\"$key\""; + } + + $newline = isset($GLOBALS['it_html']->hasnonewline[$this->tag]) ? "" : "\n"; + + if (count($this) || preg_match('/^(a|div|iframe|script|span|td|textarea)$/i', $this->tag)) + { + $result[] = ">"; + + $data = join("", $this->getArrayCopy()); + + # Ultra XML PrettyPrinter 3000 [\] by SCA + if ($GLOBALS['it_html']->p['prettyprint'] && $newline && (substr($data, -1, 1) == "\n") && (strpos($data, 'p['htmltype'] == 'html') + $result[] = ">$newline"; + else + $result[] = " />$newline"; + + if ($GLOBALS['debug_srclines']) + array_unshift($result, ""); + + $result[] = $this->suffix; + + return join("", $result); +} + +function clear() +{ + $this->exchangeArray(array()); +} + +} + +?> diff --git a/itjs.class b/itjs.class index 40d65b8..bafc103 100644 --- a/itjs.class +++ b/itjs.class @@ -89,7 +89,8 @@ function encode($values) $result .= 'false'; else if (!is_array($value)) { - $quote = (strval(intval($value)) === strval($value)) ? "" : '"'; + $value = strval($value); + $quote = (strval(intval($value)) === $value) ? "" : '"'; $string = strtr($value, array("\0" => '\\0', '"' => '\\"', ""<\\/", "\n" => '\\n', "\r" => '\\r', "\t" => '\\t', "\\" => '\\\\')); $string = $GLOBALS['itjs_defaultconfig']['latin2unicode'] ? preg_replace('/([\xa0-\xff])/e', 'sprintf("\\u%04x", ord("\\1"))', $string) : $string; $result .= $quote . $string . $quote; diff --git a/itools.lib b/itools.lib index 3b6a2f1..b472d38 100644 --- a/itools.lib +++ b/itools.lib @@ -23,6 +23,8 @@ require_once 'itools/it.class'; require_once 'itools/it_browser.class'; require_once 'itools/it_dbi.class'; require_once 'itools/it_debug.class'; +require_once 'itools/it_q.class'; +require_once 'itools/it_tag.class'; require_once 'itools/it_html.class'; require_once 'itools/itjs.class'; require_once 'itools/it_mail.class'; -- cgit v1.2.3