1<?php 2/** 3 * EGroupware API: generates html with methods representing html-tags or higher widgets 4 * 5 * @link http://www.egroupware.org 6 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de> complete rewrite in 6/2006 and earlier modifications 7 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License 8 * @author RalfBecker-AT-outdoor-training.de 9 * @copyright 2001-2016 by RalfBecker@outdoor-training.de 10 * @package api 11 * @subpackage html 12 * @version $Id$ 13 */ 14 15namespace EGroupware\Api; 16use EGroupware\Api\Header\ContentSecurityPolicy; 17/** 18 * Generates html with methods representing html-tags or higher widgets 19 * 20 * The class has only static methods now, so there's no need to instanciate as object anymore! 21 */ 22class Html 23{ 24 /** 25 * Automatically turn on enhanced selectboxes if there's more than this many options 26 */ 27 const SELECT_ENHANCED_ROW_COUNT = 12; 28 29 /** 30 * activates URLs in a text, URLs get replaced by html-links 31 * 32 * @param string $content text containing URLs 33 * @return string html with activated links 34 */ 35 static function activate_links($content) 36 { 37 if (!$content || strlen($content) < 20) return $content; // performance 38 39 // Exclude everything which is already a link 40 $NotAnchor = '(?<!"|href=|href\s=\s|href=\s|href\s=)'; 41 42 // spamsaver emailaddress 43 $result = preg_replace('/'.$NotAnchor.'mailto:([a-z0-9._-]+)@([a-z0-9_-]+)\.([a-z0-9._-]+)/i', 44 "<a href=\"mailto:$1@$2.$3\" target=\"_blank\">$1 AT $2 DOT $3</a>", 45 $content); 46 47 // First match things beginning with http:// (or other protocols) 48 $optBracket0 = '(<|<)'; 49 $Protocol = '(http:\/\/|(ftp:\/\/|https:\/\/))'; // only http:// gets removed, other protocolls are shown 50 $Domain = '([\w-]+\.[\w\-.]+)'; 51 $Subdir = '([\w\-\.,@?^=%&;:\/~\+#]*[\w\-\@?^=%&\/~\+#])?'; 52 $optBracket = '(>|>)'; 53 $Expr = '/' .$optBracket0. $NotAnchor . $Protocol . $Domain . $Subdir . $optBracket . '/i'; 54 // use preg_replace_callback as we experienced problems with https links 55 $result2 = preg_replace_callback($Expr, function ($match) 56 { 57 return $match[1]."<a href=\"".($match[2]&&!$match[3]?$match[2]:'').($match[3]?$match[3]:'').$match[4].$match[5]."\" target=\"_blank\">".$match[4].$match[5]."</a>".$match[6]; 58 }, $result); 59 60 if (true) // hack to keep IDE from complaing about double assignments 61 { 62 // First match things beginning with http:// (or other protocols) 63 $Protocol = '(http:\/\/|(ftp:\/\/|https:\/\/))'; // only http:// gets removed, other protocolls are shown 64 $Domain = '([\w-]+\.[\w\-.]+)'; 65 $Subdir = '([\w\-\.,@?^=%&;:\/~\+#]*[\w\-\@?^=%&\/~\+#])?'; 66 $optStuff = '("|"|;)?'; 67 $Expr = '/' . $NotAnchor . $Protocol . $Domain . $Subdir . $optStuff . '/i'; 68 // use preg_replace_callback as we experienced problems with https links 69 $result3 = preg_replace_callback($Expr, function ($match) 70 { 71 $additionalQuote="";//at the end, ... 72 // only one " at the end is found. chance is, it is not belonging to the URL 73 if ($match[5]==';' && (strlen($match[4])-6) >=0 && strpos($match[4],'"',strlen($match[4])-6)!==false && strpos(substr($match[4],0,strlen($match[4])-6),'"')===false) 74 { 75 $match[4] = substr($match[4],0,strpos($match[4],'"',strlen($match[4])-6)); 76 $additionalQuote = """; 77 } 78 // if there is quoted stuff within the URL then we have at least one more " in match[4], so chance is the last " is matched by the one within 79 if ($match[5]==';' && (strlen($match[4])-6) >=0 && strpos($match[4],'"',strlen($match[4])-6)!==false && strpos(substr($match[4],0,strlen($match[4])-6),'"')!==false) 80 { 81 $match[4] .= $match[5]; 82 } 83 if ($match[5]==';'&&$match[4]==""") 84 { 85 $match[4] =''; 86 $additionalQuote = """; 87 } 88 //error_log(__METHOD__.__LINE__.array2string($match)); 89 return "<a href=\"".($match[1]&&!$match[2]?$match[1]:'').($match[2]?$match[2]:'').$match[3].$match[4]."\" target=\"_blank\">".$match[3].$match[4]."</a>$additionalQuote"; 90 }, $result2); 91 92 // Now match things beginning with www. 93 $optBracket0 = '(<|<)?'; 94 $NotHTTP = '(?<!:\/\/|" target=\"_blank\">)'; // avoid running again on http://www links already handled above 95 $Domain2 = 'www(\.[\w\-.]+)'; 96 $Subdir2 = '([\w\-\.,@?^=%&:\/~\+#]*[\w\-\@?^=%&\/~\+#])?'; 97 $optBracket = '(>|>|>|;)?'; 98 $Expr = '/' .$optBracket0. $NotAnchor . $NotHTTP . $Domain2 . $Subdir2 .$optBracket. '/i'; 99 //$Expr = '/' . $NotAnchor . $NotHTTP . $Domain . $Subdir . $optBracket . '/i'; 100 // use preg_replace_callback as we experienced problems with links such as <www.example.tld/pfad/zu/einer/pdf-Datei.pdf> 101 $result4 = preg_replace_callback( $Expr, function ($match) { 102 //error_log(__METHOD__.__LINE__.array2string($match)); 103 if ($match[4]==';' && (strlen($match[3])-4) >=0 && strpos($match[3],'>',strlen($match[3])-4)!==false) 104 { 105 $match[3] = substr($match[3],0,strpos($match[3],'>',strlen($match[3])-4)); 106 $match[4] = ">"; 107 } 108 if ($match[4]==';'&&$match[3]==">") 109 { 110 $match[3] =''; 111 $match[4] = ">"; 112 } 113 //error_log(__METHOD__.__LINE__.array2string($match)); 114 return $match[1]."<a href=\"http://www".$match[2].$match[3]."\" target=\"_blank\">"."www".$match[2].$match[3]."</a>".$match[4]; 115 }, $result3 ); 116 } 117 return $result4; 118 } 119 120 /** 121 * escapes chars with special meaning in html as entities 122 * 123 * Allows to use and char in the html-output and prevents XSS attacks. 124 * Some entities are allowed and get NOT escaped: -> prevented by 4th param = doubleencode=false 125 * - &# some translations (AFAIK: the arabic ones) need this; 126 * - < > for convenience -> should not happen anymore, as we do not doubleencode anymore (20101020) 127 * 128 * @param string $str string to escape 129 * @param boolean $double_encoding =false do we want double encoding or not, default no 130 * @return string 131 */ 132 static function htmlspecialchars($str, $double_encoding=false) 133 { 134 return htmlspecialchars($str,ENT_COMPAT,Translation::charset(),$double_encoding); 135 } 136 137 /** 138 * allows to show and select one item from an array 139 * 140 * @param string $name string with name of the submitted var which holds the key of the selected item form array 141 * @param string|array $key key(s) of already selected item(s) from $arr, eg. '1' or '1,2' or array with keys 142 * @param array $arr array with items to select, eg. $arr = array ( 'y' => 'yes','n' => 'no','m' => 'maybe'); 143 * @param boolean $no_lang NOT run the labels of the options through lang(), default false=use lang() 144 * @param string $options additional options (e.g. 'width') 145 * @param int $multiple number of lines for a multiselect, default 0 = no multiselect, < 0 sets size without multiple 146 * @param boolean $enhanced Use enhanced selectbox with search. Null for default yes if more than 12 options. 147 * @return string to set for a template or to echo into html page 148 */ 149 static function select($name, $key, $arr=0,$no_lang=false,$options='',$multiple=0,$enhanced=null) 150 { 151 if(is_null($enhanced)) $enhanced = false; //disabled by default (count($arr) > self::SELECT_ENHANCED_ROW_COUNT); 152 153 if (!is_array($arr)) 154 { 155 $arr = array('no','yes'); 156 } 157 if ((int)$multiple > 0) 158 { 159 $options .= ' multiple="1" size="'.(int)$multiple.'"'; 160 if (substr($name,-2) != '[]') 161 { 162 $name .= '[]'; 163 } 164 } 165 elseif($multiple < 0) 166 { 167 $options .= ' size="'.abs($multiple).'"'; 168 } 169 // fix width for MSIE < 9 in/for selectboxes 170 if (Header\UserAgent::type() == 'msie' && Header\UserAgent::version() < 9) 171 { 172 if (stripos($options,'onfocus="') === false) 173 { 174 $options .= ' onfocus="window.dropdown_menu_hack(this);" '; 175 } 176 else 177 { 178 $options = str_ireplace('onfocus="','onfocus="window.dropdown_menu_hack(this);',$options); 179 } 180 } 181 $out = "<select name=\"$name\" $options>\n"; 182 183 if (!is_array($key)) 184 { 185 // explode on ',' only if multiple values expected and the key contains just numbers and commas 186 $key = $multiple > 0 && preg_match('/^[,0-9]+$/',$key) ? explode(',',$key) : array($key); 187 } 188 foreach($arr as $k => $data) 189 { 190 if (!is_array($data) || count($data) == 2 && isset($data['label']) && isset($data['title'])) 191 { 192 $out .= self::select_option($k,is_array($data)?$data['label']:$data,$key,$no_lang, 193 is_array($data)?$data['title']:''); 194 } 195 else 196 { 197 if (isset($data['lable'])) 198 { 199 $k = $data['lable']; 200 unset($data['lable']); 201 } 202 $out .= '<optgroup label="'.self::htmlspecialchars($no_lang || $k == '' ? $k : lang($k))."\">\n"; 203 204 foreach($data as $k => $label) 205 { 206 $out .= self::select_option($k,is_array($label)?$label['label']:$label,$key,$no_lang, 207 is_array($label)?$label['title']:''); 208 } 209 $out .= "</optgroup>\n"; 210 } 211 } 212 $out .= "</select>\n"; 213 214 if($enhanced) { 215 Framework::includeJS('/api/js/jquery/chosen/chosen.jquery.js'); 216 Framework::includeCSS('/api/js/jquery/chosen/chosen.css',null,false); 217 $out .= "<script>var lab = egw_LAB || \$LAB; lab.wait(function() {jQuery(function() {if(jQuery().chosen) jQuery('select[name=\"$name\"]').chosen({width: '100%'});});})</script>\n"; 218 } 219 return $out; 220 } 221 222 /** 223 * emulating a multiselectbox using checkboxes 224 * 225 * Unfortunaly this is not in all aspects like a multi-selectbox, eg. you cant select options via javascript 226 * in the same way. Therefor I made it an extra function. 227 * 228 * @param string $name string with name of the submitted var which holds the key of the selected item form array 229 * @param string|array $key key(s) of already selected item(s) from $arr, eg. '1' or '1,2' or array with keys 230 * @param array $arr array with items to select, eg. $arr = array ( 'y' => 'yes','n' => 'no','m' => 'maybe'); 231 * @param boolean $no_lang NOT run the labels of the options through lang(), default false=use lang() 232 * @param string $options additional options (e.g. 'width') 233 * @param int $multiple number of lines for a multiselect, default 3 234 * @param boolean $selected_first show the selected items before the not selected ones, default true 235 * @param string $style ='' extra style settings like "width: 100%", default '' none 236 * @return string to set for a template or to echo into html page 237 */ 238 static function checkbox_multiselect($name, $key, $arr=0,$no_lang=false,$options='',$multiple=3,$selected_first=true,$style='',$enhanced = null) 239 { 240 //echo "<p align=right>checkbox_multiselect('$name',".array2string($key).",".array2string($arr).",$no_lang,'$options',$multiple,$selected_first,'$style')</p>\n"; 241 if(is_null($enhanced)) $enhanced = (count($arr) > self::SELECT_ENHANCED_ROW_COUNT); 242 243 if (!is_array($arr)) 244 { 245 $arr = array('no','yes'); 246 } 247 if ((int)$multiple <= 0) $multiple = 1; 248 249 if (substr($name,-2) != '[]') 250 { 251 $name .= '[]'; 252 } 253 $base_name = substr($name,0,-2); 254 255 if($enhanced) return self::select($name, $key, $arr,$no_lang,$options." style=\"$style\" ",$multiple,$enhanced); 256 257 if (!is_array($key)) 258 { 259 // explode on ',' only if multiple values expected and the key contains just numbers and commas 260 $key = preg_match('/^[,0-9]+$/',$key) ? explode(',',$key) : array($key); 261 } 262 $html = ''; 263 $options_no_id = preg_replace('/id="[^"]+"/i','',$options); 264 265 if ($selected_first) 266 { 267 $selected = $not_selected = array(); 268 foreach($arr as $val => $label) 269 { 270 if (in_array((string)$val,$key)) 271 { 272 $selected[$val] = $label; 273 } 274 else 275 { 276 $not_selected[$val] = $label; 277 } 278 } 279 $arr = $selected + $not_selected; 280 } 281 $max_len = 0; 282 foreach($arr as $val => $label) 283 { 284 if (is_array($label)) 285 { 286 $title = $label['title']; 287 $label = $label['label']; 288 } 289 else 290 { 291 $title = ''; 292 } 293 if ($label && !$no_lang) $label = lang($label); 294 if ($title && !$no_lang) $title = lang($title); 295 296 if (strlen($label) > $max_len) $max_len = strlen($label); 297 298 $html .= self::label(self::checkbox($name,in_array((string)$val,$key),$val,$options_no_id. 299 ' id="'.$base_name.'['.$val.']'.'"').self::htmlspecialchars($label), 300 $base_name.'['.$val.']','',($title ? 'title="'.self::htmlspecialchars($title).'" ':''))."<br />\n"; 301 } 302 if ($style && substr($style,-1) != ';') $style .= '; '; 303 if (strpos($style,'height')===false) $style .= 'height: '.(1.7*$multiple).'em; '; 304 if (strpos($style,'width')===false) $style .= 'width: '.(4+$max_len*($max_len < 15 ? 0.65 : 0.6)).'em; '; 305 $style .= 'background-color: white; overflow: auto; border: lightgray 2px inset; text-align: left;'; 306 307 return self::div($html,$options,'',$style); 308 } 309 310 /** 311 * generates an option-tag for a selectbox 312 * 313 * @param string $value value 314 * @param string $label label 315 * @param mixed $selected value or array of values of options to mark as selected 316 * @param boolean $no_lang NOT running the label through lang(), default false=use lang() 317 * @param string $extra extra text, e.g.: style="", default: '' 318 * @return string html 319 */ 320 static function select_option($value,$label,$selected,$no_lang=0,$title='',$extra='') 321 { 322 // the following compares strict as strings, to archive: '0' == 0 != '' 323 // the first non-strict search via array_search, is for performance reasons, to not always search the whole array with php 324 if (($found = ($key = array_search($value,$selected)) !== false) && (string) $value !== (string) $selected[$key]) 325 { 326 $found = false; 327 foreach($selected as $sel) 328 { 329 if (($found = (((string) $value) === ((string) $selected[$key])))) break; 330 } 331 unset($sel); 332 } 333 return '<option value="'.self::htmlspecialchars($value).'"'.($found ? ' selected="selected"' : '') . 334 ($title ? ' title="'.self::htmlspecialchars($no_lang ? $title : lang($title)).'"' : '') . 335 ($extra ? ' ' . $extra : '') . '>'. 336 self::htmlspecialchars($no_lang || $label == '' ? $label : lang($label)) . "</option>\n"; 337 } 338 339 /** 340 * generates a div-tag 341 * 342 * @param string $content of a div, or '' to generate only the opening tag 343 * @param string $options to include in the tag, default ''=none 344 * @param string $class css-class attribute, default ''=none 345 * @param string $style css-styles attribute, default ''=none 346 * @return string html 347 */ 348 static function div($content,$options='',$class='',$style='') 349 { 350 if ($class) $options .= ' class="'.$class.'"'; 351 if ($style) $options .= ' style="'.$style.'"'; 352 353 return "<div $options>\n".($content ? "$content</div>\n" : ''); 354 } 355 356 /** 357 * generate one or more hidden input tag(s) 358 * 359 * @param array|string $vars var-name or array with name / value pairs 360 * @param string $value value if $vars is no array, default '' 361 * @param boolean $ignore_empty if true all empty, zero (!) or unset values, plus filer=none 362 * @param string html 363 */ 364 static function input_hidden($vars,$value='',$ignore_empty=True) 365 { 366 if (!is_array($vars)) 367 { 368 $vars = array( $vars => $value ); 369 } 370 foreach($vars as $name => $value) 371 { 372 if (is_array($value)) 373 { 374 $value = json_encode($value); 375 } 376 if (!$ignore_empty || $value && !($name == 'filter' && $value == 'none')) // dont need to send all the empty vars 377 { 378 $html .= "<input type=\"hidden\" name=\"$name\" value=\"".self::htmlspecialchars($value)."\" />\n"; 379 } 380 } 381 return $html; 382 } 383 384 /** 385 * generate a textarea tag 386 * 387 * @param string $name name attr. of the tag 388 * @param string $value default 389 * @param boolean $ignore_empty if true all empty, zero (!) or unset values, plus filer=none 390 * @param boolean $double_encoding =false do we want double encoding or not, default no 391 * @param string html 392 */ 393 static function textarea($name,$value='',$options='',$double_encoding=false) 394 { 395 return "<textarea name=\"$name\" $options>".self::htmlspecialchars($value,$double_encoding)."</textarea>\n"; 396 } 397 398 /** 399 * Checks if HTMLarea (or an other richtext editor) is availible for the used browser 400 * 401 * @return boolean 402 */ 403 static function htmlarea_availible() 404 { 405 // this one is for testing how it will turn out, if you do not have the device or agent ready at your fingertips 406 // if (stripos($_SERVER[HTTP_USER_AGENT],'mozilla') !== false) return false; 407 return true; 408 } 409 410 /** 411 * compability static function for former used htmlarea. Please use static function fckeditor now! 412 * 413 * creates a textarea inputfield for the htmlarea js-widget (returns the necessary html and js) 414 */ 415 static function htmlarea($name,$content='',$style='',$base_href=''/*,$plugins='',$custom_toolbar='',$set_width_height_in_config=false*/) 416 { 417 /*if (!self::htmlarea_availible()) 418 { 419 return self::textarea($name,$content,'style="'.$style.'"'); 420 }*/ 421 return self::fckEditor($name, $content, $style, array('toolbar_expanded' =>'true'), '400px', '100%', $base_href); 422 } 423 424 /** 425 * this static function is a wrapper for fckEditor to create some reuseable layouts 426 * 427 * @param string $_name name and id of the input-field 428 * @param string $_content of the tinymce (will be run through htmlspecialchars !!!), default '' 429 * @param string $_mode display mode of the tinymce editor can be: simple, extended or advanced 430 * @param array $_options (toolbar_expanded true/false) 431 * @param string $_height ='400px' 432 * @param string $_width ='100%' 433 * @param string $_start_path ='' if passed activates the browser for image at absolute path passed 434 * @param boolean $_purify =true run $_content through htmlpurifier before handing it to fckEditor 435 * @param mixed (boolean/string) $_focusToBody=false USED only for CKEDIOR true means yes, focus on top, you may specify TOP or BOTTOM (to focus on the end of the editor area) 436 * @param string $_executeJSAfterInit ='' Javascript to be executed after InstanceReady of CKEditor 437 * @return string the necessary html for the textarea 438 */ 439 static function fckEditor($_name, $_content, $_mode, $_options=array('toolbar_expanded' =>'true'), 440 $_height='400px', $_width='100%',$_start_path='',$_purify=true, $_focusToBody=false, $_executeJSAfterInit='') 441 { 442 //not used anymore but defined in function signature 443 unset ($_options); 444 445 if (!self::htmlarea_availible() || $_mode == 'ascii') 446 { 447 return self::textarea($_name,$_content,'style="width: '.$_width.'; height: '.$_height.';" id="'.htmlspecialchars($_name).'"'); 448 } 449 450 //include the ckeditor js file 451 Framework::includeJS('/vendor/tinymce/tinymce/tinymce.min.js'); 452 453 // run content through htmlpurifier 454 if ($_purify && !empty($_content)) 455 $_content = self::purify($_content); 456 457 // User preferences 458 $font = $GLOBALS['egw_info']['user']['preferences']['common']['rte_font']; 459 $font_size = $GLOBALS['egw_info']['user']['preferences']['common']['rte_font_size']; 460 $font_size_unit = $GLOBALS['egw_info']['user']['preferences']['common']['rte_font_unit']; 461 $rte_menubar = $GLOBALS['egw_info']['user']['preferences']['common']['rte_menubar']; 462 $focusToBody = $_focusToBody ? "tinymce" : false; 463 ContentSecurityPolicy::add('script-src', 'unsafe-inline'); 464 // we need to enable double encoding here, as ckEditor has to undo one level of encoding 465 // otherwise < and > chars eg. from html markup entered in regular (not source) input, will turn into html! 466 //error_log(__METHOD__.__LINE__.' '.Header\UserAgent::type().','.Header\UserAgent::version()); 467 return self::textarea($_name,$_content,'id="'.htmlspecialchars($_name).'"',true). // true = double encoding 468' 469<script type="text/javascript"> 470 471egw_LAB.wait(function() { 472 473var imageUpload = egw.ajaxUrl("EGroupware\\Api\\Etemplate\\Widget\\Vfs::ajax_htmlarea_upload")+"&type=htmlarea"; 474imageUpload = imageUpload.substr(egw.webserverUrl.length+1); 475var font_size_formats = { 476 pt: "8pt 10pt 12pt 14pt 18pt 24pt 36pt 48pt 72pt", 477 px:"8px 10px 12px 14px 18px 24px 36px 48px 72px" 478}; 479var language_code = { 480 bg: "bg_BG", ca: "ca", cs: "cs", da: "da", de: "de", en:"en_CA", 481 el:"el", "es-es":"es", et: "et", eu: "eu" , fa: "fa_IR", fi: "fi", 482 fr: "fr_FR", hi:"", hr:"hr", hu:"hu_HU", id: "id", it: "it", iw: "", 483 ja: "ja", ko: "ko_KR", lo: "", lt: "lt", lv: "lv", nl: "nl", no: "nb_NO", 484 pl: "pl", pt: "pt_PT", "pt-br": "pt_BR", ru: "ru", sk: "sk", sl: "sl_SI", 485 sv: "sv_SE", th: "th_TH", tr: "tr_TR", uk: "en_GB", vi: "vi_VN", zh: "zh_CN", 486 "zh-tw": "zh_TW" 487}; 488var name = "#"+"'.$_name.'".replace( /(:|\.|\[|\]|,|=|@)/g, "\\\$1" ); 489var height = "'.$_height.'"; 490var width = "'.$_width.'"; 491var value = jQuery(name).val(); 492/** 493 * language code represention for TinyMCE lang code 494 */ 495var language_code = { 496 bg: "bg_BG", ca: "ca", cs: "cs", da: "da", de: "de", en:"en_CA", 497 el:"el", "es-es":"es", et: "et", eu: "eu" , fa: "fa_IR", fi: "fi", 498 fr: "fr_FR", hi:"", hr:"hr", hu:"hu_HU", id: "id", it: "it", iw: "", 499 ja: "ja", ko: "ko_KR", lo: "", lt: "lt", lv: "lv", nl: "nl", no: "nb_NO", 500 pl: "pl", pt: "pt_PT", "pt-br": "pt_BR", ru: "ru", sk: "sk", sl: "sl_SI", 501 sv: "sv_SE", th: "th_TH", tr: "tr_TR", uk: "en_GB", vi: "vi_VN", zh: "zh_CN", 502 "zh-tw": "zh_TW" 503}; 504tinymce.init({ 505 selector: name, 506 menubar: parseInt('. $rte_menubar.')? true : false, 507 branding: false, 508 resize: false, 509 height: height.match(/%/) ? height : parseInt(height), 510 width: width.match(/%/) ? width : parseInt(width), 511 min_height: 200, 512 auto_focus: "'.$focusToBody.'", 513 language: language_code["'. $GLOBALS['egw_info']['user']['preferences']['common']['lang'].'"], 514 language_url: egw.webserverUrl+"/api/js/tinymce/langs/"+language_code[egw.preference("lang", "common")]+".js", 515 browser_spellcheck: true, 516 contextmenu: false, 517 file_picker_callback: function(_callback, _value, _meta){ 518 var callback = _callback; 519 var attrs = { 520 menuaction: "filemanager.filemanager_select.select", 521 mode: "open", 522 method: "download_url", 523 path: "'. $_start_path.'" 524 }; 525 526 // Open the filemanager select in a popup 527 var popup = egw(window).open_link( 528 egw().link("/index.php", attrs), 529 "link_existing", 530 "680x400" 531 ); 532 if(popup) 533 { 534 // Safari and IE lose reference to global variables after window close 535 // Try to get updated data before window is closed then later we trigger 536 // change event on widget 537 egw().window.setTimeout(function(){ 538 jQuery(popup).bind("unload",function(){ 539 callback(this.selected_files, {alt:this.selected_files}); 540 }); 541 },1000); 542 } 543 }, 544 init_instance_callback : function(_editor){ 545 console.log(_editor); 546 _editor.execCommand("fontName", true,"'.$font.'"); 547 _editor.execCommand("fontSize", true,"'.$font_size.$font_size_unit.'"); 548 _editor.setContent(value); 549 }, 550 plugins: [ 551 "print searchreplace autolink directionality "+ 552 "visualblocks visualchars image link media template "+ 553 "codesample table charmap hr pagebreak nonbreaking anchor toc "+ 554 "insertdatetime advlist lists textcolor wordcount imagetools "+ 555 "colorpicker textpattern help paste code searchreplace" 556 ], 557 toolbar: "undo redo | formatselect | fontselect fontsizeselect | bold italic strikethrough forecolor backcolor | "+ 558 "link | alignleft aligncenter alignright alignjustify | numlist "+ 559 "bullist outdent indent | removeformat code| image | searchreplace", 560 block_formats: "Paragraph=p;Heading 1=h1;Heading 2=h2;Heading 3=h3;"+ 561 "Heading 4=h4;Heading 5=h5;Heading 6=h6;Preformatted=pre", 562 font_formats: "Andale Mono=andale mono,times;Arial=arial,helvetica,"+ 563 "sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book "+ 564 "antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;"+ 565 "Courier New=courier new,courier;Georgia=georgia,palatino;"+ 566 "Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;"+ 567 "Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,"+ 568 "monaco;Times New Roman=times new roman,times;Trebuchet "+ 569 "MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;"+ 570 "Wingdings=wingdings,zapf dingbats", 571 fontsize_formats:font_size_formats["'. $font_size_unit.'"], 572 }); 573 '.($_executeJSAfterInit?$_executeJSAfterInit:'').' 574}); 575</script> 576'; 577 } 578 579 /** 580 * this static function is a wrapper for tinymce to create some reuseable layouts 581 * 582 * Please note: if you did not run init_tinymce already you this static function need to be called before the call to phpgw_header() !!! 583 * 584 * @param string $_name name and id of the input-field 585 * @param string $_mode display mode of the tinymce editor can be: simple, extended or advanced 586 * @param string $_content ='' of the tinymce (will be run through htmlspecialchars !!!), default '' 587 * @param string $_height ='400px' 588 * @param string $_width ='100%' 589 * @param boolean $_purify =true 590 * @param string $_border ='0px' NOT used for CKEditor 591 * @param mixed (boolean/string) $_focusToBody=false USED only for CKEDIOR true means yes, focus on top, you may specify TOP or BOTTOM (to focus on the end of the editor area) 592 * @param string $_executeJSAfterInit ='' Javascript to be executed after InstanceReady of CKEditor 593 * @return string the necessary html for the textarea 594 */ 595 static function fckEditorQuick($_name, $_mode, $_content='', $_height='400px', $_width='100%',$_purify=true, $_border='0px',$_focusToBody=false,$_executeJSAfterInit='') 596 { 597 if (!self::htmlarea_availible() || $_mode == 'ascii') 598 { 599 //TODO: use self::textarea 600 return "<textarea name=\"$_name\" style=\"".($_width?" width:".$_width.';':" width:100%;").($_height?" height:".$_height.';':" height:400px;").($_border?" border:".$_border.';':" border:0px;")."\">$_content</textarea>"; 601 } 602 else 603 { 604 return self::fckEditor($_name, $_content, $_mode, array(), $_height, $_width,'',$_purify,$_focusToBody,$_executeJSAfterInit); 605 } 606 } 607 608 /** 609 * represents html's input tag 610 * 611 * @param string $name name 612 * @param string $value default value of the field 613 * @param string $type type, default ''=not specified = text 614 * @param string $options attributes for the tag, default ''=none 615 */ 616 static function input($name,$value='',$type='',$options='' ) 617 { 618 switch ((string)$type) 619 { 620 case ''; 621 break; 622 default: 623 $type = 'type="'.htmlspecialchars($type).'"'; 624 } 625 return "<input $type name=\"$name\" value=\"".self::htmlspecialchars($value)."\" $options />\n"; 626 } 627 628 static protected $default_background_images = array( 629 'save' => '/save(&|\]|$)/', 630 'apply' => '/apply(&|\]|$)/', 631 'cancel' => '/cancel(&|\]|$)/', 632 'delete' => '/delete(&|\]|$)/', 633 'edit' => '/edit(&|\]|$)/', 634 'next' => '/(next|continue)(&|\]|$)/', 635 'finish' => '/finish(&|\]|$)/', 636 'back' => '/(back|previous)(&|\]|$)/', 637 'copy' => '/copy(&|\]|$)/', 638 'more' => '/more(&|\]|$)/', 639 'check' => '/(yes|check)(&|\]|$)/', 640 'cancelled' => '/no(&|\]|$)/', 641 'ok' => '/ok(&|\]|$)/', 642 'close' => '/close(&|\]|$)/', 643 'add' => '/(add(&|\]|$)|create)/', // customfields use create* 644 ); 645 646 static protected $default_classes = array( 647 'et2_button_cancel' => '/cancel(&|\]|$)/', // yellow 648 'et2_button_question' => '/(yes|no)(&|\]|$)/', // yellow 649 'et2_button_delete' => '/delete(&|\]|$)/' // red 650 ); 651 652 /** 653 * represents html's button (input type submit or input type button or image) 654 * 655 * @param string $name name 656 * @param string $label label of the button 657 * @param string $onClick javascript to call, when button is clicked 658 * @param boolean $no_lang NOT running the label through lang(), default false=use lang() 659 * @param string $options attributes for the tag, default ''=none 660 * @param string $image to show instead of the label, default ''=none 661 * @param string $app app to search the image in 662 * @param string $buttontype which type of html button (button|submit), default ='submit' 663 * @return string html 664 */ 665 static function submit_button($name,$label,$onClick='',$no_lang=false,$options='',$image='',$app='phpgwapi', $buttontype='submit') 666 { 667 // workaround for idots and IE button problem (wrong cursor-image) 668 if (Header\UserAgent::type() == 'msie') 669 { 670 $options .= ' style="cursor: pointer;"'; 671 } 672 // add et2_classes to "old" buttons 673 $classes = array('et2_button'); 674 675 if ($image != '') 676 { 677 $image = str_replace(array('.gif','.GIF','.png','.PNG'),'',$image); 678 679 if (!($path = Image::find($app, $image))) 680 { 681 $path = $image; // name may already contain absolut path 682 } 683 $image = ' src="'.$path.'"'; 684 $classes[] = 'image_button'; 685 } 686 if (!$no_lang) 687 { 688 $label = lang($label); 689 } 690 if (($accesskey = @strstr($label,'&')) && $accesskey[1] != ' ' && 691 (($pos = strpos($accesskey,';')) === false || $pos > 5)) 692 { 693 $label_u = str_replace('&'.$accesskey[1],'<u>'.$accesskey[1].'</u>',$label); 694 $label = str_replace('&','',$label); 695 $options .= ' accesskey="'.$accesskey[1].'" '.$options; 696 } 697 else 698 { 699 $accesskey = ''; 700 $label_u = $label; 701 } 702 if ($onClick) $options .= ' onclick="'.str_replace('"','\\"',$onClick).'"'; 703 704 // add default background-image to get et2 like buttons 705 foreach(self::$default_background_images as $img => $reg_exp) 706 { 707 if (preg_match($reg_exp, $name) && ($url = Image::find($GLOBALS['egw_info']['flags']['currentapp'], $img))) 708 { 709 $options .= ' style="background-image: url('.$url.');"'; 710 $classes[] = 'et2_button_with_image et2_button_text'; 711 break; 712 } 713 } 714 // add default class for cancel, delete or yes/no buttons 715 foreach(self::$default_classes as $class => $reg_exp) 716 { 717 if (preg_match($reg_exp, $name)) 718 { 719 $classes[] = $class; 720 break; 721 } 722 } 723 if (strpos($options, 'class="') !== false) 724 { 725 $options = str_replace('class="', 'class="'.implode(' ', $classes).' ', $options); 726 } 727 else 728 { 729 $options .= ' class="'.implode(' ', $classes).'"'; 730 } 731 732 return '<button type="'.$buttontype.'" name="'.htmlspecialchars($name). 733 '" value="'.htmlspecialchars($label). 734 '" '.$options.'>'. 735 ($image != '' ? '<img'.$image.' title="'.self::htmlspecialchars($label).'"> ' : ''). 736 ($image == '' || $accesskey ? self::htmlspecialchars($label_u) : '').'</button>'; 737 } 738 739 /** 740 * creates an absolut link + the query / get-variables 741 * 742 * Example link('/index.php?menuaction=infolog.uiinfolog.get_list',array('info_id' => 123)) 743 * gives 'http://domain/phpgw-path/index.php?menuaction=infolog.uiinfolog.get_list&info_id=123' 744 * 745 * @param string $_url egw-relative link, may include query / get-vars 746 * @param array|string $vars query or array ('name' => 'value', ...) with query 747 * @return string absolut link already run through $phpgw->link 748 */ 749 static function link($_url,$vars='') 750 { 751 if (!is_array($vars)) 752 { 753 parse_str($vars,$vars); 754 } 755 list($url,$v) = explode('?', $_url); // url may contain additional vars 756 if ($v) 757 { 758 parse_str($v,$v); 759 $vars += $v; 760 } 761 return Framework::link($url,$vars); 762 } 763 764 /** 765 * represents html checkbox 766 * 767 * @param string $name name 768 * @param boolean $checked box checked on display 769 * @param string $value value the var should be set to, default 'True' 770 * @param string $options attributes for the tag, default ''=none 771 * @return string html 772 */ 773 static function checkbox($name,$checked=false,$value='True',$options='') 774 { 775 return '<input type="checkbox" name="'.$name.'" value="'.self::htmlspecialchars($value).'"' .($checked ? ' checked="1"' : '') . "$options />\n"; 776 } 777 778 /** 779 * represents a html form 780 * 781 * @param string $content of the form, if '' only the opening tag gets returned 782 * @param array $hidden_vars array with name-value pairs for hidden input fields 783 * @param string $_url eGW relative URL, will be run through the link function, if empty the current url is used 784 * @param string|array $url_vars parameters for the URL, send to link static function too 785 * @param string $name name of the form, defaul ''=none 786 * @param string $options attributes for the tag, default ''=none 787 * @param string $method method of the form, default 'POST' 788 * @return string html 789 */ 790 static function form($content,$hidden_vars,$_url,$url_vars='',$name='',$options='',$method='POST') 791 { 792 $url = $_url ? self::link($_url, $url_vars) : $_SERVER['PHP_SELF'].'?'.$_SERVER['QUERY_STRING']; 793 $html = "<form method=\"$method\" ".($name != '' ? "name=\"$name\" " : '')."action=\"$url\" $options>\n"; 794 $html .= self::input_hidden($hidden_vars); 795 796 if ($content) 797 { 798 $html .= $content; 799 $html .= "</form>\n"; 800 } 801 return $html; 802 } 803 804 /** 805 * represents a html form with one button 806 * 807 * @param string $name name of the button 808 * @param string $label label of the button 809 * @param array $hidden_vars array with name-value pairs for hidden input fields 810 * @param string $url eGW relative URL, will be run through the link function 811 * @param string|array $url_vars parameters for the URL, send to link static function too 812 * @param string $options attributes for the tag, default ''=none 813 * @param string $form_name name of the form, defaul ''=none 814 * @param string $method method of the form, default 'POST' 815 * @return string html 816 */ 817 static function form_1button($name,$label,$hidden_vars,$url,$url_vars='',$form_name='',$method='POST') 818 { 819 return self::form(self::submit_button($name,$label),$hidden_vars,$url,$url_vars,$form_name,' style="display: inline-block"',$method); 820 } 821 822 const THEAD = 1; 823 const TFOOT = 2; 824 const TBODY = 3; 825 static $part2tag = array( 826 self::THEAD => 'thead', 827 self::TFOOT => 'tfoot', 828 self::TBODY => 'tbody', 829 ); 830 831 /** 832 * creates table from array of rows 833 * 834 * abstracts the html stuff for the table creation 835 * Example: $rows = array ( 836 * 'h1' => array( // optional header row(s) 837 * ), 838 * 'f1' => array( // optional footer row(s) 839 * ), 840 * '1' => array( 841 * 1 => 'cell1', '.1' => 'colspan=3', 842 * 2 => 'cell2', 843 * 3 => 'cell3', '.3' => 'width="10%"' 844 * ),'.1' => 'BGCOLOR="#0000FF"' ); 845 * table($rows,'width="100%"') = '<table width="100%"><tr><td colspan=3>cell1</td><td>cell2</td><td width="10%">cell3</td></tr></table>' 846 * 847 * @param array $rows with rows, each row is an array of the cols 848 * @param string $options options for the table-tag 849 * @param boolean $no_table_tr dont return the table- and outmost tr-tabs, default false=return table+tr 850 * @return string with html-code of the table 851 */ 852 static function table($rows,$options = '',$no_table_tr=False) 853 { 854 $html = $no_table_tr ? '' : "<table $options>\n"; 855 856 $part = 0; 857 foreach($rows as $key => $row) 858 { 859 if (!is_array($row)) 860 { 861 continue; // parameter 862 } 863 // get the current part from the optional 'h' or 'f' prefix of the key 864 $p = $key[0] == 'h' ? self::THEAD : ($key[0] == 'f' ? self::TFOOT : self::TBODY); 865 if ($part < $p && ($part || $p < self::TBODY)) // add only allowed and neccessary transitions 866 { 867 if ($part) $html .= '</'.self::$part2tag[$part].">\n"; 868 $html .= '<'.self::$part2tag[$part=$p].">\n"; 869 } 870 $html .= $no_table_tr && $key == 1 ? '' : "\t<tr ".$rows['.'.$key].">\n"; 871 872 foreach($row as $key => $cell) 873 { 874 if ($key[0] == '.') 875 { 876 continue; // parameter 877 } 878 $table_pos = strpos($cell,'<table'); 879 $td_pos = strpos($cell,'<td'); 880 if ($td_pos !== False && ($table_pos === False || $td_pos < $table_pos)) 881 { 882 $html .= $cell; 883 } 884 else 885 { 886 $html .= "\t\t<td ".$row['.'.$key].">$cell</td>\n"; 887 } 888 } 889 $html .= "\t</tr>\n"; 890 } 891 if (!is_array($rows)) 892 { 893 echo "<p>".function_backtrace()."</p>\n"; 894 } 895 if ($part) // close current part 896 { 897 $html .= "</".self::$part2tag[$part].">\n"; 898 } 899 $html .= "</table>\n"; 900 901 if ($no_table_tr) 902 { 903 $html = substr($html,0,-16); 904 } 905 return $html; 906 } 907 908 /** 909 * changes a selectbox to submit the form if it gets changed, to be used with the sbox-class 910 * 911 * @param string $sbox html with the select-box 912 * @param boolean $no_script if true generate a submit-button if javascript is off 913 * @return string html 914 */ 915 static function sbox_submit( $sbox,$no_script=false ) 916 { 917 $html = str_replace('<select','<select onchange="this.form.submit()" ',$sbox); 918 if ($no_script) 919 { 920 $html .= '<noscript>'.self::submit_button('send','>').'</noscript>'; 921 } 922 return $html; 923 } 924 925 /** 926 * html-widget showing progessbar with a view div's (html4 only, textual percentage otherwise) 927 * 928 * @param mixed $_percent percent-value, gets casted to int 929 * @param string $_title title for the progressbar, default ''=the percentage itself 930 * @param string $options attributes for the outmost div (may include onclick="...") 931 * @param string $width width, default 30px 932 * @param string $color color, default '#D00000' (dark red) 933 * @param string $height height, default 5px 934 * @return string html 935 */ 936 static function progressbar($_percent, $_title='',$options='',$width='',$color='',$height='' ) 937 { 938 $percent = (int)$_percent; 939 if (!$width) $width = '30px'; 940 if (!$height)$height= '5px'; 941 if (!$color) $color = '#D00000'; 942 $title = $_title ? self::htmlspecialchars($_title) : $percent.'%'; 943 944 return '<div class="onlyPrint">'.$title.'</div><div class="noPrint" title="'.$title.'" '.$options. 945 ' style="height: '.$height.'; width: '.$width.'; border: 1px solid black; padding: 1px; text-align: left;'. 946 (@stristr($options,'onclick="') ? ' cursor: pointer;' : '').'">'."\n\t". 947 '<div style="height: '.$height.'; width: '.$percent.'%; background: '.$color.';"></div>'."\n</div>\n"; 948 } 949 950 /** 951 * representates a html img tag, output a picture 952 * 953 * If the name ends with a '%' and the rest is numeric, a progressionbar is shown instead of an image. 954 * The vfs:/ pseudo protocoll allows to access images in the vfs, eg. vfs:/home/ralf/me.png 955 * Instead of a name you specify an array with get-vars, it is passed to eGW's link function. 956 * This way session-information gets passed, eg. $name=array('menuaction'=>'myapp.class.image','id'=>123). 957 * 958 * @param string $app app-name to search the image 959 * @param string|array $name image-name or URL (incl. vfs:/) or array with get-vars 960 * @param string $title tooltip, default '' = none 961 * @param string $options further options for the tag, default '' = none 962 * @return string the html 963 */ 964 static function image( $app,$name,$title='',$options='' ) 965 { 966 if (is_array($name)) // menuaction and other get-vars 967 { 968 $name = $GLOBALS['egw']->link('/index.php',$name); 969 } 970 if (substr($name,0,5) == 'vfs:/') // vfs pseudo protocoll 971 { 972 $name = Framework::link(Vfs::download_url(substr($name,4))); 973 } 974 if ($name[0] == '/' || substr($name,0,7) == 'http://' || substr($name,0,8) == 'https://' || stripos($name,'api/thumbnail.php') ) 975 { 976 if (!($name[0] == '/' || substr($name,0,7) == 'http://' || substr($name,0,8) == 'https://')) $name = '/'.$name; 977 $url = $name; 978 } 979 else // no URL, so try searching the image 980 { 981 $name = str_replace(array('.gif','.GIF','.png','.PNG'),'',$name); 982 983 if (!($url = Image::find($app,$name))) 984 { 985 $url = $name; // name may already contain absolut path 986 } 987 if($GLOBALS['egw_info']['server']['webserver_url']) 988 { 989 list(,$path) = explode($GLOBALS['egw_info']['server']['webserver_url'],$url); 990 991 if (!is_null($path)) $path = EGW_SERVER_ROOT.$path; 992 } 993 else 994 { 995 $path = EGW_SERVER_ROOT.$url; 996 } 997 998 if (is_null($path) || (!@is_readable($path) && stripos($path,'webdav.php')===false)) 999 { 1000 // if the image-name is a percentage, use a progressbar 1001 if (substr($name,-1) == '%' && is_numeric($percent = substr($name,0,-1))) 1002 { 1003 return self::progressbar($percent,$title); 1004 } 1005 return $title; 1006 } 1007 } 1008 if ($title) 1009 { 1010 $options .= ' title="'.self::htmlspecialchars($title).'"'; 1011 } 1012 1013 // This block makes pngfix.js useless, adding a check on disable_pngfix to have pngfix.js do its thing 1014 if (Header\UserAgent::type() == 'msie' && Header\UserAgent::version() < 7.0 && substr($url,-4) == '.png' && ($GLOBALS['egw_info']['user']['preferences']['common']['disable_pngfix'] || !isset($GLOBALS['egw_info']['user']['preferences']['common']['disable_pngfix']))) 1015 { 1016 $extra_styles = "display: inline-block; filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='$url',sizingMethod='image'); width: 1px; height: 1px;"; 1017 if (false!==strpos($options,'style="')) 1018 { 1019 $options = str_replace('style="','style="'.$extra_styles, $options); 1020 } 1021 else 1022 { 1023 $options .= ' style="'.$extra_styles.'"'; 1024 } 1025 return "<span $options></span>"; 1026 } 1027 return "<img src=\"$url\" $options />"; 1028 } 1029 1030 /** 1031 * representates a html link 1032 * 1033 * @param string $content of the link, if '' only the opening tag gets returned 1034 * @param string $url eGW relative URL, will be run through the link function 1035 * @param string|array $vars parameters for the URL, send to link static function too 1036 * @param string $options attributes for the tag, default ''=none 1037 * @return string the html 1038 */ 1039 static function a_href( $content,$url,$vars='',$options='') 1040 { 1041 if (is_array($url)) 1042 { 1043 $vars = $url; 1044 $url = '/index.php'; 1045 } 1046 elseif (strpos($url,'/')===false && 1047 count(explode('.',$url)) >= 3 && 1048 !(strpos($url,'mailto:')!==false || 1049 strpos($url,'://')!==false || 1050 strpos($url,'javascript:')!==false)) 1051 { 1052 $url = "/index.php?menuaction=$url"; 1053 } 1054 if ($url[0] == '/') // link relative to eGW 1055 { 1056 $url = self::link($url,$vars); 1057 } 1058 //echo "<p>self::a_href('".self::htmlspecialchars($content)."','$url',".print_r($vars,True).") = ".self::link($url,$vars)."</p>"; 1059 return '<a href="'.self::htmlspecialchars($url).'" '.$options.'>'.$content.'</a>'; 1060 } 1061 1062 /** 1063 * representates a b tag (bold) 1064 * 1065 * @param string $content of the link, if '' only the opening tag gets returned 1066 * @return string the html 1067 */ 1068 static function bold($content) 1069 { 1070 return '<b>'.($content?$content.'</b>':''); 1071 } 1072 1073 /** 1074 * representates a hr tag (horizontal rule) 1075 * 1076 * @param string $width default ''=none given 1077 * @param string $options attributes for the tag, default ''=none 1078 * @return string the html 1079 */ 1080 static function hr($width='',$options='') 1081 { 1082 if ($width) $options .= " width=\"$width\""; 1083 1084 return "<hr $options />\n"; 1085 } 1086 1087 /** 1088 * formats option-string for most of the above functions 1089 * 1090 * Example: formatOptions('100%,,1','width,height,border') = ' width="100%" border="1"' 1091 * 1092 * @param mixed $options String (or Array) with option-values eg. '100%,,1' 1093 * @param mixed $names String (or Array) with the option-names eg. 'WIDTH,HEIGHT,BORDER' 1094 * @return string with options/attributes 1095 */ 1096 static function formatOptions($options,$names) 1097 { 1098 if (!is_array($options)) $options = explode(',',$options); 1099 if (!is_array($names)) $names = explode(',',$names); 1100 1101 foreach($options as $n => $val) 1102 { 1103 if ($val != '' && $names[$n] != '') 1104 { 1105 $html .= ' '.strtolower($names[$n]).'="'.$val.'"'; 1106 } 1107 } 1108 return $html; 1109 } 1110 1111 /** 1112 * html style tag (incl. type) 1113 * 1114 * @param string $styles css-style definitions 1115 * @return string html 1116 */ 1117 static function style($styles) 1118 { 1119 return $styles ? "<style type=\"text/css\">\n<!--\n$styles\n-->\n</style>" : ''; 1120 } 1121 1122 /** 1123 * html label tag 1124 * 1125 * @param string $content the label 1126 * @param string $id for the for attribute, default ''=none 1127 * @param string $accesskey accesskey, default ''=none 1128 * @param string $options attributes for the tag, default ''=none 1129 * @return string the html 1130 */ 1131 static function label($content,$id='',$accesskey='',$options='') 1132 { 1133 if ($id != '') 1134 { 1135 $id = " for=\"$id\""; 1136 } 1137 if ($accesskey != '') 1138 { 1139 $accesskey = " accesskey=\"$accesskey\""; 1140 } 1141 return "<label$id$accesskey $options>$content</label>"; 1142 } 1143 1144 /** 1145 * html fieldset, eg. groups a group of radiobuttons 1146 * 1147 * @param string $content the content 1148 * @param string $legend legend / label of the fieldset, default ''=none 1149 * @param string $options attributes for the tag, default ''=none 1150 * @return string the html 1151 */ 1152 static function fieldset($content,$legend='',$options='') 1153 { 1154 $html = "<fieldset $options>".($legend ? '<legend>'.self::htmlspecialchars($legend).'</legend>' : '')."\n"; 1155 1156 if ($content) 1157 { 1158 $html .= $content; 1159 $html .= "\n</fieldset>\n"; 1160 } 1161 return $html; 1162 } 1163 1164 /** 1165 * tree widget using dhtmlXtree 1166 * 1167 * Code inspired by Lars's Felamimail uiwidgets::createFolderTree() 1168 * 1169 * @author Lars Kneschke <lars-AT-kneschke.de> original code in felamimail 1170 * @param array $_folders array of folders: pairs path => node (string label or array with keys: label, (optional) image, (optional) title, (optional) checked) 1171 * @param string $_selected path of selected folder 1172 * @param mixed $_topFolder =false node of topFolder or false for none 1173 * @param string $_onNodeSelect ='alert' js static function to call if node gets selected 1174 * @param string $tree ='foldertree' id of the div and name of the variable containing the tree object 1175 * @param string $_divClass ='' css class of the div 1176 * @param string $_leafImage ='' default image of a leaf-node, ''=default of foldertree, set it eg. 'folderClosed.gif' to show leafs as folders 1177 * @param boolean|string $_onCheckHandler =false string with handler-name to display a checkbox for each folder, or false (default), 'null' switches checkboxes on without an handler! 1178 * @param string $delimiter ='/' path-delimiter, default / 1179 * @param string $folderImageDir =null string path to the tree menu images, null uses default path 1180 * @param string|array $autoLoading =null EGw relative path or array with get parameter, both send through Framework::link 1181 * @param string $dataMode ='JSON' data type for autoloading: XML, JSON, CSV 1182 * @param boolean $dragndrop =false true to enable drag-n-drop (must be before autoloading get enabled!) 1183 * 1184 * @return string the html code, to be added into the template 1185 */ 1186 static function tree($_folders,$_selected,$_topFolder=false,$_onNodeSelect="null",$tree='foldertree',$_divClass='', 1187 $_leafImage='',$_onCheckHandler=false,$delimiter='/',$folderImageDir=null,$autoLoading=null,$dataMode='JSON', 1188 $dragndrop=false) 1189 { 1190 $webserver_url = $GLOBALS['egw_info']['server']['webserver_url']; 1191 if (empty($folderImageDir)) 1192 { 1193 $folderImageDir = $webserver_url.'/phpgwapi/templates/default/images'; 1194 } 1195 // check if we have template-set specific image path 1196 $image_path = $folderImageDir; 1197 if ($webserver_url && $webserver_url != '/') 1198 { 1199 list(,$image_path) = explode($webserver_url, $image_path, 2); 1200 } 1201 $templated_path = strtr($image_path, array( 1202 '/phpgwapi/templates/default' => $GLOBALS['egw']->framework->template_dir, 1203 '/default/' => '/'.$GLOBALS['egw']->framework->template.'/', 1204 )); 1205 if (file_exists(EGW_SERVER_ROOT.$templated_path.'/dhtmlxtree')) 1206 { 1207 $folderImageDir = ($webserver_url != '/' ? $webserver_url : '').$templated_path; 1208 //error_log(__METHOD__."() setting templated image-path: $folderImageDir"); 1209 } 1210 1211 static $tree_initialised=false; 1212 if (!$tree_initialised) 1213 { 1214 Framework::includeCSS('/api/js/dhtmlxtree/codebase/dhtmlxtree.css'); 1215 Framework::includeJS('/api/js/dhtmlxtree/codebase/dhtmlxcommon.js'); 1216 Framework::includeJS('/api/js/dhtmlxtree/sources/dhtmlxtree.js'); 1217 if ($autoLoading && $dataMode != 'XML') Framework::includeJS('/api/js/dhtmlxtree/sources/ext/dhtmlxtree_json.js'); 1218 $tree_initialised = true; 1219 if (!$_folders && !$autoLoading) return null; 1220 } 1221 $html = self::div("\n",'id="'.$tree.'"',$_divClass).$html; 1222 $html .= "<script type='text/javascript'>\n"; 1223 $html .= "var $tree;"; 1224 $html .= "egw_LAB.wait(function() {"; 1225 $html .= "$tree = new"." dhtmlXTreeObject('$tree','100%','100%',0);\n"; 1226 $html .= "$tree.parentObject.style.overflow='auto';\n"; // dhtmlXTree constructor has hidden hardcoded 1227 if (Translation::charset() == 'utf-8') $html .= "if ($tree.setEscapingMode) $tree.setEscapingMode('utf8');\n"; 1228 $html .= "$tree.setImagePath('$folderImageDir/dhtmlxtree/');\n"; 1229 1230 if($_onCheckHandler) 1231 { 1232 $html .= "$tree.enableCheckBoxes(1);\n"; 1233 $html .= "$tree.setOnCheckHandler('$_onCheckHandler');\n"; 1234 } 1235 1236 if ($dragndrop) $html .= "$tree.enableDragAndDrop(true);\n"; 1237 1238 if ($autoLoading) 1239 { 1240 $autoLoading = is_array($autoLoading) ? 1241 Framework::link('/index.php',$autoLoading) : Framework::link($autoLoading); 1242 $html .= "$tree.setXMLAutoLoading('$autoLoading');\n"; 1243 if ($dataMode != 'XML') $html .= "$tree.setDataMode('$dataMode');\n"; 1244 1245 // if no folders given, use xml url to load root, incl. setting of selected folder 1246 if (!$_folders) 1247 { 1248 if ($_selected) $autoLoading .= '&selected='.urlencode($_selected); 1249 unset($_selected); 1250 $html .= "$tree.loadXML('$autoLoading');\n"; 1251 $html .= "});"; 1252 return $html."</script>\n"; 1253 } 1254 } 1255 1256 $top = 0; 1257 if ($_topFolder) 1258 { 1259 $top = '--topfolder--'; 1260 $topImage = ''; 1261 if (is_array($_topFolder)) 1262 { 1263 $label = $_topFolder['label']; 1264 if (isset($_topFolder['image'])) 1265 { 1266 $topImage = $_topFolder['image']; 1267 } 1268 } 1269 else 1270 { 1271 $label = $_topFolder; 1272 } 1273 $html .= "\n$tree.insertNewItem(0,'$top','".addslashes($label)."',$_onNodeSelect,'$topImage','$topImage','$topImage','CHILD,TOP');\n"; 1274 1275 if (is_array($_topFolder) && isset($_topFolder['title'])) 1276 { 1277 $html .= "$tree.setItemText('$top','".addslashes($label)."','".addslashes($_topFolder['title'])."');\n"; 1278 } 1279 } 1280 if (is_string($_folders)) 1281 { 1282 switch($dataMode) 1283 { 1284 case 'JSON': 1285 $html .= "$tree.loadJSONObject($_folders);\n"; break; 1286 case 'XML': 1287 $html .= "$tree.loadXMLString('$_folders');\n"; break; 1288 } 1289 } 1290 else 1291 { 1292 // evtl. remove leading delimiter 1293 if ($_selected[0] == $delimiter) $_selected = substr($_selected,1); 1294 1295 $n = 0; 1296 foreach($_folders as $path => $data) 1297 { 1298 if (!is_array($data)) 1299 { 1300 $data = array('label' => $data); 1301 } 1302 $image1 = $image2 = $image3 = '0'; 1303 1304 // if _leafImage given, set it only for leaves, not for folders containing children 1305 if ($_leafImage) 1306 { 1307 $image1 = $image2 = $image3 = "'".$_leafImage."'"; 1308 if (($next_item = array_slice($_folders, $n+1, 1, true))) 1309 { 1310 $next_path = key($next_item); 1311 if (substr($next_path,0,strlen($path)+1) == $path.'/') 1312 { 1313 $image1 = $image2 = $image3 = '0'; 1314 } 1315 } 1316 } 1317 if (isset($data['image'])) 1318 { 1319 $image1 = $image2 = $image3 = "'".$data['image']."'"; 1320 } 1321 // evtl. remove leading delimiter 1322 if ($path[0] == $delimiter) $path = substr($path,1); 1323 $folderParts = explode($delimiter,$path); 1324 1325 //get rightmost folderpart 1326 $label = array_pop($folderParts); 1327 if (isset($data['label'])) $label = $data['label']; 1328 1329 // the rest of the array is the name of the parent 1330 $parentName = implode((array)$folderParts,$delimiter); 1331 if(empty($parentName)) $parentName = $top; 1332 1333 $entryOptions = !isset($data['child']) || $data['child'] ? 'CHILD' : ''; 1334 if ($_onCheckHandler && $_selected) // check selected items on multi selection 1335 { 1336 if (!is_array($_selected)) $_selected = explode(',',$_selected); 1337 if (array_search("$path",$_selected)!==false) $entryOptions .= ',CHECKED'; 1338 //echo "<p>path=$path, _selected=".print_r($_selected,true).": $entryOptions</p>\n"; 1339 } 1340 // highlight current item 1341 elseif ((string)$_selected === (string)$path) 1342 { 1343 $entryOptions .= ',SELECT'; 1344 } 1345 $html .= "$tree.insertNewItem('".addslashes($parentName)."','".addslashes($path)."','".addslashes($label). 1346 "',$_onNodeSelect,$image1,$image2,$image3,'$entryOptions');\n"; 1347 if (isset($data['title'])) 1348 { 1349 $html .= "$tree.setItemText('".addslashes($path)."','".addslashes($label)."','".addslashes($data['title'])."');\n"; 1350 } 1351 ++$n; 1352 } 1353 } 1354 $html .= "$tree.closeAllItems(0);\n"; 1355 if ($_selected) 1356 { 1357 foreach(is_array($_selected)?$_selected:array($_selected) as $path) 1358 { 1359 $html .= "$tree.openItem('".addslashes($path)."');\n"; 1360 } 1361 } 1362 else 1363 { 1364 $html .= "$tree.openItem('$top');\n"; 1365 } 1366 $html .= "});"; 1367 $html .= "</script>\n"; 1368 1369 return $html; 1370 } 1371 1372 /** 1373 * Runs HTMLPurifier over supplied html to remove malicious code 1374 * 1375 * @param string $html 1376 * @param array|string $config =null - config to influence the behavior of current purifying engine 1377 * @param array|string $spec =null - spec to influence the behavior of current purifying engine 1378 * The $spec argument can be used to disallow an otherwise legal attribute for an element, 1379 * or to restrict the attribute's values 1380 * @param boolean $_force =null - force the config passed to be used without merging to the default 1381 */ 1382 static function purify($html,$config=null,$spec=array(),$_force=false) 1383 { 1384 return Html\HtmLawed::purify($html, $config, $spec, $_force); 1385 } 1386} 1387