complete rewrite in 6/2006 and earlier modifications * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @author RalfBecker-AT-outdoor-training.de * @copyright 2001-2014 by RalfBecker@outdoor-training.de * @package api * @subpackage html * @version $Id$ */ /** * Generates html with methods representing html-tags or higher widgets * * The class has only static methods now, so there's no need to instanciate as object anymore! */ class html { /** * user-agent: 'firefox', 'msie', 'safari' (incl. iPhone), 'chrome', 'opera', 'konqueror', 'mozilla' * @var string */ static $user_agent; /** * User agent is mobile browser: "iphone", "ipod", "ipad", "android", "symbianos", "blackberry", "kindle", "opera mobi", "windows phone" * @var string with name of mobile browser or null, if not mobile browser */ static $ua_mobile; /** * version of user-agent as specified by browser * @var string */ static $ua_version; /** * what attribute to use for the title of an image: 'title' for everything but netscape4='alt' * @var string */ static $netscape4; static private $prefered_img_title; /** * charset used by the page, as returned by $GLOBALS['egw']->translation->charset() * @var string */ static $charset; /** * URL (NOT path) of the js directory in the api * @var string */ static $api_js_url; /** * Automatically turn on enhanced selectboxes if there's more than this many options */ const SELECT_ENHANCED_ROW_COUNT = 12; /** * initialise our static vars */ static function _init_static() { // should be Ok for all HTML 4 compatible browsers $parts = null; if(!preg_match('/compatible; ([a-z]+)[\/ ]+([0-9.]+)/i',$_SERVER['HTTP_USER_AGENT'],$parts)) { preg_match_all('/([a-z]+)\/([0-9.]+)/i',$_SERVER['HTTP_USER_AGENT'],$parts,PREG_SET_ORDER); $parts = $parts[2][1] == 'Chrome' ? $parts[2] : array_pop($parts); } list(,self::$user_agent,self::$ua_version) = $parts; if ((self::$user_agent = strtolower(self::$user_agent)) == 'version') self::$user_agent = 'opera'; // IE no longer reports MSIE, but "Trident/7.0; rv:11.0" if (self::$user_agent=='trident') { self::$user_agent='msie'; $matches = null; self::$ua_version = preg_match('|Trident/[0-9.]+; rv:([0-9.]+)|i', $_SERVER['HTTP_USER_AGENT'], $matches) ? $matches[1] : 11.0; } self::$ua_mobile = preg_match('/(iPhone|iPod|iPad|Android|SymbianOS|Blackberry|Kindle|Opera Mobi|Windows Phone)/i', $_SERVER['HTTP_USER_AGENT'], $matches) ? strtolower($matches[1]) : null; self::$netscape4 = self::$user_agent == 'mozilla' && self::$ua_version < 5; self::$prefered_img_title = self::$netscape4 ? 'alt' : 'title'; //error_log("HTTP_USER_AGENT='$_SERVER[HTTP_USER_AGENT]', UserAgent: '".self::$user_agent."', Version: '".self::$ua_version."', isMobile=".array2string(self::$ua_mobile).", img_title: '".self::$prefered_img_title."'"); if ($GLOBALS['egw']->translation) { self::$charset = $GLOBALS['egw']->translation->charset(); } self::$api_js_url = $GLOBALS['egw_info']['server']['webserver_url'].'/phpgwapi/js'; } /** * Created an input-field with an attached color-picker * * Please note: it need to be called before the call to egw_header() !!! * * @param string $name the name of the input-field * @param string $value ='' the actual value for the input-field, default '' * @param string $title ='' tooltip/title for the picker-activation-icon * @param string $options ='' options for input * @return string the html */ static function inputColor($name,$value='',$title='',$options='') { $options .= ' id="'.htmlspecialchars($id=str_replace(array('[',']'),array('_',''),$name).'_colorpicker').'"'; $onclick = "javascript:egw_openWindowCentered2('".self::$api_js_url.'/colorpicker/select_color.html?id='.urlencode($id)."&color='+encodeURIComponent(document.getElementById('$id').value),'colorPicker',240,187);"; if (preg_match('/^#[0-9A-F]{6}$/i',$value)) { $options .= ' style="background-color: '.$value.'"'; } $options .= ' onChange="this.style.backgroundColor=this.value.match(/^(#[0-9A-F]+|[a-z]+)$/i)?this.value:\'#FFFFFF\'+(this.value=\'\')"'; return self::input($name, $value, 'text', $options.' size="7" maxsize="7"').' '. ''. '"; } /** * Handles tooltips via the wz_tooltip class from Walter Zorn * * @param string $text text or html for the tooltip, all chars allowed, they will be quoted approperiate * @param boolean $do_lang (default False) should the text be run though lang() * @param array $options param/value pairs, eg. 'TITLE' => 'I am the title'. Some common parameters: * title (string) gives extra title-row, width (int,'auto') , padding (int), above (bool), bgcolor (color), bgimg (URL) * For a complete list and description see http://www.walterzorn.com/tooltip/tooltip_e.htm * @param boolean $return_as_attributes true to return array(onmouseover, onmouseout) attributes * @return string|array to be included in any tag, like '
Ralf').'>Text with tooltip
' */ static function tooltip($text,$do_lang=False,$options=False, $return_as_attributes=false) { // tell egw_framework to include wz_tooltip.js $GLOBALS['egw_info']['flags']['include_wz_tooltip'] = true; if ($do_lang) $text = lang($text); $ttip = 'Tip(\''.str_replace(array("\n","\r","'",'"'),array('','',"\\'",'"'),$text).'\''; $sticky = false; if (is_array($options)) { foreach($options as $option => $value) { $option = strtoupper($option); if ($option == 'STICKY') $sticky = (bool)$value; switch(gettype($value)) { case 'boolean': $value = $value ? 'true' : 'false'; break; case 'string': if (stripos($value,"'")===false) $value = "'$value'"; break; } $ttip .= ','.$option.','.$value; } } $ttip .= ')'; $untip = 'UnTip()'; return $return_as_attributes ? array($ttip, $untip) : ' onmouseover="'.self::htmlspecialchars($ttip).'" onmouseout="'.$untip.'"'; } /** * activates URLs in a text, URLs get replaced by html-links * * @param string $content text containing URLs * @return string html with activated links */ static function activate_links($content) { if (!$content || strlen($content) < 20) return $content; // performance // Exclude everything which is already a link $NotAnchor = '(?\\1 AT \\2 DOT \\3', $content); // First match things beginning with http:// (or other protocols) $Protocol = '(http:\/\/|(ftp:\/\/|https:\/\/))'; // only http:// gets removed, other protocolls are shown $Domain = '([\w-]+\.[\w-.]+)'; $Subdir = '([\w\-\.,@?^=%&;:\/~\+#]*[\w\-\@?^=%&\/~\+#])?'; $optBracket = ''; //$optBracket = '(>|>)'; $Expr = '/' . $NotAnchor . $Protocol . $Domain . $Subdir . $optBracket . '/i'; $result2 = preg_replace( $Expr, "$2$3$4", $result ); //$result = preg_replace( $Expr, "$2$3$4$5 ", $result ); // Now match things beginning with www. $NotHTTP = '(?)'; // avoid running again on http://www links already handled above $Domain2 = 'www(\.[\w-.]+)'; $Subdir2 = '([\w\-\.,@?^=%&:\/~\+#]*[\w\-\@?^=%&\/~\+#])?'; $Expr = '/' . $NotAnchor . $NotHTTP . $Domain2 . $Subdir2 . '/i'; //$optBracket = '(>|>)'; //$Expr = '/' . $NotAnchor . $NotHTTP . $Domain . $Subdir . $optBracket . '/i'; return preg_replace( $Expr, "$0", $result2 ); //return preg_replace( $Expr, "www$1$2$3 ", $result ); } /** * escapes chars with special meaning in html as entities * * Allows to use and char in the html-output and prevents XSS attacks. * Some entities are allowed and get NOT escaped: -> prevented by 4th param = doubleencode=false * - some translations (AFAIK: the arabic ones) need this; * - < > for convenience -> should not happen anymore, as we do not doubleencode anymore (20101020) * * @param string $str string to escape * @param boolean $double_encoding =false do we want double encoding or not, default no * @return string */ static function htmlspecialchars($str, $double_encoding=false) { //if (!is_scalar($str) && !is_null($str)) error_log(__METHOD__.'('.array2string($str).') '.function_backtrace()); // as EGroupware supports only utf-8 we should not need to worry about wrong charsets //if (is_array($str)) error_log(__METHOD__.__LINE__.' string expected -> array given:'.array2string($str).'->'.function_backtrace()); return htmlspecialchars($str,ENT_COMPAT,self::$charset,$double_encoding); // we need '' unchanged, so we translate it back -> this is provided by 4th param = false -> do not doubleencode //$str = str_replace(array('&#',' ','<','>'),array('',' ','<','>'),$str); //return $str; } /** * allows to show and select one item from an array * * @param string $name string with name of the submitted var which holds the key of the selected item form array * @param string|array $key key(s) of already selected item(s) from $arr, eg. '1' or '1,2' or array with keys * @param array $arr array with items to select, eg. $arr = array ( 'y' => 'yes','n' => 'no','m' => 'maybe'); * @param boolean $no_lang NOT run the labels of the options through lang(), default false=use lang() * @param string $options additional options (e.g. 'width') * @param int $multiple number of lines for a multiselect, default 0 = no multiselect, < 0 sets size without multiple * @param boolean $enhanced Use enhanced selectbox with search. Null for default yes if more than 12 options. * @return string to set for a template or to echo into html page */ static function select($name, $key, $arr=0,$no_lang=false,$options='',$multiple=0,$enhanced=null) { if(is_null($enhanced)) $enhanced = false; //disabled by default (count($arr) > self::SELECT_ENHANCED_ROW_COUNT); if (!is_array($arr)) { $arr = array('no','yes'); } if ((int)$multiple > 0) { $options .= ' multiple="1" size="'.(int)$multiple.'"'; if (substr($name,-2) != '[]') { $name .= '[]'; } } elseif($multiple < 0) { $options .= ' size="'.abs($multiple).'"'; } // fix width for MSIE < 9 in/for selectboxes if (self::$user_agent == 'msie' && self::$ua_version < 9) { if (stripos($options,'onfocus="') === false) { $options .= ' onfocus="window.dropdown_menu_hack(this);" '; } else { $options = str_ireplace('onfocus="','onfocus="window.dropdown_menu_hack(this);',$options); } } $out = "\n"; if($enhanced) { egw_framework::validate_file('/phpgwapi/js/jquery/chosen/chosen.jquery.js'); egw_framework::includeCSS('/phpgwapi/js/jquery/chosen/chosen.css',null,false); $out .= "\n"; } return $out; } /** * emulating a multiselectbox using checkboxes * * Unfortunaly this is not in all aspects like a multi-selectbox, eg. you cant select options via javascript * in the same way. Therefor I made it an extra function. * * @param string $name string with name of the submitted var which holds the key of the selected item form array * @param string|array $key key(s) of already selected item(s) from $arr, eg. '1' or '1,2' or array with keys * @param array $arr array with items to select, eg. $arr = array ( 'y' => 'yes','n' => 'no','m' => 'maybe'); * @param boolean $no_lang NOT run the labels of the options through lang(), default false=use lang() * @param string $options additional options (e.g. 'width') * @param int $multiple number of lines for a multiselect, default 3 * @param boolean $selected_first show the selected items before the not selected ones, default true * @param string $style ='' extra style settings like "width: 100%", default '' none * @return string to set for a template or to echo into html page */ static function checkbox_multiselect($name, $key, $arr=0,$no_lang=false,$options='',$multiple=3,$selected_first=true,$style='',$enhanced = null) { //echo "checkbox_multiselect('$name',".array2string($key).",".array2string($arr).",$no_lang,'$options',$multiple,$selected_first,'$style')
\n"; if(is_null($enhanced)) $enhanced = (count($arr) > self::SELECT_ENHANCED_ROW_COUNT); if (!is_array($arr)) { $arr = array('no','yes'); } if ((int)$multiple <= 0) $multiple = 1; if (substr($name,-2) != '[]') { $name .= '[]'; } $base_name = substr($name,0,-2); if($enhanced) return self::select($name, $key, $arr,$no_lang,$options." style=\"$style\" ",$multiple,$enhanced); if (!is_array($key)) { // explode on ',' only if multiple values expected and the key contains just numbers and commas $key = preg_match('/^[,0-9]+$/',$key) ? explode(',',$key) : array($key); } $html = ''; $options_no_id = preg_replace('/id="[^"]+"/i','',$options); if ($selected_first) { $selected = $not_selected = array(); foreach($arr as $val => $label) { if (in_array((string)$val,$key)) { $selected[$val] = $label; } else { $not_selected[$val] = $label; } } $arr = $selected + $not_selected; } $max_len = 0; foreach($arr as $val => $label) { if (is_array($label)) { $title = $label['title']; $label = $label['label']; } else { $title = ''; } if ($label && !$no_lang) $label = lang($label); if ($title && !$no_lang) $title = lang($title); if (strlen($label) > $max_len) $max_len = strlen($label); $html .= self::label(self::checkbox($name,in_array((string)$val,$key),$val,$options_no_id. ' id="'.$base_name.'['.$val.']'.'"').self::htmlspecialchars($label), $base_name.'['.$val.']','',($title ? 'title="'.self::htmlspecialchars($title).'" ':''))."html::link(url='$url',vars='"; print_r($vars); echo "')
\n"; if (!is_array($vars)) { parse_str($vars,$vars); } list($url,$v) = explode('?', $_url); // url may contain additional vars if ($v) { parse_str($v,$v); $vars += $v; } return egw::link($url,$vars); } /** * represents html checkbox * * @param string $name name * @param boolean $checked box checked on display * @param string $value value the var should be set to, default 'True' * @param string $options attributes for the tag, default ''=none * @return string html */ static function checkbox($name,$checked=false,$value='True',$options='') { return '\n"; } /** * represents a html form * * @param string $content of the form, if '' only the opening tag gets returned * @param array $hidden_vars array with name-value pairs for hidden input fields * @param string $_url eGW relative URL, will be run through the link function, if empty the current url is used * @param string|array $url_vars parameters for the URL, send to link static function too * @param string $name name of the form, defaul ''=none * @param string $options attributes for the tag, default ''=none * @param string $method method of the form, default 'POST' * @return string html */ static function form($content,$hidden_vars,$_url,$url_vars='',$name='',$options='',$method='POST') { $url = $_url ? self::link($_url, $url_vars) : $_SERVER['PHP_SELF'].'?'.$_SERVER['QUERY_STRING']; $html = "\n"; } return $html; } /** * represents a html form with one button * * @param string $name name of the button * @param string $label label of the button * @param array $hidden_vars array with name-value pairs for hidden input fields * @param string $url eGW relative URL, will be run through the link function * @param string|array $url_vars parameters for the URL, send to link static function too * @param string $options attributes for the tag, default ''=none * @param string $form_name name of the form, defaul ''=none * @param string $method method of the form, default 'POST' * @return string html */ static function form_1button($name,$label,$hidden_vars,$url,$url_vars='',$form_name='',$method='POST') { return self::form(self::submit_button($name,$label),$hidden_vars,$url,$url_vars,$form_name,' style="display: inline-block"',$method); } const THEAD = 1; const TFOOT = 2; const TBODY = 3; static $part2tag = array( self::THEAD => 'thead', self::TFOOT => 'tfoot', self::TBODY => 'tbody', ); /** * creates table from array of rows * * abstracts the html stuff for the table creation * Example: $rows = array ( * 'h1' => array( // optional header row(s) * ), * 'f1' => array( // optional footer row(s) * ), * '1' => array( * 1 => 'cell1', '.1' => 'colspan=3', * 2 => 'cell2', * 3 => 'cell3', '.3' => 'width="10%"' * ),'.1' => 'BGCOLOR="#0000FF"' ); * table($rows,'width="100%"') = 'cell1 | cell2 | cell3 |