| [ Index ] |
|
Code source de b2evolution 2.1.0-beta |
1 <?php 2 /** 3 * This file implements general purpose functions. 4 * 5 * This file is part of the evoCore framework - {@link http://evocore.net/} 6 * See also {@link http://sourceforge.net/projects/evocms/}. 7 * 8 * @copyright (c)2003-2007 by Francois PLANQUE - {@link http://fplanque.net/} 9 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}. 10 * Parts of this file are copyright (c)2005-2006 by PROGIDISTRI - {@link http://progidistri.com/}. 11 * 12 * {@internal License choice 13 * - If you have received this file as part of a package, please find the license.txt file in 14 * the same folder or the closest folder above for complete license terms. 15 * - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/) 16 * then you must choose one of the following licenses before using the file: 17 * - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php 18 * - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php 19 * }} 20 * 21 * {@internal Open Source relicensing agreement: 22 * Daniel HAHLER grants Francois PLANQUE the right to license 23 * Daniel HAHLER's contributions to this file and the b2evolution project 24 * under any OSI approved OSS license (http://www.opensource.org/licenses/). 25 * 26 * PROGIDISTRI S.A.S. grants Francois PLANQUE the right to license 27 * PROGIDISTRI S.A.S.'s contributions to this file and the b2evolution project 28 * under any OSI approved OSS license (http://www.opensource.org/licenses/). 29 * }} 30 * 31 * @package evocore 32 * 33 * @todo dh> Refactor into smaller chunks/files. We should avoid using a "huge" misc early! 34 * - _debug.funcs.php 35 * - _formatting.funcs.php 36 * - _date.funcs.php 37 * - ? 38 * NOTE: Encapsulation functions into classes would allow using autoloading (http://php.net/autoload) in PHP5..! 39 * 40 * {@internal Below is a list of authors who have contributed to design/coding of this file: }} 41 * @author cafelog (team) 42 * @author blueyed: Daniel HAHLER. 43 * @author fplanque: Francois PLANQUE. 44 * @author jeffbearer: Jeff BEARER. 45 * @author sakichan: Nobuo SAKIYAMA. 46 * @author vegarg: Vegar BERG GULDAL. 47 * @author mbruneau: Marc BRUNEAU / PROGIDISTRI 48 * 49 * @version $Id: _misc.funcs.php,v 1.7 2007/11/03 21:04:25 fplanque Exp $ 50 */ 51 if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' ); 52 53 54 /** 55 * Dependencies 56 */ 57 load_funcs('antispam/model/_antispam.funcs.php'); 58 load_funcs('files/model/_file.funcs.php'); 59 60 61 /***** Formatting functions *****/ 62 63 /** 64 * Format a string/content for being output 65 * 66 * @author fplanque 67 * @param string raw text 68 * @param string format, can be one of the following 69 * - raw: do nothing 70 * - htmlbody: display in HTML page body: allow full HTML 71 * - entityencoded: Special mode for RSS 0.92: allow full HTML but escape it 72 * - htmlhead: strips out HTML (mainly for use in Title) 73 * - htmlattr: use as an attribute: escapes quotes, strip tags 74 * - formvalue: use as a form value: escapes quotes and < > but leaves code alone 75 * - text: use as plain-text, e.g. for ascii-mails 76 * - xml: use in an XML file: strip HTML tags 77 * - xmlattr: use as an attribute: strips tags and escapes quotes 78 * @return string formatted text 79 */ 80 function format_to_output( $content, $format = 'htmlbody' ) 81 { 82 global $Plugins; 83 84 switch( $format ) 85 { 86 case 'raw': 87 // do nothing! 88 break; 89 90 case 'htmlbody': 91 // display in HTML page body: allow full HTML 92 $content = convert_chars($content, 'html'); 93 break; 94 95 case 'urlencoded': 96 // Encode string to be passed as part of an URL 97 $content = rawurlencode( $content ); 98 break; 99 100 case 'entityencoded': 101 // Special mode for RSS 0.92: apply renders and allow full HTML but escape it 102 $content = convert_chars($content, 'html'); 103 $content = htmlspecialchars( $content ); 104 break; 105 106 case 'htmlhead': 107 // strips out HTML (mainly for use in Title) 108 $content = strip_tags($content); 109 $content = convert_chars($content, 'html'); 110 break; 111 112 case 'htmlattr': 113 // use as an attribute: strips tags and escapes quotes 114 $content = strip_tags($content); 115 $content = convert_chars($content, 'html'); 116 $content = str_replace('"', '"', $content ); 117 $content = str_replace("'", ''', $content ); 118 break; 119 120 case 'formvalue': 121 // use as a form value: escapes &, quotes and < > but leaves code alone 122 $content = htmlspecialchars( $content ); // Handles &, ", < and > 123 $content = str_replace("'", ''', $content ); // Handles ' 124 break; 125 126 case 'xml': 127 // use in an XML file: strip HTML tags 128 $content = strip_tags($content); 129 $content = convert_chars($content, 'xml'); 130 break; 131 132 case 'xmlattr': 133 // use as an attribute: strips tags and escapes quotes 134 $content = strip_tags($content); 135 $content = convert_chars($content, 'xml'); 136 $content = str_replace('"', '"', $content ); 137 $content = str_replace("'", ''', $content ); 138 break; 139 140 case 'text': 141 // use as plain-text, e.g. for ascii-mails 142 $content = strip_tags( $content ); 143 $trans_tbl = get_html_translation_table( HTML_ENTITIES ); 144 $trans_tbl = array_flip( $trans_tbl ); 145 $content = strtr( $content, $trans_tbl ); 146 $content = preg_replace( '/[ \t]+/', ' ', $content); 147 $content = trim($content); 148 break; 149 150 default: 151 debug_die( 'Output format ['.$format.'] not supported.' ); 152 } 153 154 return $content; 155 } 156 157 158 /** 159 * Format raw HTML input to cleaned up and validated HTML. 160 * 161 * @param string The content to format 162 * @param integer Create automated <br /> tags? (Deprecated??!) 163 * @param integer Is this a comment? (Used for balanceTags(), SafeHtmlChecker()'s URI scheme, styling restrictions) 164 * @param string Encoding (used for SafeHtmlChecker() only!); defaults to $io_charset 165 * @return string 166 */ 167 function format_to_post( $content, $autobr = 0, $is_comment = 0, $encoding = NULL ) 168 { 169 global $use_balanceTags, $use_html_checker, $use_security_checker; 170 global $allowed_tags, $allowed_attributes, $uri_attrs, $allowed_uri_scheme; 171 global $comments_allowed_tags, $comments_allowed_attributes, $comments_allowed_uri_scheme; 172 global $io_charset; 173 global $Messages; 174 175 // Replace any & that is not a character or entity reference with & 176 $content = preg_replace( '/&(?!#[0-9]+;|#x[0-9a-fA-F]+;|[a-zA-Z_:][a-zA-Z0-9._:-]*;)/', '&', $content ); 177 178 if( $autobr ) 179 { // Auto <br />: 180 // may put brs in the middle of multiline tags... 181 // TODO: this may create "<br />" tags in "<UL>" (outside of <LI>) and make the HTML invalid! 182 $content = autobrize($content); 183 } 184 185 if( $use_balanceTags ) 186 { // Auto close open tags: 187 $content = balanceTags($content, $is_comment); 188 } 189 190 if( $use_html_checker ) 191 { // Check the code: 192 load_class( 'xhtml_validator/_xhtml_validator.class.php' ); 193 194 if( empty($encoding) ) 195 { 196 $encoding = $io_charset; 197 } 198 if( ! $is_comment ) 199 { 200 $checker = & new SafeHtmlChecker( $allowed_tags, $allowed_attributes, 201 $uri_attrs, $allowed_uri_scheme, $encoding ); 202 } 203 else 204 { 205 $checker = & new SafeHtmlChecker( $comments_allowed_tags, $comments_allowed_attributes, 206 $uri_attrs, $comments_allowed_uri_scheme, $encoding ); 207 } 208 209 $checker->check( $content ); // TODO: see if we need to use convert_chars( $content, 'html' ) 210 } 211 212 if( !isset( $use_security_checker ) ) $use_security_checker = 1; 213 if( $use_security_checker ) 214 { 215 // Security checking: 216 $check = $content; 217 // Open comments or '<![CDATA[' are dangerous 218 $check = str_replace('<!', '<', $check); 219 // # # are delimiters 220 // i modifier at the end means caseless 221 $matches = array(); 222 // onclick= etc... 223 if( preg_match ('#\s(on[a-z]+)\s*=#i', $check, $matches) 224 // action=, background=, cite=, classid=, codebase=, data=, href=, longdesc=, profile=, src= 225 // usemap= 226 || preg_match ('#=["\'\s]*(javascript|vbscript|about):#i', $check, $matches) 227 || preg_match ('#\<\/?\s*(frame|iframe|applet|object)#i', $check, $matches) ) 228 { 229 $Messages->add( T_('Illegal markup found: ').htmlspecialchars($matches[1]), 'error' ); 230 } 231 // Styling restictions: 232 $matches = array(); 233 if( $is_comment && preg_match ('#\s(style|class|id)\s*=#i', $check, $matches) ) 234 { 235 $Messages->add( T_('Unallowed CSS markup found: ').htmlspecialchars($matches[1]), 'error' ); 236 } 237 } 238 return($content); 239 } 240 241 242 /* 243 * autobrize(-) 244 */ 245 function autobrize($content) { 246 $content = preg_replace("/<br>\n/", "\n", $content); 247 $content = preg_replace("/<br \/>\n/", "\n", $content); 248 $content = preg_replace("/(\015\012)|(\015)|(\012)/", "<br />\n", $content); 249 return($content); 250 } 251 252 /* 253 * unautobrize(-) 254 */ 255 function unautobrize($content) 256 { 257 $content = preg_replace("/<br>\n/", "\n", $content); //for PHP versions before 4.0.5 258 $content = preg_replace("/<br \/>\n/", "\n", $content); 259 return($content); 260 } 261 262 /* 263 * zeroise(-) 264 */ 265 function zeroise($number, $threshold) 266 { // function to add leading zeros when necessary 267 $l = strlen($number); 268 if ($l < $threshold) 269 for ($i = 0; $i < ($threshold - $l); $i = $i + 1) { $number='0'.$number; } 270 return($number); 271 } 272 273 274 /** 275 * Convert all non ASCII chars (except if UTF-8, GB2312 or CP1251) to &#nnnn; unicode references. 276 * Also convert entities to &#nnnn; unicode references if output is not HTML (eg XML) 277 * 278 * Preserves < > and quotes. 279 * 280 * fplanque: simplified 281 * sakichan: pregs instead of loop 282 */ 283 function convert_chars( $content, $flag='html' ) 284 { 285 global $b2_htmltrans, $b2_htmltranswinuni, $evo_charset; 286 287 // Convert highbyte non ASCII/UTF-8 chars to urefs: 288 if( ! in_array($evo_charset, array('utf-8', 'gb2312', 'windows-1251') ) ) 289 { // This is a single byte charset 290 $content = preg_replace_callback( 291 '/[\x80-\xff]/', 292 create_function( '$j', 'return "&#".ord($j[0]).";";' ), 293 $content); 294 } 295 296 // Convert Windows CP1252 => Unicode (valid HTML) 297 // TODO: should this go to input conversions instead (?) 298 $content = strtr( $content, $b2_htmltranswinuni ); 299 300 if( $flag == 'html' ) 301 { // we can use entities 302 // Convert & chars that are not used in an entity 303 $content = preg_replace('/&(?![#A-Za-z0-9]{2,20};)/', '&', $content); 304 } 305 else 306 { // unicode, xml... 307 // Convert & chars that are not used in an entity 308 $content = preg_replace('/&(?![#A-Za-z0-9]{2,20};)/', '&', $content); 309 310 // Convert HTML entities to urefs: 311 $content = strtr($content, $b2_htmltrans); 312 } 313 314 return( $content ); 315 } 316 317 318 /** 319 * Split $text into blocks by using $pattern and call $callback on the non-matching blocks. 320 * 321 * The non-matching block's text is the first param to $callback and additionally $params gets passed. 322 * 323 * This gets used to make links clickable or replace smilies. 324 * 325 * E.g., to replace only in non-HTML tags, call it like: 326 * <code>callback_on_non_matching_blocks( $text, '~<[^>]*>~s', 'your_callback' );</code> 327 * 328 * {@internal This function gets tested in misc.funcs.simpletest.php.}} 329 * 330 * @param string Text to handle 331 * @param string Regular expression pattern that defines blocks to exclude. 332 * @param callback Function name or object/method array to use as callback. 333 * Each non-matching block gets passed as first param, additional params may be 334 * passed with $params. 335 * @param array Of additional ("static") params to $callback. 336 * @return string 337 */ 338 function callback_on_non_matching_blocks( $text, $pattern, $callback, $params = array() ) 339 { 340 if( preg_match_all( $pattern, $text, $matches, PREG_OFFSET_CAPTURE | PREG_PATTERN_ORDER ) ) 341 { // $pattern matches, call the callback method on each non-matching block 342 $pos = 0; 343 $new_r = ''; 344 345 foreach( $matches[0] as $l_matching ) 346 { 347 $pos_match = $l_matching[1]; 348 $non_match = substr( $text, $pos, ($pos_match - $pos) ); 349 350 // Callback: 351 $callback_params = $params; 352 array_unshift( $callback_params, $non_match ); 353 $new_r .= call_user_func_array( $callback, $callback_params ); 354 355 $new_r .= $l_matching[0]; 356 $pos += strlen($non_match)+strlen($l_matching[0]); 357 } 358 359 // Callback: 360 $callback_params = $params; 361 array_unshift( $callback_params, substr( $text, $pos ) ); 362 #pre_dump( $matches, $callback_params ); 363 $new_r .= call_user_func_array( $callback, $callback_params ); 364 365 return $new_r; 366 } 367 368 $callback_params = $params; 369 array_unshift( $callback_params, $text ); 370 return call_user_func_array( $callback, $callback_params ); 371 } 372 373 374 /** 375 * Make links clickable in a given text. 376 * 377 * It replaces only text which is not between <a> tags already. 378 * 379 * @uses callback_on_non_matching_blocks() 380 * 381 * {@internal This function gets tested in misc.funcs.simpletest.php.}} 382 * 383 * @return string 384 */ 385 function make_clickable( $text, $moredelim = '&' ) 386 { 387 $text = callback_on_non_matching_blocks( $text, '~<a[^>]*("[^"]"|\'[^\']\')?[^>]*>.*?</a>~is', 'make_clickable_callback', array( $moredelim ) ); 388 389 return $text; 390 } 391 392 393 /** 394 * Callback function for {@link make_clickable()}. 395 * 396 * @todo IMHO it would be better to use "\b" (word boundary) to match the beginning of links.. 397 * 398 * original function: phpBB, extended here for AIM & ICQ 399 * fplanque restricted :// to http:// and mailto:// 400 * Fixed to not include trailing dot and comma. 401 * 402 * @return string The clickable text. 403 */ 404 function make_clickable_callback( & $text, $moredelim = '&' ) 405 { 406 $pattern_domain = '([a-z0-9\-]+\.[a-z0-9\-.\~]+)'; // a domain name (not very strict) 407 $text = preg_replace( 408 array( '#(^|[\s>])(https?|mailto)://([^<>{}\s]+[^.,<>{}\s])#i', 409 '#(^|[\s>])aim:([^,<\s]+)#i', 410 '#(^|[\s>])icq:(\d+)#i', 411 '#(^|[\s>])www\.'.$pattern_domain.'((?:/[^<\s]*)?[^.,\s])#i', 412 '#(^|[\s>])([a-z0-9\-_.]+?)@'.$pattern_domain.'([^.,<\s]+)#i', ), 413 array( '$1<a href="$2://$3">$2://$3</a>', 414 '$1<a href="aim:goim?screenname=$2$3'.$moredelim.'message='.rawurlencode(T_('Hello')).'">$2$3</a>', 415 '$1<a href="http://wwp.icq.com/scripts/search.dll?to=$2">$2</a>', 416 '$1<a href="http://www.$2$3$4">www.$2$3$4</a>', 417 '$1<a href="mailto:$2@$3$4">$2@$3$4</a>', ), 418 $text ); 419 420 return $text; 421 } 422 423 424 /***** // Formatting functions *****/ 425 426 427 function date2mysql( $ts ) 428 { 429 return date( 'Y-m-d H:i:s', $ts ); 430 } 431 432 /** 433 * Convert a MYSQL date to a UNIX timestamp 434 */ 435 function mysql2timestamp( $m ) 436 { 437 return mktime(substr($m,11,2),substr($m,14,2),substr($m,17,2),substr($m,5,2),substr($m,8,2),substr($m,0,4)); 438 } 439 440 /** 441 * Convert a MYSQL date -- WITHOUT the time -- to a UNIX timestamp 442 */ 443 function mysql2datestamp( $m ) 444 { 445 return mktime( 0, 0, 0, substr($m,5,2), substr($m,8,2), substr($m,0,4) ); 446 } 447 448 /** 449 * Format a MYSQL date to current locale date format. 450 * 451 * @param string MYSQL date YYYY-MM-DD HH:MM:SS 452 */ 453 function mysql2localedate( $mysqlstring ) 454 { 455 return mysql2date( locale_datefmt(), $mysqlstring ); 456 } 457 458 function mysql2localetime( $mysqlstring ) 459 { 460 return mysql2date( locale_timefmt(), $mysqlstring ); 461 } 462 463 function mysql2localedatetime( $mysqlstring ) 464 { 465 return mysql2date( locale_datefmt().' '.locale_timefmt(), $mysqlstring ); 466 } 467 468 function mysql2localedatetime_spans( $mysqlstring ) 469 { 470 return '<span class="date">' 471 .mysql2date( locale_datefmt(), $mysqlstring ) 472 .'</span> <span class="time">' 473 .mysql2date( locale_timefmt(), $mysqlstring ) 474 .'</span>'; 475 } 476 477 478 /** 479 * Format a MYSQL date. 480 * 481 * @param string enhanced format string 482 * @param string MYSQL date YYYY-MM-DD HH:MM:SS 483 * @param boolean true to use GM time 484 */ 485 function mysql2date( $dateformatstring, $mysqlstring, $useGM = false ) 486 { 487 $m = $mysqlstring; 488 if( empty($m) || ($m == '0000-00-00 00:00:00' )) 489 return false; 490 491 // Get a timestamp: 492 $unixtimestamp = mysql2timestamp( $m ); 493 494 return date_i18n( $dateformatstring, $unixtimestamp, $useGM ); 495 } 496 497 498 /** 499 * Date internationalization: same as date() formatting but with i18n support 500 * 501 * @param string enhanced format string 502 * @param integer UNIX timestamp 503 * @param boolean true to use GM time 504 */ 505 function date_i18n( $dateformatstring, $unixtimestamp, $useGM = false ) 506 { 507 global $month, $month_abbrev, $weekday, $weekday_abbrev, $weekday_letter; 508 global $localtimenow, $time_difference; 509 510 if( $dateformatstring == 'isoZ' ) 511 { // full ISO 8601 format 512 $dateformatstring = 'Y-m-d\TH:i:s\Z'; 513 } 514 515 if( $useGM ) 516 { // We want a Greenwich Meridian time: 517 $r = gmdate($dateformatstring, ($unixtimestamp - $time_difference)); 518 } 519 else 520 { // We want default timezone time: 521 522 /* 523 Special symbols: 524 'b': wether it's today (1) or not (0) 525 'l': weekday 526 'D': weekday abbrev 527 'e': weekday letter 528 'F': month 529 'M': month abbrev 530 */ 531 532 #echo $dateformatstring, '<br />'; 533 534 // protect special symbols, that date() would need proper locale set for 535 $protected_dateformatstring = preg_replace( '/(?<!\\\)([blDeFM])/', '@@@\\\$1@@@', $dateformatstring ); 536 537 #echo $protected_dateformatstring, '<br />'; 538 539 $r = date( $protected_dateformatstring, $unixtimestamp ); 540 541 if( $protected_dateformatstring != $dateformatstring ) 542 { // we had special symbols, replace them 543 544 $istoday = ( date('Ymd',$unixtimestamp) == date('Ymd',$localtimenow) ) ? '1' : '0'; 545 $datemonth = date('m', $unixtimestamp); 546 $dateweekday = date('w', $unixtimestamp); 547 548 // replace special symbols 549 $r = str_replace( array( 550 '@@@b@@@', 551 '@@@l@@@', 552 '@@@D@@@', 553 '@@@e@@@', 554 '@@@F@@@', 555 '@@@M@@@', 556 ), 557 array( $istoday, 558 trim(T_($weekday[$dateweekday])), 559 trim(T_($weekday_abbrev[$dateweekday])), 560 trim(T_($weekday_letter[$dateweekday])), 561 trim(T_($month[$datemonth])), 562 trim(T_($month_abbrev[$datemonth])) ), 563 $r ); 564 } 565 } 566 567 return $r; 568 } 569 570 571 /** 572 * Format dates into a string in a way similar to sprintf() 573 */ 574 function date_sprintf( $string, $timestamp ) 575 { 576 global $date_sprintf_timestamp; 577 $date_sprintf_timestamp = $timestamp; 578 579 return preg_replace_callback( '/%\{(.*?)\}/', 'date_sprintf_callback', $string ); 580 } 581 582 function date_sprintf_callback( $matches ) 583 { 584 global $date_sprintf_timestamp; 585 586 return date_i18n( $matches[1], $date_sprintf_timestamp ); 587 } 588 589 590 591 /** 592 * 593 * @param integer year 594 * @param integer month (0-53) 595 * @param integer 0 for sunday, 1 for monday 596 */ 597 function get_start_date_for_week( $year, $week, $startofweek ) 598 { 599 $new_years_date = mktime( 0, 0, 0, 1, 1, $year ); 600 $weekday = date('w', $new_years_date); 601 // echo '<br> 1st day is a: '.$weekday; 602 603 // How many days until start of week: 604 $days_to_new_week = (7 - $weekday + $startofweek) % 7; 605 // echo '<br> days to new week: '.$days_to_new_week; 606 607 // We now add the required number of days to find the 1st sunday/monday in the year: 608 //$first_week_start_date = $new_years_date + $days_to_new_week * 86400; 609 //echo '<br> 1st week starts on '.date( 'Y-m-d H:i:s', $first_week_start_date ); 610 611 // We add the number of requested weeks: 612 // This will fail when passing to Daylight Savings Time: $date = $first_week_start_date + (($week-1) * 604800); 613 $date = mktime( 0, 0, 0, 1, $days_to_new_week + 1 + ($week-1) * 7, $year ); 614 // echo '<br> week '.$week.' starts on '.date( 'Y-m-d H:i:s', $date ); 615 616 return $date; 617 } 618 619 620 621 /** 622 * Get start and end day of a week, based on week number and start-of-week 623 * 624 * Used by Calendar 625 * 626 * fp>> I'd really like someone to comment the magic of that thing... 627 * 628 * @param date 629 * @param integer 0 for Sunday, 1 for Monday 630 */ 631 function get_weekstartend( $date, $startOfWeek ) 632 { 633 $weekday = date('w', $date); 634 $i = 86400; 635 while( $weekday <> $startOfWeek ) 636 { 637 $weekday = date('w', $date); 638 $date = $date - 86400; 639 $i = 0; 640 } 641 $week['start'] = $date + 86400 - $i; 642 $week['end'] = $date + 604800; // 691199; 643 644 // pre_dump( 'weekstartend: ', date( 'Y-m-d', $week['start'] ), date( 'Y-m-d', $week['end'] ) ); 645 646 return( $week ); 647 } 648 649 650 /** 651 * Check that email address looks valid. 652 * 653 * @param string email address to check 654 * @param string Format to use ('simple', 'rfc') 655 * 'simple': 656 * Single email address. 657 * 'rfc': 658 * Full email address, may include name (RFC2822) 659 * - example@example.org 660 * - Me <example@example.org> 661 * - "Me" <example@example.org> 662 * @param boolean Return the match or boolean 663 * 664 * @return bool|array Either true/false or the match (see {@link $return_match}) 665 */ 666 function is_email( $email, $format = 'simple', $return_match = false ) 667 { 668 #$chars = "/^([a-z0-9_]|\\-|\\.)+@(([a-z0-9_]|\\-)+\\.)+[a-z]{2,4}\$/i"; 669 670 switch( $format ) 671 { 672 case 'rfc': 673 case 'rfc2822': 674 /** 675 * Regexp pattern converted from: http://www.regexlib.com/REDetails.aspx?regexp_id=711 676 * Extended to allow escaped quotes. 677 */ 678 $pattern_email = '/^ 679 ( 680 (?>[a-zA-Z\d!\#$%&\'*+\-\/=?^_`{|}~]+\x20* 681 |"( \\\" | (?=[\x01-\x7f])[^"\\\] | \\[\x01-\x7f] )*"\x20*)* # Name 682 (<) 683 )? 684 ( 685 (?!\.)(?>\.?[a-zA-Z\d!\#$%&\'*+\-\/=?^_`{|}~]+)+ 686 |"( \\\" | (?=[\x01-\x7f])[^"\\\] | \\[\x01-\x7f] )* " # quoted mailbox name 687 ) 688 @ 689 ( 690 ((?!-)[a-zA-Z\d\-]+(?<!-)\.)+[a-zA-Z]{2,} 691 | 692 \[( 693 ( (?(?<!\[)\.)(25[0-5] | 2[0-4]\d | [01]?\d?\d) ){4} 694 | 695 [a-zA-Z\d\-]*[a-zA-Z\d]:( (?=[\x01-\x7f])[^\\\[\]] | \\[\x01-\x7f] )+ 696 )\] 697 ) 698 (?(3)>) # match ">" if it was there 699 $/x'; 700 break; 701 702 case 'simple': 703 default: 704 $pattern_email = '/^\S+@[^\.\s]\S*\.[a-z]{2,}$/i'; 705 break; 706 } 707 708 if( strpos( $email, '@' ) !== false && strpos( $email, '.' ) !== false ) 709 { 710 if( $return_match ) 711 { 712 preg_match( $pattern_email, $email, $match ); 713 return $match; 714 } 715 else 716 { 717 return (bool)preg_match( $pattern_email, $email ); 718 } 719 } 720 else 721 { 722 return $return_match ? array() : false; 723 } 724 } 725 726 727 /** 728 * Are we running on a Windows server? 729 */ 730 function is_windows() 731 { 732 return ( strtoupper(substr(PHP_OS,0,3)) == 'WIN' ); 733 } 734 735 736 function xmlrpc_getposttitle($content) 737 { 738 global $post_default_title; 739 if (preg_match('/<title>(.+?)<\/title>/is', $content, $matchtitle)) 740 { 741 $post_title = $matchtitle[1]; 742 } 743 else 744 { 745 $post_title = $post_default_title; 746 } 747 return($post_title); 748 } 749 750 751 /** 752 * Also used by post by mail 753 * @deprecated by xmlrpc_getpostcategories() 754 */ 755 function xmlrpc_getpostcategory($content) 756 { 757 if (preg_match('/<category>([0-9]+?)<\/category>/is', $content, $matchcat)) 758 { 759 return $matchcat[1]; 760 } 761 762 return false; 763 } 764 765 766 /** 767 * Extract categories out of "<category>" tag from $content. 768 * 769 * NOTE: w.bloggar sends something like "<category>00000013,00000001,00000004,</category>" to 770 * blogger.newPost. 771 * 772 * @return false|array 773 */ 774 function xmlrpc_getpostcategories($content) 775 { 776 if( preg_match('~<category>(\d+\s*(,\s*\d*)*)</category>~i', $content, $match) ) 777 { 778 $cats = preg_split('~\s*,\s*~', $match[1], -1, PREG_SPLIT_NO_EMPTY); 779 foreach( $cats as $k => $v ) 780 { 781 $cats[$k] = (int)$v; 782 } 783 return $cats; 784 } 785 786 return false; 787 } 788 789 790 /* 791 * xmlrpc_removepostdata(-) 792 */ 793 function xmlrpc_removepostdata($content) 794 { 795 $content = preg_replace('/<title>(.*?)<\/title>/si', '', $content); 796 $content = preg_replace('/<category>(.*?)<\/category>/si', '', $content); 797 $content = trim($content); 798 return($content); 799 } 800 801 802 /** 803 * Echo the XML-RPC call Result and optionally log into file 804 * 805 * @param object XMLRPC response object 806 * @param boolean true to echo 807 * @param mixed File resource or == '' for no file logging. 808 */ 809 function xmlrpc_displayresult( $result, $display = true, $log = '' ) 810 { 811 if( ! $result ) 812 { // We got no response: 813 if( $display ) echo T_('No response!')."<br />\n"; 814 return false; 815 } 816 817 if( $result->faultCode() ) 818 { // We got a remote error: 819 if( $display ) echo T_('Remote error'), ': ', $result->faultString(), ' (', $result->faultCode(), ")<br />\n"; 820 debug_fwrite($log, $result->faultCode().' -- '.$result->faultString()); 821 return false; 822 } 823 824 // We'll display the response: 825 $val = $result->value(); 826 $value = xmlrpc_decode_recurse($result->value()); 827 828 if( is_array($value) ) 829 { 830 $out = ''; 831 foreach($value as $l_value) 832 { 833 $out .= ' ['.$l_value.'] '; 834 } 835 } 836 else 837 { 838 $out = $value; 839 } 840 841 debug_fwrite($log, $out); 842 843 if( $display ) echo T_('Response').': '.$out."<br />\n"; 844 845 return true; 846 } 847 848 849 /** 850 * Log the XML-RPC call Result into LOG object 851 * 852 * @param object XMLRPC response object 853 * @param Log object to add messages to 854 * @return boolean true = success, false = error 855 */ 856 function xmlrpc_logresult( $result, & $message_Log ) 857 { 858 if( ! $result ) 859 { // We got no response: 860 $message_Log->add( T_('No response!'), 'error' ); 861 return false; 862 } 863 864 if( $result->faultCode() ) 865 { // We got a remote error: 866 $message_Log->add( T_('Remote error').': '.$result->faultString().' ('.$result->faultCode().')', 'error' ); 867 return false; 868 } 869 870 // We got a response: 871 $val = $result->value(); 872 $value = xmlrpc_decode_recurse($result->value()); 873 874 if( is_array($value) ) 875 { 876 $out = ''; 877 foreach($value as $l_value) 878 { 879 $out .= ' ['.$l_value.'] '; 880 } 881 } 882 else 883 { 884 $out = $value; 885 } 886 887 $message_Log->add( T_('Response').': '.$out, 'success' ); 888 889 return true; 890 } 891 892 893 894 function debug_fopen($filename, $mode) { 895 global $debug; 896 if ($debug == 1 && ( !empty($filename) ) ) 897 { 898 $fp = fopen($filename, $mode); 899 return $fp; 900 } else { 901 return false; 902 } 903 } 904 905 function debug_fwrite($fp, $string) 906 { 907 global $debug; 908 if( $debug && $fp ) 909 { 910 fwrite($fp, $string); 911 } 912 } 913 914 function debug_fclose($fp) 915 { 916 global $debug; 917 if( $debug && $fp ) 918 { 919 fclose($fp); 920 } 921 } 922 923 924 925 /** 926 balanceTags 927 928 Balances Tags of string using a modified stack. 929 930 @param string Text to be balanced 931 @return string Returns balanced text 932 @author Leonard Lin (leonard@acm.org) 933 @version v1.1 934 @date November 4, 2001 935 @license GPL v2.0 936 @notes 937 @changelog 938 1.2 ***TODO*** Make better - change loop condition to $text 939 1.1 Fixed handling of append/stack pop order of end text 940 Added Cleaning Hooks 941 1.0 First Version 942 */ 943 function balanceTags($text) 944 { 945 $tagstack = array(); 946 $stacksize = 0; 947 $tagqueue = ''; 948 $newtext = ''; 949 950 # b2 bug fix for comments - in case you REALLY meant to type '< !--' 951 $text = str_replace('< !--', '< !--', $text); 952 953 # b2 bug fix for LOVE <3 (and other situations with '<' before a number) 954 $text = preg_replace('#<([0-9]{1})#', '<$1', $text); 955 956 while( preg_match('~<(\s*/?\w+)\s*([^>]*)>~', $text, $regex) ) 957 { 958 $newtext = $newtext . $tagqueue; 959 960 $i = strpos($text,$regex[0]); 961 $l = strlen($tagqueue) + strlen($regex[0]); 962 963 // clear the shifter 964 $tagqueue = ''; 965 966 // Pop or Push 967 if( substr($regex[1],0,1) == '/' ) 968 { // End Tag 969 $tag = strtolower(substr($regex[1],1)); 970 971 // if too many closing tags 972 if($stacksize <= 0) { 973 $tag = ''; 974 //or close to be safe $tag = '/' . $tag; 975 } 976 // if stacktop value = tag close value then pop 977 else if ($tagstack[$stacksize - 1] == $tag) { // found closing tag 978 $tag = '</' . $tag . '>'; // Close Tag 979 // Pop 980 array_pop ($tagstack); 981 $stacksize--; 982 } else { // closing tag not at top, search for it 983 for ($j=$stacksize-1;$j>=0;$j--) { 984 if ($tagstack[$j] == $tag) { 985 // add tag to tagqueue 986 for ($k=$stacksize-1;$k>=$j;$k--){ 987 $tagqueue .= '</' . array_pop ($tagstack) . '>'; 988 $stacksize--; 989 } 990 break; 991 } 992 } 993 $tag = ''; 994 } 995 } 996 else 997 { // Begin Tag 998 $tag = strtolower($regex[1]); 999 1000 // Tag Cleaning 1001 1002 // Push if not img or br or hr 1003 if($tag != 'br' && $tag != 'img' && $tag != 'hr' && $tag != 'param') 1004 { 1005 $stacksize = array_push ($tagstack, $tag); 1006 } 1007 1008 // Attributes 1009 // $attributes = $regex[2]; 1010 $attributes = $regex[2]; 1011 if($attributes) { 1012 $attributes = ' '.$attributes; 1013 } 1014 1015 $tag = '<'.$tag.$attributes.'>'; 1016 } 1017 1018 $newtext .= substr($text,0,$i) . $tag; 1019 $text = substr($text,$i+$l); 1020 } 1021 1022 // Clear Tag Queue 1023 $newtext = $newtext . $tagqueue; 1024 1025 // Add Remaining text 1026 $newtext .= $text; 1027 1028 // Empty Stack 1029 while($x = array_pop($tagstack)) { 1030 $newtext = $newtext . '</' . $x . '>'; // Add remaining tags to close 1031 } 1032 1033 # b2 fix for the bug with HTML comments 1034 $newtext = str_replace( '< !--', '<'.'!--', $newtext ); // the concatenation is needed to work around some strange parse error in PHP 4.3.1 1035 $newtext = str_replace( '< !--', '< !--', $newtext ); 1036 1037 return $newtext; 1038 } 1039 1040 1041 /** 1042 * Wrap pre tag around {@link var_dump()} for better debugging. 1043 * 1044 * @param $var__var__var__var__,... mixed variable(s) to dump 1045 */ 1046 function pre_dump( $var__var__var__var__ ) 1047 { 1048 global $is_cli; 1049 1050 #echo 'pre_dump(): '.debug_get_backtrace(); // see where a pre_dump() comes from 1051 1052 $func_num_args = func_num_args(); 1053 $count = 0; 1054 1055 if( ! empty($is_cli) ) 1056 { // CLI, no encoding of special chars: 1057 $count = 0; 1058 foreach( func_get_args() as $lvar ) 1059 { 1060 var_dump($lvar); 1061 1062 $count++; 1063 if( $count < $func_num_args ) 1064 { // Put newline between arguments 1065 echo "\n"; 1066 } 1067 } 1068 } 1069 elseif( function_exists('xdebug_var_dump') ) 1070 { // xdebug already does fancy displaying: 1071 1072 // no limits: 1073 $old_var_display_max_children = ini_set('xdebug.var_display_max_children', -1); // default: 128 1074 $old_var_display_max_data = ini_set('xdebug.var_display_max_data', -1); // max string length; default: 512 1075 $old_var_display_max_depth = ini_set('xdebug.var_display_max_depth', -1); // default: 3 1076 1077 echo "\n<div style=\"padding:1ex;border:1px solid #00f;\">\n"; 1078 foreach( func_get_args() as $lvar ) 1079 { 1080 xdebug_var_dump($lvar); 1081 1082 $count++; 1083 if( $count < $func_num_args ) 1084 { // Put HR between arguments 1085 echo "<hr />\n"; 1086 } 1087 } 1088 echo '</div>'; 1089 1090 // restore xdebug settings: 1091 ini_set('xdebug.var_display_max_children', $old_var_display_max_children); 1092 ini_set('xdebug.var_display_max_data', $old_var_display_max_data); 1093 ini_set('xdebug.var_display_max_depth', $old_var_display_max_depth); 1094 } 1095 else 1096 { 1097 $orig_html_errors = ini_set('html_errors', 0); // e.g. xdebug would use fancy html, if this is on; we catch (and use) xdebug explicitly above, but just in case 1098 1099 echo "\n<pre style=\"padding:1ex;border:1px solid #00f;\">\n"; 1100 foreach( func_get_args() as $lvar ) 1101 { 1102 ob_start(); 1103 var_dump($lvar); // includes "\n"; do not use var_export() because it does not detect recursion by design 1104 $buffer = ob_get_contents(); 1105 ob_end_clean(); 1106 echo htmlspecialchars($buffer); 1107 1108 $count++; 1109 if( $count < $func_num_args ) 1110 { // Put HR between arguments 1111 echo "<hr />\n"; 1112 } 1113 } 1114 echo "</pre>\n"; 1115 ini_set('html_errors', $orig_html_errors); 1116 } 1117 } 1118 1119 1120 /** 1121 * Get a function trace from {@link debug_backtrace()} as html table. 1122 * 1123 * Adopted from {@link http://us2.php.net/manual/de/function.debug-backtrace.php#47644}. 1124 * 1125 * @param integer|NULL Get the last x entries from the stack (after $ignore_from is applied). Anything non-numeric means "all". 1126 * @param array After a key/value pair matches a stack entry, this and the rest is ignored. 1127 * For example, array('class' => 'DB') would exclude everything after the stack 1128 * "enters" class DB and everything that got called afterwards. 1129 * You can also give an array of arrays which means that every condition in one of the given array must match. 1130 * @param integer Number of stack entries to include, after $ignore_from matches. 1131 * @return string HTML table 1132 */ 1133 function debug_get_backtrace( $limit_to_last = NULL, $ignore_from = array( 'function' => 'debug_get_backtrace' ), $offset_ignore_from = 0 ) 1134 { 1135 if( ! function_exists( 'debug_backtrace' ) ) // PHP 4.3.0 1136 { 1137 return 'Function debug_backtrace() is not available!'; 1138 } 1139 1140 $r = ''; 1141 1142 $backtrace = debug_backtrace(); 1143 $count_ignored = 0; // remember how many have been ignored 1144 $limited = false; // remember if we have limited to $limit_to_last 1145 1146 if( $ignore_from ) 1147 { // we want to ignore from a certain point 1148 $trace_length = 0; 1149 $break_because_of_offset = false; 1150 1151 for( $i = count($backtrace); $i > 0; $i-- ) 1152 { // Search the backtrace from behind (first call). 1153 $l_stack = & $backtrace[$i-1]; 1154 1155 if( $break_because_of_offset && $offset_ignore_from < 1 ) 1156 { // we've respected the offset, but need to break now 1157 break; // ignore from here 1158 } 1159 1160 foreach( $ignore_from as $l_ignore_key => $l_ignore_value ) 1161 { // Check if we want to ignore from here 1162 if( is_array($l_ignore_value) ) 1163 { // It's an array - all must match 1164 foreach( $l_ignore_value as $l_ignore_mult_key => $l_ignore_mult_val ) 1165 { 1166 if( !isset($l_stack[$l_ignore_mult_key]) /* not set with this stack entry */ 1167 || strcasecmp($l_stack[$l_ignore_mult_key], $l_ignore_mult_val) /* not this value (case-insensitive) */ ) 1168 { 1169 continue 2; // next ignore setting, because not all match. 1170 } 1171 } 1172 if( $offset_ignore_from-- > 0 ) 1173 { 1174 $break_because_of_offset = true; 1175 break; 1176 } 1177 break 2; // ignore from here 1178 } 1179 elseif( isset($l_stack[$l_ignore_key]) 1180 && !strcasecmp($l_stack[$l_ignore_key], $l_ignore_value) /* is equal case-insensitive */ ) 1181 { 1182 if( $offset_ignore_from-- > 0 ) 1183 { 1184 $break_because_of_offset = true; 1185 break; 1186 } 1187 break 2; // ignore from here 1188 } 1189 } 1190 $trace_length++; 1191 } 1192 1193 $count_ignored = count($backtrace) - $trace_length; 1194 1195 $backtrace = array_slice( $backtrace, 0-$trace_length ); // cut off ignored ones 1196 } 1197 1198 $count_backtrace = count($backtrace); 1199 if( is_numeric($limit_to_last) && $limit_to_last < $count_backtrace ) 1200 { // we want to limit to a maximum number 1201 $limited = true; 1202 $backtrace = array_slice( $backtrace, 0, $limit_to_last ); 1203 $count_backtrace = $limit_to_last; 1204 } 1205 1206 $r .= '<div style="padding:1ex; margin-bottom:1ex; text-align:left; color:#000; background-color:#ddf;"> 1207 <h3>Backtrace:</h3>'."\n"; 1208 if( $count_backtrace ) 1209 { 1210 $r .= '<ol style="font-family:monospace;">'; 1211 1212 $i = 0; 1213 foreach( $backtrace as $l_trace ) 1214 { 1215 if( ++$i == $count_backtrace ) 1216 { 1217 $r .= '<li style="padding:0.5ex 0;">'; 1218 } 1219 else 1220 { 1221 $r .= '<li style="padding:0.5ex 0; border-bottom:1px solid #77d;">'; 1222 } 1223 $args = array(); 1224 if( isset($l_trace['args']) && is_array( $l_trace['args'] ) ) 1225 { // Prepare args: 1226 foreach( $l_trace['args'] as $l_arg ) 1227 { 1228 $l_arg_type = gettype($l_arg); 1229 switch( $l_arg_type ) 1230 { 1231 case 'integer': 1232 case 'double': 1233 $args[] = $l_arg; 1234 break; 1235 case 'string': 1236 $args[] = '"'.htmlspecialchars(str_replace("\n", '', substr($l_arg, 0, 64))).((strlen($l_arg) > 64) ? '...' : '').'"'; 1237 break; 1238 case 'array': 1239 $args[] = 'Array('.count($l_arg).')'; 1240 break; 1241 case 'object': 1242 $args[] = 'Object('.get_class($l_arg).')'; 1243 break; 1244 case 'resource': 1245 $args[] = 'Resource('.strstr($l_arg, '#').')'; 1246 break; 1247 case 'boolean': 1248 $args[] = $l_arg ? 'true' : 'false'; 1249 break; 1250 default: 1251 $args[] = $l_arg_type; 1252 } 1253 } 1254 } 1255 1256 $call = "<strong>\n"; 1257 if( isset($l_trace['class']) ) 1258 { 1259 $call .= $l_trace['class']; 1260 } 1261 if( isset($l_trace['type']) ) 1262 { 1263 $call .= $l_trace['type']; 1264 } 1265 $call .= $l_trace['function']."( </strong>\n"; 1266 if( $args ) 1267 { 1268 $call .= ' '.implode( ', ', $args ).' '; 1269 } 1270 $call .='<strong>)</strong>'; 1271 1272 $r .= $call."<br />\n"; 1273 1274 $r .= '<strong>'; 1275 if( isset($l_trace['file']) ) 1276 { 1277 $r .= "File: </strong> ".$l_trace['file']; 1278 } 1279 else 1280 { 1281 $r .= '[runtime created function]</strong>'; 1282 } 1283 if( isset($l_trace['line']) ) 1284 { 1285 $r .= ' on line '.$l_trace['line']; 1286 } 1287 1288 $r .= "</li>\n"; 1289 } 1290 $r .= '</ol>'; 1291 } 1292 else 1293 { 1294 $r .= '<p>No backtrace available.</p>'; 1295 } 1296 1297 // Extra notes, might be to much, but explains why we stopped at some point. Feel free to comment it out or remove it. 1298 $notes = array(); 1299 if( $count_ignored ) 1300 { 1301 $notes[] = 'Ignored last: '.$count_ignored; 1302 } 1303 if( $limited ) 1304 { 1305 $notes[] = 'Limited to'.( $count_ignored ? ' remaining' : '' ).': '.$limit_to_last; 1306 } 1307 if( $notes ) 1308 { 1309 $r .= '<p class="small">'.implode( ' - ', $notes ).'</p>'; 1310 } 1311 1312 $r .= "</div>\n"; 1313 1314 return $r; 1315 } 1316 1317 1318 /** 1319 * Outputs Unexpected Error message. When in debug mode it also prints a backtrace. 1320 * 1321 * This should be used instead of die() everywhere. 1322 * This should NOT be used instead of exit() anywhere. 1323 * Dying means the application has encontered and unexpected situation, 1324 * i-e: something that should never occur during normal operation. 1325 * Examples: database broken, user changed URL by hand... 1326 * 1327 * @param string Message to output 1328 */ 1329 function debug_die( $additional_info = '' ) 1330 { 1331 global $debug, $baseurl; 1332 global $log_app_errors, $app_name, $is_cli; 1333 1334 // Attempt to output an error header (will not work if the output buffer has already flushed once): 1335 // This should help preventing indexing robots from indexing the error :P 1336 if( ! headers_sent() ) 1337 { 1338 global $io_charset; 1339 header('Content-type: text/html; charset='.$io_charset); // it's ok, if a previous header would be replaced; 1340 header('HTTP/1.0 500 Internal Server Error'); 1341 } 1342 1343 if( $is_cli ) 1344 { // Command line interface, e.g. in cron_exec.php: 1345 echo '== '.T_('An unexpected error has occured!')." ==\n"; 1346 echo T_('If this error persits, please report it to the administrator.')."\n"; 1347 echo T_('Additional information about this error:')."\n"; 1348 echo strip_tags( $additional_info ); 1349 } 1350 else 1351 { 1352 echo '<div style="background-color: #fdd; padding: 1ex; margin-bottom: 1ex;">'; 1353 echo '<h3 style="color:#f00;">'.T_('An unexpected error has occured!').'</h3>'; 1354 echo '<p>'.T_('If this error persits, please report it to the administrator.').'</p>'; 1355 echo '<p><a href="'.$baseurl.'">'.T_('Go back to home page').'</a></p>'; 1356 echo '</div>'; 1357 1358 if( ! empty( $additional_info ) ) 1359 { 1360 echo '<div style="background-color: #ddd; padding: 1ex; margin-bottom: 1ex;">'; 1361 echo '<h3>'.T_('Additional information about this error:').'</h3>'; 1362 echo $additional_info; 1363 echo '</div>'; 1364 } 1365 } 1366 1367 if( $log_app_errors > 1 || $debug ) 1368 { // Prepare backtrace 1369 $backtrace = debug_get_backtrace(); 1370 1371 if( $log_app_errors > 1 || $is_cli ) 1372 { 1373 $backtrace_cli = trim(strip_tags($backtrace)); 1374 } 1375 } 1376 1377 if( $log_app_errors ) 1378 { // Log error through PHP's logging facilities: 1379 $log_message = $app_name.' error: '; 1380 if( ! empty($additional_info) ) 1381 { 1382 $log_message .= trim( strip_tags($additional_info) ); 1383 } 1384 else 1385 { 1386 $log_message .= 'No info specified in debug_die()'; 1387 } 1388 1389 // Get file and line info: 1390 $file = 'Unknown'; 1391 $line = 'Unknown'; 1392 if( function_exists('debug_backtrace') /* PHP 4.3 */ ) 1393 { // get the file and line 1394 foreach( debug_backtrace() as $v ) 1395 { 1396 if( isset($v['function']) && $v['function'] == 'debug_die' ) 1397 { 1398 $file = isset($v['file']) ? $v['file'] : 'Unknown'; 1399 $line = isset($v['line']) ? $v['line'] : 'Unknown'; 1400 break; 1401 } 1402 } 1403 } 1404 $log_message .= ' in '.$file.' at line '.$line; 1405 1406 if( $log_app_errors > 1 ) 1407 { // Append backtrace: 1408 // indent after newlines: 1409 $backtrace_cli = preg_replace( '~(\S)(\n)(\S)~', '$1 $2$3', $backtrace_cli ); 1410 $log_message .= "\nBacktrace:\n".$backtrace_cli; 1411 } 1412 $log_message .= "\nREQUEST_URI: ".( isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '-' ); 1413 $log_message .= "\nHTTP_REFERER: ".( isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '-' ); 1414 1415 error_log( str_replace("\n", ' / ', $log_message), 0 /* PHP's system logger */ ); 1416 } 1417 1418 1419 // DEBUG OUTPUT: 1420 if( $debug ) 1421 { 1422 if( $is_cli ) 1423 echo $backtrace_cli; 1424 else 1425 echo $backtrace; 1426 1427 debug_info(); 1428 } 1429 1430 // EXIT: 1431 if( ! $is_cli ) 1432 { // Attempt to keep the html valid (but it doesn't really matter anyway) 1433 echo '</body></html>'; 1434 } 1435 die(); 1436 } 1437 1438 1439 /** 1440 * Outputs Bad request Error message. When in debug mode it also prints a backtrace. 1441 * 1442 * This should be used when a bad user input is detected? 1443 * 1444 * @param string Message to output 1445 */ 1446 function bad_request_die( $additional_info = '' ) 1447 { 1448 global $debug, $baseurl; 1449 1450 // Attempt to output an error header (will not work if the output buffer has already flushed once): 1451 // This should help preventing indexing robots from indexing the error :P 1452 if( ! headers_sent() ) 1453 { 1454 global $io_charset; 1455 header('Content-type: text/html; charset='.$io_charset); // it's ok, if a previous header would be replaced; 1456 header('HTTP/1.0 400 Bad Request'); 1457 } 1458 1459 echo '<div style="background-color: #fdd; padding: 1ex; margin-bottom: 1ex;">'; 1460 echo '<h3 style="color:#f00;">'.T_('Bad Request!').'</h3>'; 1461 echo '<p>'.T_('The parameters of your request are invalid.').'</p>'; 1462 echo '<p>'.T_('If you have obtained this error by clicking on a link INSIDE of this site, please report the bad link to the administrator.').'</p>'; 1463 echo '<p><a href="'.$baseurl.'">'.T_('Go back to home page').'</a></p>'; 1464 echo '</div>'; 1465 1466 if( !empty( $additional_info ) ) 1467 { 1468 echo '<div style="background-color: #ddd; padding: 1ex; margin-bottom: 1ex;">'; 1469 echo '<h3>'.T_('Additional information about this error:').'</h3>'; 1470 echo $additional_info; 1471 echo '</div>'; 1472 } 1473 1474 if( $debug ) 1475 { 1476 echo debug_get_backtrace(); 1477 debug_info(); 1478 } 1479 1480 // Attempt to keep the html valid (but it doesn't really matter anyway) 1481 die( '</body></html>' ); 1482 } 1483 1484 1485 /** 1486 * Outputs debug info, according to {@link $debug} or $force param. This gets called typically at the end of the page. 1487 * 1488 * @todo dh> add get_debug_info() which returns and supports non-html format (for debug_die()) 1489 * @param boolean true to force output regardless of {@link $debug} 1490 */ 1491 function debug_info( $force = false ) 1492 { 1493 global $debug, $Debuglog, $DB, $obhandler_debug, $Timer, $ReqHost, $ReqPath; 1494 global $cache_imgsize, $cache_File; 1495 global $Session; 1496 global $db_config, $tableprefix; 1497 1498 if( ! $debug && ! $force ) 1499 { // No debug output: 1500 return; 1501 } 1502 1503 $ReqHostPathQuery = $ReqHost.$ReqPath.( empty( $_SERVER['QUERY_STRING'] ) ? '' : '?'.$_SERVER['QUERY_STRING'] ); 1504 echo '<div class="debug"><h2>Debug info</h2>'; 1505 1506 $Debuglog->add( 'Len of serialized $cache_imgsize: '.strlen(serialize($cache_imgsize)), 'memory' ); 1507 $Debuglog->add( 'Len of serialized $cache_File: '.strlen(serialize($cache_File)), 'memory' ); 1508 1509 if( !$obhandler_debug ) 1510 { // don't display changing items when we want to test obhandler 1511 1512 // Timer table: 1513 $time_page = $Timer->get_duration( 'total' ); 1514 $timer_rows = array(); 1515 foreach( $Timer->get_categories() as $l_cat ) 1516 { 1517 if( $l_cat == 'sql_query' ) 1518 { 1519 continue; 1520 } 1521 $timer_rows[ $l_cat ] = $Timer->get_duration( $l_cat ); 1522 } 1523 // arsort( $timer_rows ); 1524 ksort( $timer_rows ); 1525 echo '<table><thead>' 1526 .'<tr><th colspan="4" class="center">Timers</th></tr>' 1527 .'<tr><th>Category</th><th>Time</th><th>%</th><th>Count</th></tr>' 1528 .'</thead><tbody>'; 1529 1530 $table_rows_ignore_perhaps = array(); 1531 foreach( $timer_rows as $l_cat => $l_time ) 1532 { 1533 $percent_l_cat = $time_page > 0 ? number_format( 100/$time_page * $l_time, 2 ) : '0'; 1534 1535 $row = "\n<tr>" 1536 .'<td>'.$l_cat.'</td>' 1537 .'<td class="right">'.$l_time.'</td>' 1538 .'<td class="right">'.$percent_l_cat.'%</td>' 1539 .'<td class="right">'.$Timer->get_count( $l_cat ).'</td></tr>'; 1540 1541 if( $l_time < 0.005 ) 1542 { 1543 $table_rows_ignore_perhaps[] = $row; 1544 } 1545 else 1546 { 1547 echo $row; 1548 } 1549 } 1550 $count_ignored = count($table_rows_ignore_perhaps); 1551 if( $count_ignored > 5 ) 1552 { 1553 echo '<tr><td colspan="4" class="center"> + '.$count_ignored.' < 0.005s </td></tr>'; 1554 } 1555 else 1556 { 1557 echo implode( "\n", $table_rows_ignore_perhaps ); 1558 } 1559 echo '</tbody>'; 1560 echo '</table>'; 1561 1562 if( isset($DB) ) 1563 { 1564 echo '<a href="'.$ReqHostPathQuery.'#evo_debug_queries">Database queries: '.$DB->num_queries.'.</a><br />'; 1565 } 1566 1567 foreach( array( // note: 8MB is default for memory_limit and is reported as 8388608 bytes 1568 'memory_get_usage' => array( 'display' => 'Memory usage', 'high' => 8000000 ), 1569 'memory_get_peak_usage' /* PHP 5.2 */ => array( 'display' => 'Memory peak usage', 'high' => 8000000 ) ) as $l_func => $l_var ) 1570 { 1571 if( function_exists( $l_func ) ) 1572 { 1573 $_usage = $l_func(); 1574 if( $_usage > $l_var['high'] ) echo '<span style="color:red; font-weight:bold">'; 1575 echo $l_var['display'].': '.bytesreadable( $_usage ); 1576 if( $_usage > $l_var['high'] ) echo '</span>'; 1577 echo '<br />'; 1578 } 1579 } 1580 } 1581 1582 1583 // DEBUGLOG(s) FROM PREVIOUS SESSIONS, after REDIRECT(s) (with list of categories at top): 1584 if( isset($Session) && ($sess_Debuglogs = $Session->get('Debuglogs')) && ! empty($sess_Debuglogs) ) 1585 { 1586 $count_sess_Debuglogs = count($sess_Debuglogs); 1587 if( $count_sess_Debuglogs > 1 ) 1588 { // Links to those Debuglogs: 1589 echo '<p>There are '.$count_sess_Debuglogs.' Debuglogs from redirected pages: '; 1590 for( $i = 1; $i <= $count_sess_Debuglogs; $i++ ) 1591 { 1592 echo '<a href="'.$ReqHostPathQuery.'#debug_sess_debuglog_'.$i.'">#'.$i.'</a> '; 1593 } 1594 echo '</p>'; 1595 } 1596 1597 foreach( $sess_Debuglogs as $k => $sess_Debuglog ) 1598 { 1599 $log_categories = array( 'error', 'note', 'all' ); // Categories to output (in that order) 1600 $log_cats = array_keys($sess_Debuglog->get_messages( $log_categories )); // the real list (with all replaced and only existing ones) 1601 $log_container_head = '<h3 id="debug_sess_debuglog_'.($k+1).'" style="color:#f00;">Debug messages from redirected page (#'.($k+1).')</h3>' 1602 // link to real Debuglog: 1603 .'<p><a href="'.$ReqHostPathQuery.'#debug_debuglog">See below for the Debuglog from the current request.</a></p>'; 1604 $log_head_links = array(); 1605 foreach( $log_cats as $l_cat ) 1606 { 1607 $log_head_links[] .= '<a href="'.$ReqHostPathQuery.'#debug_redir_'.($k+1).'_info_cat_'.str_replace( ' ', '_', $l_cat ).'">'.$l_cat.'</a>'; 1608 } 1609 $log_container_head .= implode( ' | ', $log_head_links ); 1610 echo format_to_output( 1611 $sess_Debuglog->display( array( 1612 'container' => array( 'string' => $log_container_head, 'template' => false ), 1613 'all' => array( 'string' => '<h4 id="debug_redir_'.($k+1).'_info_cat_%s">%s:</h4>', 'template' => false ) ), 1614 '', false, $log_categories ), 1615 'htmlbody' ); 1616 } 1617 1618 $Session->delete( 'Debuglogs' ); 1619 } 1620 1621 1622 // CURRENT DEBUGLOG (with list of categories at top): 1623 $log_categories = array( 'error', 'note', 'all' ); // Categories to output (in that order) 1624 $log_cats = array_keys($Debuglog->get_messages( $log_categories )); // the real list (with all replaced and only existing ones) 1625 $log_container_head = '<h3 id="debug_debuglog">Debug messages</h3>'; 1626 if( ! empty($sess_Debuglogs) ) 1627 { // link to first sess_Debuglog: 1628 $log_container_head .= '<p><a href="'.$ReqHostPathQuery.'#debug_sess_debuglog_1">See above for the Debuglog(s) from before the redirect.</a></p>'; 1629 } 1630 $log_head_links = array(); 1631 foreach( $log_cats as $l_cat ) 1632 { 1633 $log_head_links[] .= '<a href="'.$ReqHostPathQuery.'#debug_info_cat_'.str_replace( ' ', '_', $l_cat ).'">'.$l_cat.'</a>'; 1634 } 1635 $log_container_head .= implode( ' | ', $log_head_links ); 1636 echo format_to_output( 1637 $Debuglog->display( array( 1638 'container' => array( 'string' => $log_container_head, 'template' => false ), 1639 'all' => array( 'string' => '<h4 id="debug_info_cat_%s">%s:</h4>', 'template' => false ) ), 1640 '', false, $log_categories ), 1641 'htmlbody' ); 1642 1643 1644 echo '<h3 id="evo_debug_queries">DB</h3>'; 1645 1646 if($db_config) 1647 { 1648 echo '<pre>', 1649 T_('DB Username').': '.$db_config['user']."\n". 1650 T_('DB Database').': '.$db_config['name']."\n". 1651 T_('DB Host').': '.$db_config['host']."\n". 1652 T_('DB tables prefix').': '.$tableprefix."\n". 1653 T_('DB connection charset').': '.$db_config['connection_charset']."\n". 1654 '</pre>'; 1655 } 1656 1657 if( !isset($DB) ) 1658 { 1659 echo 'No DB object.'; 1660 } 1661 else 1662 { 1663 $DB->dump_queries(); 1664 } 1665 echo '</div>'; 1666 } 1667 1668 1669 /** 1670 * Sends a mail, wrapping PHP's mail() function. 1671 * 1672 * {@link $current_locale} will be used to set the charset. 1673 * 1674 * Note: we use a single \n as line ending, though it does not comply to 1675 * {@link http://www.faqs.org/rfcs/rfc2822 RFC2822}, but seems to be safer, 1676 * because some mail transfer agents replace \n by \r\n automatically. 1677 * 1678 * @todo Unit testing with "nice addresses" This gets broken over and over again. 1679 * 1680 * @param string Recipient, either email only or in "Name <example@example.com>" format (RFC2822). 1681 * Can be multiple comma-separated addresses. 1682 * @param string Subject of the mail 1683 * @param string The message text 1684 * @param string From address, being added to headers (we'll prevent injections); 1685 * see {@link http://securephp.damonkohler.com/index.php/Email_Injection}. 1686 * Might be just an email address or of the same form as {@link $to}. 1687 * {@link $notify_from} gets used as default (if NULL). 1688 * @param array Additional headers ( headername => value ). Take care of injection! 1689 * @return boolean True if mail could be sent (not necessarily delivered!), false if not - (return value of {@link mail()}) 1690 */ 1691 function send_mail( $to, $subject, $message, $from = NULL, $headers = array() ) 1692 { 1693 global $debug, $app_name, $app_version, $current_locale, $current_charset, $evo_charset, $locales, $Debuglog, $notify_from; 1694 1695 $NL = "\n"; 1696 1697 if( is_windows() ) 1698 { // fplanque: Windows XP, Apache 1.3, PHP 4.4, MS SMTP : will not accept "nice" addresses. 1699 $to = preg_replace( '/^.*?<(.+?)>$/', '$1', $to ); 1700 $from = preg_replace( '/^.*?<(.+?)>$/', '$1', $from ); 1701 } 1702 1703 // echo 'sending email to: ['.htmlspecialchars($to).'] from ['.htmlspecialchars($from).']'; 1704 1705 if( !is_array( $headers ) ) 1706 { // Make sure $headers is an array 1707 $headers = array( $headers ); 1708 } 1709 1710 // Specify charset and content-type of email 1711 $headers['Content-Type'] = 'text/plain; charset='.$current_charset; 1712 $headers['X-Mailer'] = $app_name.' '.$app_version.' - PHP/'.phpversion(); 1713 $headers['X-Remote-Addr'] = implode( ',', get_ip_list() ); 1714 1715 // -- Build headers ---- 1716 if( empty($from) ) 1717 { 1718 $from = $notify_from; 1719 } 1720 else 1721 { 1722 $from = trim($from); 1723 } 1724 1725 if( ! empty($from) ) 1726 { // From has to go into headers 1727 $from_save = preg_replace( '~(\r|\n).*$~s', '', $from ); // Prevent injection! (remove everything after (and including) \n or \r) 1728 1729 if( $from != $from_save ) 1730 { 1731 if( strpos( $from_save, '<' ) !== false && !strpos( $from_save, '>' ) ) 1732 { // We have probably stripped the '>' at the end! 1733 $from_save .= '>'; 1734 } 1735 1736 // Add X-b2evo notification mail header about this 1737 $headers['X-b2evo'] = 'Fixed email header injection (From)'; 1738 $Debuglog->add( 'Detected email injection! Fixed «'.htmlspecialchars($from).'» to «'.htmlspecialchars($from_save).'».', 'security' ); 1739 1740 $from = $from_save; 1741 } 1742 1743 $headers['From'] = $from; 1744 } 1745 1746 $headerstring = ''; 1747 reset( $headers ); 1748 while( list( $lKey, $lValue ) = each( $headers ) ) 1749 { // Add additional headers 1750 $headerstring .= $lKey.': '.$lValue.$NL; 1751 } 1752 1753 if( function_exists('mb_encode_mimeheader') ) 1754 { // encode subject 1755 $subject = mb_encode_mimeheader( $subject, mb_internal_encoding(), 'B', $NL ); 1756 } 1757 1758 $message = str_replace( array( "\r\n", "\r" ), $NL, $message ); 1759 1760 // Convert encoding of message (from internal encoding to the one of the message): 1761 $message = convert_charset( $message, $current_charset, $evo_charset ); 1762 1763 if( $debug > 1 ) 1764 { // We agree to die for debugging... 1765 if( ! mail( $to, $subject, $message, $headerstring ) ) 1766 { 1767 debug_die( 'Sending mail from «'.htmlspecialchars($from).'» to «'.htmlspecialchars($to).'», Subject «'.htmlspecialchars($subject).'» FAILED.' ); 1768 } 1769 } 1770 else 1771 { // Soft debugging only.... 1772 if( ! @mail( $to, $subject, $message, $headerstring ) ) 1773 { 1774 $Debuglog->add( 'Sending mail from «'.htmlspecialchars($from).'» to «'.htmlspecialchars($to).'», Subject «'.htmlspecialchars($subject).'» FAILED.', 'error' ); 1775 return false; 1776 } 1777 } 1778 1779 $Debuglog->add( 'Sent mail from «'.htmlspecialchars($from).'» to «'.htmlspecialchars($to).'», Subject «'.htmlspecialchars($subject).'».' ); 1780 return true; 1781 } 1782 1783 1784 1785 /** 1786 * If first parameter evaluates to true printf() gets called using the first parameter 1787 * as args and the second parameter as print-pattern 1788 * 1789 * @param mixed variable to test and output if it's true or $disp_none is given 1790 * @param string printf-pattern to use (%s gets replaced by $var) 1791 * @param string printf-pattern to use, if $var is numeric and > 1 (%s gets replaced by $var) 1792 * @param string printf-pattern to use if $var evaluates to false (%s gets replaced by $var) 1793 */ 1794 function disp_cond( $var, $disp_one, $disp_more = NULL, $disp_none = NULL ) 1795 { 1796 if( is_numeric($var) && $var > 1 ) 1797 { 1798 printf( ( $disp_more === NULL ? $disp_one : $disp_more ), $var ); 1799 return true; 1800 } 1801 elseif( $var ) 1802 { 1803 printf( $disp_one, $var ); 1804 return true; 1805 } 1806 else 1807 { 1808 if( $disp_none !== NULL ) 1809 { 1810 printf( $disp_none, $var ); 1811 return false; 1812 } 1813 } 1814 } 1815 1816 1817 /** 1818 * Create IMG tag for an action icon. 1819 * 1820 * @param string TITLE text (IMG and A link) 1821 * @param string icon code for {@link get_icon()} 1822 * @param string URL where the icon gets linked to (empty to not wrap the icon in a link) 1823 * @param string word to be displayed after icon 1824 * @param integer 1-5: weight of the icon. the icon will be displayed only if its weight is >= than the user setting threshold 1825 * Use 5, if it's a required icon - all others could get disabled by the user. 1826 * @param integer 1-5: weight of the word. the word will be displayed only if its weight is >= than the user setting threshold 1827 * @param array Additional attributes to the A tag. The values must be properly encoded for html output (e.g. quotes). 1828 * It may also contain these params: 1829 * - 'use_js_popup': if true, the link gets opened as JS popup. You must also pass an "id" attribute for this! 1830 * - 'use_js_size': use this to override the default popup size ("500, 400") 1831 * - 'class': defaults to 'action_icon', if not set; use "" to not use it 1832 * @return string The generated action icon link. 1833 */ 1834 function action_icon( $title, $icon, $url, $word = NULL, $icon_weight = 4, $word_weight = 1, $link_attribs = array() ) 1835 { 1836 global $UserSettings; 1837 1838 $link_attribs['href'] = $url; 1839 $link_attribs['title'] = $title; 1840 1841 if( ! isset($link_attribs['class']) ) 1842 { 1843 $link_attribs['class'] = 'action_icon'; 1844 } 1845 1846 if( get_icon( $icon, 'rollover' ) ) 1847 { 1848 if( empty($link_attribs['class']) ) 1849 { 1850 $link_attribs['class'] = 'rollover'; 1851 } 1852 else 1853 { 1854 $link_attribs['class'] .= ' rollover'; 1855 } 1856 } 1857 1858 // "use_js_popup": open link in a JS popup 1859 if( ! empty($link_attribs['use_js_popup']) ) 1860 { 1861 $popup_js = 'var win = new PopupWindow(); win.setUrl( \''.$link_attribs['href'].'\' ); win.setSize( ); '; 1862 1863 if( isset($link_attribs['use_js_size']) ) 1864 { 1865 if( ! empty($link_attribs['use_js_size']) ) 1866 { 1867 $popup_size = $link_attribs['use_js_size']; 1868 } 1869 } 1870 else 1871 { 1872 $popup_size = '500, 400'; 1873 } 1874 if( isset($popup_size) ) 1875 { 1876 $popup_js .= 'win.setSize( '.$popup_size.' ); '; 1877 } 1878 $popup_js .= 'win.showPopup(\''.$link_attribs['id'].'\'); return false;'; 1879 1880 if( empty( $link_attribs['onclick'] ) ) 1881 { 1882 $link_attribs['onclick'] = $popup_js; 1883 } 1884 else 1885 { 1886 $link_attribs['onclick'] .= $popup_js; 1887 } 1888 unset($link_attribs['use_js_popup']); 1889 unset($link_attribs['use_js_size']); 1890 } 1891 1892 // NOTE: We do not use format_to_output with get_field_attribs_as_string() here, because it interferes with the Results class (eval() fails on entitied quotes..) (blueyed) 1893 $r = '<a '.get_field_attribs_as_string( $link_attribs, false ).'>'; 1894 1895 $display_icon = ($icon_weight >= $UserSettings->get('action_icon_threshold')); 1896 $display_word = ($word_weight >= $UserSettings->get('action_word_threshold')); 1897 1898 if( $display_icon || ! $display_word ) 1899 { // We MUST display an action icon in order to make the user happy: 1900 // OR we default to icon because the user doesn't want the word either!! 1901 1902 $r .= get_icon( $icon, 'imgtag', array( 'title'=>$title ), true ); 1903 } 1904 1905 if( $display_word ) 1906 { // We MUST display an action word in order to make the user happy: 1907 1908 if( $display_icon ) 1909 { // We already have an icon, display a SHORT word: 1910 if( !empty($word) ) 1911 { // We have provided a short word: 1912 $r .= $word; 1913 } 1914 else 1915 { // We fall back to alt: 1916 $r .= get_icon( $icon, 'legend' ); 1917 } 1918 } 1919 else 1920 { // No icon display, let's display a LONG word/text: 1921 $r .= trim( $title, ' .!' ); 1922 } 1923 } 1924 1925 $r .= '</a>'; 1926 1927 return $r; 1928 } 1929 1930 1931 /** 1932 * Get properties of an icon. 1933 * 1934 * Note: to get a file type icon, use {@link File::get_icon()} instead. 1935 * 1936 * @uses get_icon_info() 1937 * @param string icon for what? (key) 1938 * @param string what to return for that icon ('imgtag', 'alt', 'legend', 'file', 'url', 'size' {@link imgsize()}) 1939 * @param array additional params ( 1940 * 'class' => class name when getting 'imgtag', 1941 * 'size' => param for 'size', 1942 * 'title' => title attribute for 'imgtag') 1943 * @param boolean true to include this icon into the legend at the bottom of the page (works for 'imgtag' only) 1944 */ 1945 function get_icon( $iconKey, $what = 'imgtag', $params = NULL, $include_in_legend = false ) 1946 { 1947 global $basepath, $admin_subdir, $baseurl, $Debuglog, $IconLegend, $use_strict; 1948 global $conf_path; 1949 1950 if( ! function_exists('get_icon_info') ) 1951 { 1952 require_once $conf_path.'_icons.php'; 1953 } 1954 1955 $icon = get_icon_info($iconKey); 1956 if( ! $icon || ! isset( $icon['file'] ) ) 1957 { 1958 return '[no image defined for '.var_export( $iconKey, true ).'!]'; 1959 } 1960 1961 switch( $what ) 1962 { 1963 case 'rollover': 1964 if( isset( $icon['rollover'] ) ) 1965 { // Image has rollover available 1966 return $icon['rollover']; 1967 } 1968 return false; 1969 /* BREAK */ 1970 1971 1972 case 'file': 1973 return $basepath.$icon['file']; 1974 /* BREAK */ 1975 1976 1977 case 'alt': 1978 if( isset( $icon['alt'] ) ) 1979 { // alt tag from $map_iconfiles 1980 return $icon['alt']; 1981 } 1982 else 1983 { // fallback to $iconKey as alt-tag 1984 return $iconKey; 1985 } 1986 /* BREAK */ 1987 1988 1989 case 'legend': 1990 if( isset( $icon['legend'] ) ) 1991 { // legend tag from $map_iconfiles 1992 return $icon['legend']; 1993 } 1994 else 1995 if( isset( $icon['alt'] ) ) 1996 { // alt tag from $map_iconfiles 1997 return $icon['alt']; 1998 } 1999 else 2000 { // fallback to $iconKey as alt-tag 2001 return $iconKey; 2002 } 2003 /* BREAK */ 2004 2005 2006 case 'class': 2007 if( isset($icon['class']) ) 2008 { 2009 return $icon['class']; 2010 } 2011 else 2012 { 2013 return ''; 2014 } 2015 /* BREAK */ 2016 2017 case 'url': 2018 return $baseurl.$icon['file']; 2019 /* BREAK */ 2020 2021 case 'size': 2022 if( !isset( $icon['size'] ) ) 2023 { 2024 $Debuglog->add( 'No iconsize for ['.$iconKey.']', 'icons' ); 2025 2026 $icon['size'] = imgsize( $icon['file'] ); 2027 } 2028 2029 switch( $params['size'] ) 2030 { 2031 case 'width': 2032 return $icon['size'][0]; 2033 2034 case 'height': 2035 return $icon['size'][1]; 2036 2037 case 'widthxheight': 2038 return $icon['size'][0].'x'.$icon['size'][1]; 2039 2040 case 'width': 2041 return $icon['size'][0]; 2042 2043 case 'string': 2044 return 'width="'.$icon['size'][0].'" height="'.$icon['size'][1].'"'; 2045 2046 default: 2047 return $icon['size']; 2048 } 2049 /* BREAK */ 2050 2051 2052 case 'imgtag': 2053 $r = '<img src="'.$baseurl.$icon['file'].'" '; 2054 2055 if( !$use_strict ) 2056 { // Include non CSS fallbacks - transitional only: 2057 $r .= 'border="0" align="top" '; 2058 } 2059 2060 // Include class (will default to "icon"): 2061 if( ! isset( $params['class'] ) ) 2062 { 2063 if( isset($icon['class']) ) 2064 { // This icon has a class 2065 $params['class'] = $icon['class']; 2066 } 2067 else 2068 { 2069 $params['class'] = ''; 2070 } 2071 } 2072 2073 // Include size (optional): 2074 if( isset( $icon['size'] ) ) 2075 { 2076 $r .= 'width="'.$icon['size'][0].'" height="'.$icon['size'][1].'" '; 2077 } 2078 2079 // Include alt (XHTML mandatory): 2080 if( ! isset( $params['alt'] ) ) 2081 { 2082 if( isset( $icon['alt'] ) ) 2083 { // alt-tag from $map_iconfiles 2084 $params['alt'] = $icon['alt']; 2085 } 2086 else 2087 { // $iconKey as alt-tag 2088 $params['alt'] = $iconKey; 2089 } 2090 } 2091 2092 // Add all the attributes: 2093 $r .= get_field_attribs_as_string( $params, false ); 2094 2095 // Close tag: 2096 $r .= '/>'; 2097 2098 2099 if( $include_in_legend && isset( $IconLegend ) ) 2100 { // This icon should be included into the legend: 2101 $IconLegend->add_icon( $iconKey ); 2102 } 2103 2104 return $r; 2105 /* BREAK */ 2106 2107 case 'noimg': 2108 $blank_icon = get_icon_info('pixel'); 2109 2110 $r = '<img src="'.$baseurl.$blank_icon['file'].'" '; 2111 2112 // Include non CSS fallbacks: 2113 $r .= 'border="0" align="top" '; 2114 2115 // Include class (will default to "noicon"): 2116 if( ! isset( $params['class'] ) ) 2117 { 2118 if( isset($icon['class']) ) 2119 { // This icon has a class 2120 $params['class'] = $icon['class']; 2121 } 2122 else 2123 { 2124 $params['class'] = 'no_icon'; 2125 } 2126 } 2127 2128 // Include size (optional): 2129 if( isset( $icon['size'] ) ) 2130 { 2131 $r .= 'width="'.$icon['size'][0].'" height="'.$icon['size'][1].'" '; 2132 } 2133 2134 // Include alt (XHTML mandatory): 2135 if( ! isset( $params['alt'] ) ) 2136 { 2137 $params['alt'] = ''; 2138 } 2139 2140 // Add all the attributes: 2141 $r .= get_field_attribs_as_string( $params, false ); 2142 2143 // Close tag: 2144 $r .= '/>'; 2145 2146 return $r; 2147 /* BREAK */ 2148 2149 } 2150 } 2151 2152 2153 /** 2154 * @param string date (YYYY-MM-DD) 2155 * @param string time 2156 */ 2157 function form_date( $date, $time = '' ) 2158 { 2159 return substr( $date.' ', 0, 10 ).' '.$time; 2160 } 2161 2162 2163 /** 2164 * Displays an empty or a full bullet based on boolean 2165 * 2166 * @param boolean true for full bullet, false for empty bullet 2167 */ 2168 function bullet( $bool ) 2169 { 2170 if( $bool ) 2171 return get_icon( 'bullet_full', 'imgtag' ); 2172 2173 return get_icon( 'bullet_empty', 'imgtag' ); 2174 } 2175 2176 2177 /** 2178 * Get list of client IP addresses from REMOTE_ADDR and HTTP_X_FORWARDED_FOR, 2179 * in this order. '' is used when no IP could be found. 2180 * 2181 * @param boolean True, to get only the first IP (probably REMOTE_ADDR) 2182 * @return array|string Depends on first param. 2183 */ 2184 function get_ip_list( $firstOnly = false ) 2185 { 2186 $r = array(); 2187 2188 if( !empty( $_SERVER['REMOTE_ADDR'] ) ) 2189 { 2190 foreach( explode( ',', $_SERVER['REMOTE_ADDR'] ) as $l_ip ) 2191 { 2192 $l_ip = trim($l_ip); 2193 if( !empty($l_ip) ) 2194 { 2195 $r[] = $l_ip; 2196 } 2197 } 2198 } 2199 2200 if( !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ) 2201 { // IP(s) behind Proxy - this can be easily forged! 2202 foreach( explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] ) as $l_ip ) 2203 { 2204 $l_ip = trim($l_ip); 2205 if( !empty($l_ip) && $l_ip != 'unknown' ) 2206 { 2207 $r[] = $l_ip; 2208 } 2209 } 2210 } 2211 2212 if( !isset( $r[0] ) ) 2213 { // No IP found. 2214 $r[] = ''; 2215 } 2216 2217 return $firstOnly ? $r[0] : $r; 2218 } 2219 2220 2221 /** 2222 * Get the base domain (without protocol and any subdomain) of an URL. 2223 * 2224 * Gets a max of 3 domain parts (x.y.tld) 2225 * 2226 * @param string URL 2227 * @return string the base domain (may become empty, if found invalid) 2228 */ 2229 function get_base_domain( $url ) 2230 { 2231 //echo '<p>'.$url; 2232 // Chop away the http part and the path: 2233 $domain = preg_replace( '~^([a-z]+://)?([^:/#]+)(.*)$~i', '\\2', $url ); 2234 2235 if( empty($domain) || preg_match( '~^([0-9]+\.)+$~', $domain ) ) 2236 { // Empty or All numeric = IP address, don't try to cut it any further 2237 return $domain; 2238 } 2239 2240 //echo '<br>'.$domain; 2241 2242 // Get the base domain up to 3 levels (x.y.tld): 2243 // NOTE: "_" is not really valid, but for Windows it is.. 2244 // TODO: dh> this should also handle IDN "raw" domains with umlauts.. 2245 // NOTE: \w does not always match umlauts.. it's based on setlocale().. (fp> hum, it was too good to be real :P) 2246 // NOTE: \w includes "_" 2247 if( ! preg_match( '~ ( \w (\w|-|_)* \. ){0,2} \w (\w|-|_)* $~ix', $domain, $matches ) ) 2248 { 2249 return ''; 2250 } 2251 $base_domain = $matches[0]; 2252 2253 // Remove any www*. prefix: 2254 $base_domain = preg_replace( '~^(www (\w|-|_)* \. )~xi', '', $base_domain ); 2255 2256 //echo '<br>'.$base_domain.'</p>'; 2257 2258 return $base_domain; 2259 } 2260 2261 2262 /** 2263 * Generate a valid key of size $length. 2264 * 2265 * @param integer length of key 2266 * @param string chars to use in generated key 2267 * @return string key 2268 */ 2269 function generate_random_key( $length = 32, $keychars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' ) 2270 { 2271 $key = ''; 2272 $rnd_max = strlen($keychars) - 1; 2273 2274 for( $i = 0; $i < $length; $i++ ) 2275 { 2276 $key .= $keychars{mt_rand(0, $rnd_max)}; // get a random character out of $keychars 2277 } 2278 2279 return $key; 2280 } 2281 2282 2283 /** 2284 * Generate a random password with no ambiguous chars 2285 * 2286 * @param integer length of password 2287 * @return string password 2288 */ 2289 function generate_random_passwd( $length = 8 ) 2290 { 2291 // fp> NOTE: do not include any characters that would make autogenerated passwords ambiguous 2292 // 1 (one) vs l (L) vs I (i) 2293 // O (letter) vs 0 (digit) 2294 2295 return generate_random_key( $length, 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789' ); 2296 } 2297 2298 2299 /** 2300 * Sends HTTP headers to avoid caching of the page. 2301 */ 2302 function header_nocache() 2303 { 2304 header('Expires: Tue, 25 Mar 2003 05:00:00 GMT'); 2305 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT'); 2306 header('Cache-Control: no-cache, must-revalidate'); 2307 header('Pragma: no-cache'); 2308 } 2309 2310 2311 /** 2312 * Sends HTTP header to redirect to the previous location (which 2313 * can be given as function parameter, GET parameter (redirect_to), 2314 * is taken from {@link Hit::$referer} or {@link $baseurl}). 2315 * 2316 * {@link $Debuglog} and {@link $Messages} get stored in {@link $Session}, so they 2317 * are available after the redirect. 2318 * 2319 * NOTE: This function {@link exit() exits} the php script execution. 2320 * 2321 * @todo fp> do NOT allow $redirect_to = NULL. This leads to spaghetti code and unpredictable behavior. 2322 * 2323 * @param string URL to redirect to (overrides detection) 2324 * @param boolean is this a permanent redirect? if true, send a 301; otherwise a 303 2325 */ 2326 function header_redirect( $redirect_to = NULL, $permanent = false ) 2327 { 2328 global $Hit, $baseurl, $Blog, $htsrv_url_sensitive; 2329 global $Session, $Debuglog, $Messages; 2330 2331 // fp> get this out 2332 if( empty($redirect_to) ) 2333 { // see if there's a redirect_to request param given (where & is encoded as &): 2334 $redirect_to = param( 'redirect_to', 'string', '' ); 2335 2336 if( empty($redirect_to) ) 2337 { 2338 if( ! empty($Hit->referer) ) 2339 { 2340 $redirect_to = $Hit->referer; 2341 } 2342 elseif( isset($Blog) && is_object($Blog) ) 2343 { 2344 $redirect_to = $Blog->get('url'); 2345 } 2346 else 2347 { 2348 $redirect_to = $baseurl; 2349 } 2350 } 2351 } 2352 // <fp 2353 2354 /* fp>why do we need this? 2355 if( substr($redirect_to, 0, 1) == '/' ) 2356 { // relative URL, prepend current host: 2357 global $ReqHost; 2358 2359 $redirect_to = $ReqHost.$redirect_to; 2360 } 2361 */ 2362 2363 if( strpos($redirect_to, $htsrv_url_sensitive) === 0 /* we're going somewhere on $htsrv_url_sensitive */ 2364 || strpos($redirect_to, $baseurl) === 0 /* we're going somewhere on $baseurl */ ) 2365 { 2366 // Remove login and pwd parameters from URL, so that they do not trigger the login screen again: 2367 // Also remove "action" get param to avoid unwanted actions 2368 // blueyed> Removed the removing of "action" here, as it is used to trigger certain views. Instead, "confirm(ed)?" gets removed now 2369 // fp> which views please (important to list in order to remove asap) 2370 // dh> sorry, don't remember 2371 // TODO: fp> action should actually not be used to trigger views. This should be changed at some point. 2372 $redirect_to = preg_replace( '~(?<=\?|&) (login|pwd|confirm(ed)?) = [^&]+ ~x', '', $redirect_to ); 2373 } 2374 2375 2376 $status = $permanent ? 301 : 303; 2377 $Debuglog->add('Redirecting to '.$redirect_to.' (status '.$status.')'); 2378 2379 // Transfer of Debuglog to next page: 2380 if( $Debuglog->count('all') ) 2381 { // Save Debuglog into Session, so that it's available after redirect (gets loaded by Session constructor): 2382 $sess_Debuglogs = $Session->get('Debuglogs'); 2383 if( empty($sess_Debuglogs) ) 2384 $sess_Debuglogs = array(); 2385 2386 $sess_Debuglogs[] = $Debuglog; 2387 $Session->set( 'Debuglogs', $sess_Debuglogs, 60 /* expire in 60 seconds */ ); 2388 } 2389 2390 // Transfer of Messages to next page: 2391 if( $Messages->count('all') ) 2392 { // Set Messages into user's session, so they get restored on the next page (after redirect): 2393 $Session->set( 'Messages', $Messages ); 2394 } 2395 2396 $Session->dbsave(); // If we don't save now, we run the risk that the redirect goes faster than the PHP script shutdown. 2397 2398 // see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 2399 if( $permanent ) 2400 { // This should be a permanent move redirect! 2401 header( 'HTTP/1.1 301 Moved Permanently' ); 2402 } 2403 else 2404 { // This should be a "follow up" redirect 2405 // Note: Also see http://de3.php.net/manual/en/function.header.php#50588 and the other comments around 2406 header( 'HTTP/1.1 303 See Other' ); 2407 } 2408 2409 header( 'Location: '.$redirect_to, true, $status ); // explictly setting the status is required for (fast)cgi 2410 exit(); 2411 } 2412 2413 2414 function is_create_action( $action ) 2415 { 2416 $action_parts = explode( '_', $action ); 2417 2418 switch( $action_parts[0] ) 2419 { 2420 case 'new': 2421 case 'new_switchtab': 2422 case 'copy': 2423 case 'create': // we return in this state after a validation error 2424 return true; 2425 2426 case 'edit': 2427 case 'edit_switchtab': 2428 case 'update': // we return in this state after a validation error 2429 case 'delete': 2430 // The following one's a bit far fetched, but can happen if we have no sheet display: 2431 case 'unlink': 2432 case 'view': 2433 return false; 2434 2435 default: 2436 debug_die( 'Unhandled action in form: '.strip_tags($action_parts[0]) ); 2437 } 2438 } 2439 2440 2441 /** 2442 * Generate a link that toggles display of an element on clicking. 2443 * 2444 * @todo Provide functionality to make those links accessible without JS (using GET parameter) 2445 * @uses toggle_display_by_id() (JS) 2446 * @param string ID (html) of the link 2447 * @param string ID (html) of the target to toggle displaying 2448 * @return string 2449 */ 2450 function get_link_showhide( $link_id, $target_id, $text_when_displayed, $text_when_hidden, $display_hidden = true ) 2451 { 2452 $html = "<a id='$link_id' href='#' onclick='return toggle_display_by_id(\"$link_id\", \"$target_id\", \"" 2453 .jsspecialchars( $text_when_displayed ).'", "'.jsspecialchars( $text_when_hidden ).'")\'>' 2454 .( $display_hidden ? $text_when_hidden : $text_when_displayed ) 2455 .'</a>'; 2456 2457 return $html; 2458 } 2459 2460 2461 /** 2462 * Escape a string to be used in Javascript. 2463 * 2464 * @param string 2465 * @return string 2466 */ 2467 function jsspecialchars($s) 2468 { 2469 $r = str_replace( 2470 array( '\\', '"', "'" ), 2471 array( '\\\\', '\"', "\'" ), 2472 $s ); 2473 return htmlspecialchars($r, ENT_QUOTES); 2474 } 2475 2476 2477 /** 2478 * Compact a date in a number keeping only integer value of the string 2479 * 2480 * @param string date 2481 */ 2482 function compact_date( $date ) 2483 { 2484 return preg_replace( '#[^0-9]#', '', $date ); 2485 } 2486 2487 2488 /** 2489 * Decompact a date in a date format ( Y-m-d h:m:s ) 2490 * 2491 * @param string date 2492 */ 2493 function decompact_date( $date ) 2494 { 2495 $date0 = $date; 2496 2497 return substr($date0,0,4).'-'.substr($date0,4,2).'-'.substr($date0,6,2).' ' 2498 .substr($date0,8,2).':'.substr($date0,10,2).':'.substr($date0,12,2); 2499 } 2500 2501 /** 2502 * Check the format of the phone number param and 2503 * format it in a french number if it is. 2504 * 2505 * @param string phone number 2506 */ 2507 function format_phone( $phone, $hide_country_dialing_code_if_same_as_locale = true ) 2508 { 2509 global $CountryCache; 2510 2511 $dialing_code = NULL; 2512 2513 if( substr( $phone, 0, 1 ) == '+' ) 2514 { // We have a dialing code in the phone, so we extract it: 2515 $dialing_code = $CountryCache->extract_country_dialing_code( substr( $phone, 1 ) ); 2516 } 2517 2518 if( !is_null( $dialing_code ) && ( locale_dialing_code() == $dialing_code ) 2519 && $hide_country_dialing_code_if_same_as_locale ) 2520 { // The phone dialing code is same as locale and we want to hide it in this case 2521 if( ( strlen( $phone ) - strlen( $dialing_code ) ) == 10 ) 2522 { // We can format it like a french phone number ( 0x.xx.xx.xx.xx ) 2523 $phone_formated = format_french_phone( '0'.substr( $phone, strlen( $dialing_code )+1 ) ); 2524 } 2525 else 2526 { // ( 0xxxxxxxxxxxxxx ) 2527 $phone_formated = '0'.substr( $phone, strlen( $dialing_code )+1 ); 2528 } 2529 2530 } 2531 elseif( !is_null( $dialing_code ) ) 2532 { // Phone has a dialing code 2533 if( ( strlen( $phone ) - strlen( $dialing_code ) ) == 10 ) 2534 { // We can format it like a french phone number with the dialing code ( +dialing x.xx.xx.xx.xx ) 2535 $phone_formated = '+'.$dialing_code.format_french_phone( ' '.substr( $phone, strlen( $dialing_code )+1 ) ); 2536 } 2537 else 2538 { // ( +dialing xxxxxxxxxxx ) 2539 $phone_formated = '+'.$dialing_code.' '.substr( $phone, strlen( $dialing_code )+1 ); 2540 } 2541 } 2542 else 2543 { 2544 if( strlen( $phone ) == 10 ) 2545 { // We can format it like a french phone number ( xx.xx.xx.xx.xx ) 2546 $phone_formated = format_french_phone( $phone ); 2547 } 2548 else 2549 { // We don't format phone: TODO generic format phone ( xxxxxxxxxxxxxxxx ) 2550 $phone_formated = $phone; 2551 } 2552 } 2553 2554 return $phone_formated; 2555 } 2556 2557 2558 /** 2559 * Format a string in a french phone number 2560 * 2561 * @param string phone number 2562 */ 2563 function format_french_phone( $phone ) 2564 { 2565 return substr($phone, 0 , 2).'.'.substr($phone, 2, 2).'.'.substr($phone, 4, 2) 2566 .'.'.substr($phone, 6, 2).'.'.substr($phone, 8, 2); 2567 } 2568 2569 2570 /** 2571 * Generate a link to a online help resource. 2572 * testing the concept of online help (aka webhelp). 2573 * this function should be relocated somewhere better if it is taken onboard by the project 2574 * 2575 * @todo replace [?] with icon, 2576 * @todo write url suffix dynamically based on topic and language 2577 * 2578 * QUESTION: launch new window with javascript maybe? 2579 * @param string Topic 2580 * The topic should be in a format like [\w]+(/[\w]+)*, e.g features/online_help. 2581 * @return string 2582 */ 2583 function get_manual_link( $topic ) 2584 { 2585 global $Settings, $current_locale, $app_shortname, $app_version; 2586 2587 if( $Settings->get('webhelp_enabled') ) 2588 { 2589 $manual_url = 'http://manual.b2evolution.net/redirect/'.str_replace(" ","_",strtolower($topic)).'?lang='.$current_locale.'&app='.$app_shortname.'&version='.$app_version; 2590 2591 $webhelp_link = action_icon( T_('Open relevant page in online manual'), 'manual', $manual_url, T_('Manual'), 5, 1, array( 'target' => '_blank' ) ); 2592 2593 return ' '.$webhelp_link; 2594 } 2595 else 2596 { 2597 return ''; 2598 } 2599 } 2600 2601 2602 /** 2603 * Build a string out of $field_attribs, with each attribute 2604 * prefixed by a space character. 2605 * 2606 * @param array Array of field attributes. 2607 * @param boolean Use format_to_output() for the attributes? 2608 * @return string 2609 */ 2610 function get_field_attribs_as_string( $field_attribs, $format_to_output = true ) 2611 { 2612 $r = ''; 2613 2614 if( empty( $field_attribs ) ) 2615 { // TODO: This extra check should not be needed, if $field_attribs is an array! (blueyed) 2616 return $r; 2617 } 2618 2619 foreach( $field_attribs as $l_attr => $l_value ) 2620 { 2621 if( $l_value === '' || $l_value === NULL ) 2622 { // don't generate empty attributes (it may be NULL if we pass 'value' => NULL as field_param for example, because isset() does not match it!) 2623 continue; 2624 } 2625 2626 if( $format_to_output ) 2627 { 2628 if( $l_attr == 'value' ) 2629 { 2630 $r .= ' '.$l_attr.'="'.format_to_output( $l_value, 'formvalue' ).'"'; 2631 } 2632 else 2633 { 2634 // TODO: this uses strip_tags et al! Shouldn't we just use "formvalue" always? (E.g. it kills "for( var i=0; i<a; i++ )..." (in an onclick attr) from "<a" on. The workaround is to use spaces ("i < a"), but I was confused first) 2635 $r .= ' '.$l_attr.'="'.format_to_output( $l_value, 'htmlattr' ).'"'; 2636 } 2637 } 2638 else 2639 { 2640 $r .= ' '.$l_attr.'="'.$l_value.'"'; 2641 } 2642 } 2643 2644 return $r; 2645 } 2646 2647 2648 /** 2649 * Is the current page an admin/backoffice page? 2650 * 2651 * @return boolean 2652 */ 2653 function is_admin_page() 2654 { 2655 global $is_admin_page; 2656 2657 return isset($is_admin_page) && $is_admin_page === true; // check for type also, because of register_globals! 2658 } 2659 2660 2661 /** 2662 * Implode array( 'x', 'y', 'z' ) to something like 'x, y and z'. Useful for displaying list to the end user. 2663 * 2664 * If there's one element in the table, it is returned. 2665 * If there are at least two elements, the last one is concatenated using $implode_last, while the ones before are imploded using $implode_by. 2666 * 2667 * @todo dh> I don't think using entities/HTML as default for $implode_last is sane! 2668 * Use "&" instead and make sure that the output for HTML is HTML compliant.. 2669 * @todo Support for locales that have a different kind of enumeration?! 2670 * @return string 2671 */ 2672 function implode_with_and( $arr, $implode_by = ', ', $implode_last = ' & ' ) 2673 { 2674 switch( count($arr) ) 2675 { 2676 case 0: 2677 return ''; 2678 2679 case 1: 2680 $r = array_shift($arr); 2681 return $r; 2682 2683 default: 2684 $r = implode( $implode_by, array_slice( $arr, 0, -1 ) ) 2685 .$implode_last.array_pop( $arr ); 2686 return $r; 2687 } 2688 } 2689 2690 2691 /** 2692 * Returns a "<base />" tag and remembers that we've used it ({@link regenerate_url()} needs this). 2693 * 2694 * @param string URL to use (this gets used as base URL for all relative links on the HTML page) 2695 * @return string 2696 */ 2697 function base_tag( $url, $target = NULL ) 2698 { 2699 global $base_tag_set; 2700 2701 $base_tag_set = true; 2702 echo '<base href="'.$url.'"'; 2703 2704 if( !empty($target) ) 2705 { 2706 echo ' target="'.$target.'"'; 2707 } 2708 echo ' />'; 2709 } 2710 2711 2712 /** 2713 * Display an array as a list: 2714 * 2715 * @param array 2716 * @param string 2717 * @param string 2718 * @param string 2719 * @param string 2720 * @param string 2721 */ 2722 function display_list( $items, $list_start = '<ul>', $list_end = '</ul>', $item_separator = '', 2723 $item_start = '<li>', $item_end = '</li>', $force_hash = NULL ) 2724 { 2725 if( !empty( $items ) ) 2726 { 2727 echo $list_start; 2728 $first = true; 2729 foreach( $items as $item ) 2730 { // For each list item: 2731 $link = resolve_link_params( $item, $force_hash ); 2732 if( empty( $link ) ) 2733 { 2734 continue; 2735 } 2736 2737 if( $first ) 2738 { 2739 $first = false; 2740 } 2741 else 2742 { 2743 echo $item_separator; 2744 } 2745 echo $item_start.$link.$item_end; 2746 } 2747 echo $list_end; 2748 } 2749 } 2750 2751 function display_param_link( $params ) 2752 { 2753 echo resolve_link_params( $params ); 2754 } 2755 2756 /** 2757 * Resolve a link based on params 2758 * 2759 * @param array 2760 * @return string 2761 */ 2762 function resolve_link_params( $item, $force_hash = NULL ) 2763 { 2764 global $current_locale; 2765 2766 // echo 'resolve link '; 2767 2768 if( is_array( $item ) ) 2769 { 2770 if( isset( $item[0] ) ) 2771 { // Older format, which displays the same thing for all locales: 2772 return generate_link_from_params( $item ); 2773 } 2774 else 2775 { // First get the right locale: 2776 // echo $current_locale; 2777 foreach( $item as $l_locale => $loc_item ) 2778 { 2779 if( $l_locale == substr( $current_locale, 0, strlen( $l_locale) ) ) 2780 { // We found a matching locale: 2781 2782 if( is_array( $loc_item[0] ) ) 2783 { // Randomize: 2784 $loc_item = hash_link_params( $loc_item, $force_hash ); 2785 } 2786 2787 return generate_link_from_params( $loc_item ); 2788 } 2789 } 2790 // No match found! 2791 return ''; 2792 } 2793 } 2794 2795 // Super old format: 2796 return $item; 2797 } 2798 2799 2800 /** 2801 * Get a link line, based url hash combined with probability percentage in first column 2802 * 2803 * @param array of arrays 2804 */ 2805 function hash_link_params( $link_array, $force_hash = NULL ) 2806 { 2807 global $ReqHost, $ReqPath; 2808 2809 static $hash; 2810 2811 if( !is_null($force_hash) ) 2812 { 2813 $hash = $force_hash; 2814 } 2815 elseif( !isset($hash) ) 2816 { 2817 $key = $ReqHost.$ReqPath; 2818 $hash = 0; 2819 for( $i=0; $i<strlen($key); $i++ ) 2820 { 2821 $hash += ord($key[$i]); 2822 } 2823 $hash = $hash % 100 + 1; 2824 2825 // $hash = rand( 1, 100 ); 2826 global $debug, $Debuglog; 2827 if( $debug ) 2828 { 2829 $Debuglog->add( 'Hash key: '.$hash, 'vars' ); 2830 } 2831 } 2832 // echo "[$hash] "; 2833 2834 foreach( $link_array as $link_params ) 2835 { 2836 // echo '<br>'.$hash.'-'.$link_params[ 0 ]; 2837 if( $hash <= $link_params[ 0 ] ) 2838 { // select this link! 2839 // pre_dump( $link_params ); 2840 array_shift( $link_params ); 2841 return $link_params; 2842 } 2843 } 2844 // somehow no match, return 1st element: 2845 $link_params = $link_array[0]; 2846 array_shift( $link_params ); 2847 return $link_params; 2848 } 2849 2850 2851 /** 2852 * Generate a link from params 2853 */ 2854 function generate_link_from_params( $link_params ) 2855 { 2856 $url = $link_params[0]; 2857 $text = $link_params[1]; 2858 2859 if( is_array($text) ) 2860 { 2861 $text = hash_link_params( $text ); 2862 $text = $text[0]; 2863 } 2864 2865 return '<a href="'.$url.'">'.$text.'</a>'; 2866 } 2867 2868 2869 /** 2870 * Try to make $url relative to $target_url, if scheme, host, user and pass matches. 2871 * 2872 * This is useful for redirect_to params, to keep them short and avoid mod_security 2873 * rejecting the request as "Not Acceptable" (whole URL as param). 2874 * 2875 * @param string URL to handle 2876 * @param string URL where we want to make $url relative to 2877 * @return string 2878 */ 2879 function url_rel_to_same_host( $url, $target_url ) 2880 { 2881 $parsed_url = @parse_url( $url ); 2882 if( ! $parsed_url ) 2883 { // invalid url 2884 return $url; 2885 } 2886 if( empty($parsed_url['scheme']) || empty($parsed_url['host']) ) 2887 { // no protocol or host information 2888 return $url; 2889 } 2890 2891 $target_url = @parse_url( $target_url ); 2892 if( ! $target_url ) 2893 { // invalid url 2894 return $url; 2895 } 2896 if( ! empty($target_url['scheme']) && $target_url['scheme'] != $parsed_url['scheme'] ) 2897 { // scheme/protocol is different 2898 return $url; 2899 } 2900 if( ! empty($target_url['host']) ) 2901 { 2902 if( empty($target_url['scheme']) || $target_url['host'] != $parsed_url['host'] ) 2903 { // target has no scheme (but a host) or hosts differ 2904 return $url; 2905 } 2906 2907 if( @$target_url['port'] != @$parsed_url['port'] ) 2908 return $url; 2909 if( @$target_url['user'] != @$parsed_url['user'] ) 2910 return $url; 2911 if( @$target_url['pass'] != @$parsed_url['pass'] ) 2912 return $url; 2913 } 2914 2915 // We can make the URL relative: 2916 $r = ''; 2917 if( ! empty($parsed_url['path']) ) 2918 $r .= $parsed_url['path']; 2919 if( ! empty($parsed_url['query']) ) 2920 $r .= '?'.$parsed_url['query']; 2921 if( ! empty($parsed_url['fragment']) ) 2922 $r .= '?'.$parsed_url['fragment']; 2923 2924 return $r; 2925 } 2926 2927 2928 /** 2929 * Make an $url absolute according to $host, if it is not absolute yet. 2930 * 2931 * @param string URL 2932 * @param string Host (including protocol, e.g. 'http://example.com'); defaults to {@link $ReqHost} 2933 * @return string 2934 */ 2935 function url_absolute( $url, $host = NULL ) 2936 { 2937 if( preg_match( '~^(\w+:)?//~', $url ) ) 2938 { // URL is relative already ("//foo/bar" is absolute - leaving the protocol out): 2939 return $url; 2940 } 2941 2942 if( empty($host) ) 2943 { 2944 global $ReqHost; 2945 $host = $ReqHost; 2946 } 2947 return $host.$url; 2948 } 2949 2950 2951 /** 2952 * Make links in $s absolute. 2953 * 2954 * It searches for "src" and "href" HTML tag attributes and makes the absolute. 2955 * 2956 * @uses url_absolute() 2957 * @param string content 2958 * @param string Hostname including scheme, e.g. http://example.com; defaults to $ReqHost 2959 * @return string 2960 */ 2961 function make_rel_links_abs( $s, $host = NULL ) 2962 { 2963 if( empty($host) ) 2964 { 2965 global $ReqHost; 2966 $host = $ReqHost; 2967 } 2968 2969 $s = preg_replace_callback( '~(<[^>]+?)\b((?:src|href)\s*=\s*)(["\'])?([^\\3]+?)(\\3)~i', create_function( '$m', ' 2970 return $m[1].$m[2].$m[3].url_absolute($m[4], "'.$host.'").$m[5];' ), $s ); 2971 return $s; 2972 } 2973 2974 2975 /* 2976 * $Log: _misc.funcs.php,v $ 2977 * Revision 1.7 2007/11/03 21:04:25 fplanque 2978 * skin cleanup 2979 * 2980 * Revision 1.6 2007/10/25 18:29:41 blueyed 2981 * PasteFromBranch: Fixed url_absolute for '//foo/bar' 2982 * 2983 * Revision 1.5 2007/09/22 19:23:56 fplanque 2984 * various fixes & enhancements 2985 * 2986 * Revision 1.4 2007/09/12 21:00:30 fplanque 2987 * UI improvements 2988 * 2989 * Revision 1.3 2007/09/08 18:38:08 fplanque 2990 * MFB 2991 * 2992 * Revision 1.2 2007/09/04 14:56:20 fplanque 2993 * antispam cleanup 2994 * 2995 * Revision 1.1 2007/06/25 10:58:52 fplanque 2996 * MODULES (refactored MVC) 2997 * 2998 * Revision 1.180 2007/06/19 23:15:08 blueyed 2999 * doc fixes 3000 * 3001 * Revision 1.179 2007/06/16 19:54:39 blueyed 3002 * doc/(-fixes) 3003 * 3004 * Revision 1.178 2007/06/05 17:00:02 blueyed 3005 * MFB v-1-10: Consistent logging of HTTP_REFERER/REQUEST_URI; fixed possible E_NOTICE 3006 * 3007 * Revision 1.177 2007/05/23 23:07:53 blueyed 3008 * Display DB info (user, database, host, tableprefix, charset) in debug_info() 3009 * 3010 * Revision 1.176 2007/05/13 20:49:34 fplanque 3011 * removed hack that did inconsistent action upon the HTML and the Security checkers. 3012 * 3013 * Revision 1.175 2007/05/13 18:31:23 blueyed 3014 * Trim special date param replacements in date_i18n(). Fixes http://forums.b2evolution.net/viewtopic.php?p=55213#55213. 3015 * 3016 * Revision 1.174 2007/05/12 10:13:25 yabs 3017 * secuirty checker uses setting for allowing id/style in comments 3018 * amended get_icon to respect $use_strict 3019 * 3020 * Revision 1.173 2007/04/26 00:11:08 fplanque 3021 * (c) 2007 3022 * 3023 * Revision 1.172 2007/04/25 18:47:42 fplanque 3024 * MFB 1.10: groovy links 3025 * 3026 * Revision 1.171 2007/04/19 20:36:42 blueyed 3027 * Fixed possible E_NOTICE with REQUEST_URI/cron 3028 * 3029 * Revision 1.170 2007/02/16 11:23:02 blueyed 3030 * No limits for xdebug_var_dump() in pre_dump() 3031 * 3032 * Revision 1.169 2007/02/11 02:10:35 blueyed 3033 * Minor improvements to debug_get_backtrace() 3034 * 3035 * Revision 1.168 2007/02/06 13:47:25 blueyed 3036 * Fixed escaping in get_link_showhide(); added jsspecialchars(); see http://forums.b2evolution.net/viewtopic.php?p=50564 3037 * 3038 * Revision 1.167 2007/01/29 09:58:55 fplanque 3039 * enhanced toolbar - experimental 3040 * 3041 * Revision 1.166 2007/01/29 09:24:41 fplanque 3042 * icon stuff 3043 * 3044 * Revision 1.165 2007/01/26 20:43:03 blueyed 3045 * Fixed exp. app error logging 3046 * 3047 * Revision 1.164 2007/01/26 04:52:53 fplanque 3048 * clean comment popups (skins 2.0) 3049 * 3050 * Revision 1.163 2007/01/24 01:32:30 fplanque 3051 * 'zip & css' is more readable than 'zip and css' 3052 * 3053 * Revision 1.162 2007/01/23 22:23:04 fplanque 3054 * FIXED (!!!) disappearing help window! 3055 * 3056 * Revision 1.161 2007/01/23 21:44:43 fplanque 3057 * handle generic "empty"/noimg icons 3058 * 3059 * Revision 1.160 2007/01/23 05:30:21 fplanque 3060 * "Contact the owner" 3061 * 3062 * Revision 1.159 2007/01/20 00:38:19 blueyed 3063 * Added Debulog entry in header_redirect() 3064 * 3065 * Revision 1.158 2007/01/19 03:06:57 fplanque 3066 * Changed many little thinsg in the login procedure. 3067 * There may be new bugs, sorry. I tested this for several hours though. 3068 * More refactoring to be done. 3069 * 3070 * Revision 1.157 2007/01/14 05:41:10 blueyed 3071 * Send correct charset with bad_request_die() 3072 * 3073 * Revision 1.156 2006/12/19 17:21:54 blueyed 3074 * Fixed domain extraction if anchor (#) follows domain name directly. See http://forums.b2evolution.net/viewtopic.php?p=48672#48672 3075 * 3076 * Revision 1.155 2006/12/14 00:42:04 fplanque 3077 * A little bit of windows detection / normalization 3078 * 3079 * Revision 1.154 2006/12/12 23:23:30 fplanque 3080 * finished post editing v2.0 3081 * 3082 * Revision 1.153 2006/12/05 06:22:24 blueyed 3083 * Fixed blogger.newPost to accept a list of categories, as given by w.bloggar 3084 * 3085 * Revision 1.152 2006/12/02 17:48:31 blueyed 3086 * Fixed regexp for balanceTags(), so it does not handle HTML comments as tags 3087 * 3088 * Revision 1.151 2006/12/02 17:32:26 blueyed 3089 * Missing "global $Messages" in format_to_post() 3090 * 3091 * Revision 1.150 2006/11/29 20:48:46 blueyed 3092 * Moved url_rel_to_same_host() from _misc.funcs.php to _url.funcs.php 3093 * 3094 * Revision 1.149 2006/11/29 20:17:23 blueyed 3095 * Refactored generate_random_passwd() 3096 * 3097 * Revision 1.148 2006/11/28 02:52:26 fplanque 3098 * doc 3099 * 3100 * Revision 1.147 2006/11/26 02:30:39 fplanque 3101 * doc / todo 3102 * 3103 * Revision 1.146 2006/11/24 18:27:27 blueyed 3104 * Fixed link to b2evo CVS browsing interface in file docblocks 3105 * 3106 * Revision 1.145 2006/11/24 18:06:02 blueyed 3107 * Handle saving of $Messages centrally in header_redirect() 3108 * 3109 * Revision 1.144 2006/11/23 23:20:11 blueyed 3110 * get_base_domain(): added test, whcih currently fails + notes 3111 * 3112 * Revision 1.143 2006/11/23 02:29:00 fplanque 3113 * fixed IDN and more IDN 3114 * 3115 * Revision 1.142 2006/11/22 18:47:59 blueyed 3116 * Fixed get_base_domain() again some more; added tests (2 failing ones) 3117 * 3118 * Revision 1.141 2006/11/22 01:19:34 fplanque 3119 * contact the admin feature 3120 * 3121 * Revision 1.140 2006/11/22 00:24:38 blueyed 3122 * Fixed get_base_domain() for empty URL/domain 3123 * 3124 * Revision 1.139 2006/11/21 19:18:40 fplanque 3125 * get_base_domain() / get_ban_domain() may need more unit tests, especially about what to do when invalid URLs are passed. 3126 * 3127 * Revision 1.138 2006/11/19 23:43:05 blueyed 3128 * Optimized icon and $IconLegend handling 3129 * 3130 * Revision 1.137 2006/11/14 22:10:41 blueyed 3131 * Removed own bloat in debug_die(), it is an error after all. doc 3132 * 3133 * Revision 1.136 2006/11/14 21:57:19 blueyed 3134 * Store an array of Debuglog entries in the session, not just the last one - because we may redirect more than once, e.g. when redirecting to canonical url, after having posted a comment. 3135 * 3136 * Revision 1.135 2006/11/06 19:43:27 blueyed 3137 * *** empty log message *** 3138 * 3139 * Revision 1.134 2006/11/05 20:13:57 fplanque 3140 * minor 3141 * 3142 * Revision 1.133 2006/11/02 15:57:47 blueyed 3143 * Fixes for pre_dump() and add_icon() 3144 * 3145 * Revision 1.132 2006/11/02 01:34:43 blueyed 3146 * doc/QUESTION 3147 * 3148 * Revision 1.131 2006/11/01 19:04:35 blueyed 3149 * re-added htmlspecialchars() to pre_dump() and made it CLI aware 3150 * 3151 * Revision 1.130 2006/10/31 01:17:56 blueyed 3152 * Send contenttype/CHARSET in debug_die() if not done so before 3153 * 3154 * Revision 1.129 2006/10/31 00:32:57 blueyed 3155 * doc 3156 * 3157 * Revision 1.128 2006/10/26 20:23:00 blueyed 3158 * Handle invalid URLs gracefully in url_rel_to_same_host() 3159 * 3160 * Revision 1.127 2006/10/23 22:19:03 blueyed 3161 * Fixed/unified encoding of redirect_to param. Use just rawurlencode() and no funky & replacements 3162 * 3163 * Revision 1.126 2006/10/16 08:39:10 blueyed 3164 * Merged fixes from v-1-9 branch 3165 * 3166 * Revision 1.125 2006/10/14 16:06:38 blueyed 3167 * Fixed anchors for "redirection-Debuglog" 3168 * 3169 * Revision 1.124 2006/10/14 02:12:01 blueyed 3170 * Added url_absolute(), make_rel_links_abs() + Tests; Fixed validate_url() and allow relative URLs, which get converted to absolute ones in feeds. 3171 * 3172 * Revision 1.123 2006/10/13 11:47:55 blueyed 3173 * Use var_dump() only in pre_dump() 3174 * 3175 * Revision 1.122 2006/10/13 11:44:45 blueyed 3176 * MFB: url_rel_to_same_host() 3177 * 3178 * Revision 1.121 2006/10/09 10:42:44 blueyed 3179 * better layout for pre_dump() 3180 * 3181 * Revision 1.120 2006/10/09 10:29:16 blueyed 3182 * Handling of "resource" arguments in pre_dump() 3183 */ 3184 ?>
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
| Généré le : Thu Nov 29 23:58:50 2007 | par Balluche grâce à PHPXref 0.7 |
|