1<?php 2/* 3* e107 website system 4* 5* Copyright (C) 2008-2016 e107 Inc (e107.org) 6* Released under the terms and conditions of the 7* GNU General Public License (http://www.gnu.org/licenses/gpl.txt) 8* 9* Text processing and parsing functions 10* 11*/ 12 13if (!defined('e107_INIT')) { exit(); } 14 15// Directory for the hard-coded utf-8 handling routines 16define('E_UTF8_PACK', e_HANDLER.'utf8/'); 17 18define("E_NL", chr(2)); 19 20class e_parse extends e_parser 21{ 22 /** 23 * Determine how to handle utf-8. 24 * 0 = 'do nothing' 25 * 1 = 'use mb_string' 26 * 2 = emulation 27 * 28 * @var integer 29 */ 30 protected $utfAction; 31 32 // Shortcode processor - see __get() 33 //var $e_sc; 34 35 // BBCode processor 36 var $e_bb; 37 38 // Profanity filter 39 var $e_pf; 40 41 // Emote filter 42 var $e_emote; 43 44 // 'Hooked' parsers (array) 45 var $e_hook; 46 47 var $search = array('&#039;', ''', ''', '"', 'onerror', '>', '&quot;', ' & '); 48 49 var $replace = array("'", "'", "'", '"', 'one<i></i>rror', '>', '"', ' & '); 50 51 // Set to TRUE or FALSE once it has been calculated 52 var $e_highlighting; 53 54 // Highlight query 55 var $e_query; 56 57 public $thumbWidth = 100; 58 59 public $thumbHeight = 0; 60 61 public $thumbCrop = 0; 62 63 private $thumbEncode = 0; 64 65 private $staticCount = 0; 66 67 // BBcode that contain preformatted code. 68 private $preformatted = array('html', 'markdown'); 69 70 71 // Set up the defaults 72 var $e_optDefault = array( 73 // default context: reflects legacy settings (many items enabled) 74 'context' => 'OLDDEFAULT', 75 // 76 'fromadmin' => FALSE, 77 78 // Enable emote display 79 'emotes' => TRUE, 80 81 // Convert defines(constants) within text. 82 'defs' => FALSE, 83 84 // replace all {e_XXX} constants with their e107 value - 'rel' or 'abs' 85 'constants' => FALSE, 86 87 // Enable hooked parsers 88 'hook' => TRUE, 89 90 // Allow scripts through (new for 0.8) 91 'scripts' => TRUE, 92 93 // Make links clickable 94 'link_click' => TRUE, 95 96 // Substitute on clickable links (only if link_click == TRUE) 97 'link_replace' => TRUE, 98 99 // Parse shortcodes - TRUE enables parsing 100 101 'parse_sc' => FALSE, 102 // remove HTML tags. 103 'no_tags' => FALSE, 104 105 // Restore entity form of quotes and such to single characters - TRUE disables 106 'value' => FALSE, 107 108 // Line break compression - TRUE removes newline characters 109 'nobreak' => FALSE, 110 111 // Retain newlines - wraps to \n instead of <br /> if TRUE (for non-HTML email text etc) 112 'retain_nl' => FALSE 113 ); 114 115 // Super modifiers override default option values 116 var $e_SuperMods = array( 117 //text is part of a title (e.g. news title) 118 'TITLE' => 119 array( 120 'nobreak'=>TRUE, 'retain_nl'=>TRUE, 'link_click' => FALSE, 'emotes'=>FALSE, 'defs'=>TRUE, 'parse_sc'=>TRUE 121 ), 122 'TITLE_PLAIN' => 123 array( 124 'nobreak'=>TRUE, 'retain_nl'=>TRUE, 'link_click' => FALSE, 'emotes'=>FALSE, 'defs'=>TRUE, 'parse_sc'=>TRUE, 'no_tags' => TRUE 125 ), 126 //text is user-entered (i.e. untrusted) and part of a title (e.g. forum title) 127 'USER_TITLE' => 128 array( 129 'nobreak'=>TRUE, 'retain_nl'=>TRUE, 'link_click' => FALSE, 'scripts' => FALSE, 'emotes'=>FALSE, 'hook'=>FALSE 130 ), 131 // text is 'body' of email or similar - being sent 'off-site' so don't rely on server availability 132 'E_TITLE' => 133 array( 134 'nobreak'=>TRUE, 'retain_nl'=>TRUE, 'defs'=>TRUE, 'parse_sc'=>TRUE, 'emotes'=>FALSE, 'scripts' => FALSE, 'link_click' => FALSE 135 ), 136 // text is part of the summary of a longer item (e.g. content summary) 137 'SUMMARY' => 138 array( 139 'defs'=>TRUE, 'constants'=>'full', 'parse_sc'=>TRUE 140 ), 141 // text is the description of an item (e.g. download, link) 142 'DESCRIPTION' => 143 array( 144 'defs'=>TRUE, 'constants'=>'full', 'parse_sc'=>TRUE 145 ), 146 // text is 'body' or 'bulk' text (e.g. custom page body, content body) 147 'BODY' => 148 array( 149 'defs'=>TRUE, 'constants'=>'full', 'parse_sc'=>TRUE 150 ), 151 // text is parsed by the Wysiwyg editor. eg. TinyMce 152 'WYSIWYG' => 153 array( 154 'hook' => false, 'link_click' => false, 'link_replace' => false, 'retain_nl' => true 155 ), 156 // text is user-entered (i.e. untrusted)'body' or 'bulk' text (e.g. custom page body, content body) 157 'USER_BODY' => 158 array( 159 'constants'=>'full', 'scripts' => FALSE, 'nostrip'=>FALSE 160 ), 161 // text is 'body' of email or similar - being sent 'off-site' so don't rely on server availability 162 'E_BODY' => 163 array( 164 'defs'=>TRUE, 'constants'=>'full', 'parse_sc'=>TRUE, 'emotes'=>FALSE, 'scripts' => FALSE, 'link_click' => FALSE 165 ), 166 // text is text-only 'body' of email or similar - being sent 'off-site' so don't rely on server availability 167 'E_BODY_PLAIN' => 168 array( 169 'defs'=>TRUE, 'constants'=>'full', 'parse_sc'=>TRUE, 'emotes'=>FALSE, 'scripts' => FALSE, 'link_click' => FALSE, 'retain_nl' => TRUE, 'no_tags' => TRUE 170 ), 171 // text is the 'content' of a link (A tag, etc) 172 'LINKTEXT' => 173 array( 174 'nobreak'=>TRUE, 'retain_nl'=>TRUE, 'link_click' => FALSE, 'emotes'=>FALSE, 'hook'=>FALSE, 'defs'=>TRUE, 'parse_sc'=>TRUE 175 ), 176 // text is used (for admin edit) without fancy conversions or html. 177 'RAWTEXT' => 178 array( 179 'nobreak'=>TRUE, 'retain_nl'=>TRUE, 'link_click' => FALSE, 'emotes'=>FALSE, 'hook'=>FALSE, 'no_tags'=>TRUE 180 ) 181 ); 182 183 // Individual modifiers change the current context 184 var $e_Modifiers = array( 185 'emotes_off' => array('emotes' => FALSE), 186 'emotes_on' => array('emotes' => TRUE), 187 'no_hook' => array('hook' => FALSE), 188 'do_hook' => array('hook' => TRUE), 189 // New for 0.8 190 'scripts_off' => array('scripts' => FALSE), 191 // New for 0.8 192 'scripts_on' => array('scripts' => TRUE), 193 'no_make_clickable' => array('link_click' => FALSE), 194 'make_clickable' => array('link_click' => TRUE), 195 'no_replace' => array('link_replace' => FALSE), 196 // Replace text of clickable links (only if make_clickable option set) 197 'replace' => array('link_replace' => TRUE), 198 // No path replacement 199 'consts_off' => array('constants' => FALSE), 200 // Relative path replacement 201 'consts_rel' => array('constants' => 'rel'), 202 // Absolute path replacement 203 'consts_abs' => array('constants' => 'abs'), 204 // Full path replacement 205 'consts_full' => array('constants' => 'full'), 206 // No shortcode parsing 207 'scparse_off' => array('parse_sc' => FALSE), 208 209 'scparse_on' => array('parse_sc' => TRUE), 210 // Strip tags 211 'no_tags' => array('no_tags' => TRUE), 212 // Leave tags 213 'do_tags' => array('no_tags' => FALSE), 214 215 'fromadmin' => array('fromadmin' => TRUE), 216 'notadmin' => array('fromadmin' => FALSE), 217 // entity replacement 218 'er_off' => array('value' => FALSE), 219 'er_on' => array('value' => TRUE), 220 // Decode constant if exists 221 'defs_off' => array('defs' => FALSE), 222 'defs_on' => array('defs' => TRUE), 223 224 'dobreak' => array('nobreak' => FALSE), 225 'nobreak' => array('nobreak' => TRUE), 226 // Line break using \n 227 'lb_nl' => array('retain_nl' => TRUE), 228 // Line break using <br /> 229 'lb_br' => array('retain_nl' => FALSE), 230 231 // Legacy option names below here - discontinue later 232 'retain_nl' => array('retain_nl' => TRUE), 233 'defs' => array('defs' => TRUE), 234 'parse_sc' => array('parse_sc' => TRUE), 235 'constants' => array('constants' => 'rel'), 236 'value' => array('value' => TRUE), 237 'wysiwyg' => array('wysiwyg'=>TRUE) 238 ); 239 240 241 /** 242 * Constructor - keep it public for backward compatibility 243 still some new e_parse() in the core 244 * 245 */ 246 public function __construct() 247 { 248 // initialise the type of UTF-8 processing methods depending on PHP version and mb string extension 249 parent::__construct(); 250 251 252 $this->init(); 253 $this->initCharset(); 254 255 // Preprocess the supermods to be useful default arrays with all values 256 foreach ($this->e_SuperMods as $key => $val) 257 { 258 // precalculate super defaults 259 $this->e_SuperMods[$key] = array_merge($this->e_optDefault , $this->e_SuperMods[$key]); 260 $this->e_SuperMods[$key]['context'] = $key; 261 } 262 } 263 264 265 /** 266 * Initialise the type of UTF-8 processing methods depending on PHP version and mb string extension. 267 * 268 * NOTE: can't be called until CHARSET is known 269 but we all know that it is UTF-8 now 270 * 271 * @return void 272 */ 273 private function initCharset() 274 { 275 // Start by working out what, if anything, we do about utf-8 handling. 276 // 'Do nothing' is the simple option 277 $this->utfAction = 0; 278// CHARSET is utf-8 279// if(strtolower(CHARSET) == 'utf-8') 280// { 281 if(version_compare(PHP_VERSION, '6.0.0') < 1) 282 { 283 // Need to do something here 284 if(extension_loaded('mbstring')) 285 { 286 // Check for function overloading 287 $temp = ini_get('mbstring.func_overload'); 288 // Just check the string functions - will be non-zero if overloaded 289 if(($temp & MB_OVERLOAD_STRING) == 0) 290 { 291 // Can use the mb_string routines 292 $this->utfAction = 1; 293 } 294 // Set the default encoding, so we don't have to specify every time 295 mb_internal_encoding('UTF-8'); 296 } 297 else 298 { 299 // Must use emulation - will probably be slow! 300 $this->utfAction = 2; 301 require_once(E_UTF8_PACK.'utils/unicode.php'); 302 // Always load the core routines - bound to need some of them! 303 require_once(E_UTF8_PACK.'native/core.php'); 304 } 305 } 306// } 307 } 308 309 310 /** 311 * Unicode (UTF-8) analogue of standard @link http://php.net/strlen strlen PHP function. 312 * Returns the length of the given string. 313 * 314 * @param string $str The UTF-8 encoded string being measured for length. 315 * @return integer The length (amount of UTF-8 characters) of the string on success, and 0 if the string is empty. 316 */ 317 public function ustrlen($str) 318 { 319 switch($this->utfAction) 320 { 321 case 0: 322 return strlen($str); 323 case 1: 324 return mb_strlen($str); 325 } 326 // Default case shouldn't happen often 327 // Save a call - invoke the function directly 328 return strlen(utf8_decode($str)); 329 } 330 331 332 /** 333 * Unicode (UTF-8) analogue of standard @link http://php.net/strtolower strtolower PHP function. 334 * Make a string lowercase. 335 * 336 * @param string $str The UTF-8 encoded string to be lowercased. 337 * @return string Specified string with all alphabetic characters converted to lowercase. 338 */ 339 public function ustrtolower($str) 340 { 341 switch($this->utfAction) 342 { 343 case 0: 344 return strtolower($str); 345 case 1: 346 return mb_strtolower($str); 347 } 348 // Default case shouldn't happen often 349 return utf8_strtolower($str); 350 } 351 352 353 /** 354 * Unicode (UTF-8) analogue of standard @link http://php.net/strtoupper strtoupper PHP function. 355 * Make a string uppercase. 356 * 357 * @param string $str The UTF-8 encoded string to be uppercased. 358 * @return string Specified string with all alphabetic characters converted to uppercase. 359 */ 360 public function ustrtoupper($str) 361 { 362 switch($this->utfAction) 363 { 364 case 0: 365 return strtoupper($str); 366 case 1: 367 return mb_strtoupper($str); 368 } 369 // Default case shouldn't happen often 370 return utf8_strtoupper($str); 371 } 372 373 374 /** 375 * Unicode (UTF-8) analogue of standard @link http://php.net/strpos strpos PHP function. 376 * Find the position of the first occurrence of a case-sensitive UTF-8 encoded string. 377 * Returns the numeric position (offset in amount of UTF-8 characters) 378 * of the first occurrence of needle in the haystack string. 379 * 380 * @param string $haystack The UTF-8 encoded string being searched in. 381 * @param integer $needle The UTF-8 encoded string being searched for. 382 * @param integer $offset [optional] The optional offset parameter allows you to specify which character in haystack to start searching. 383 * The position returned is still relative to the beginning of haystack. 384 * @return integer|boolean Returns the position as an integer. If needle is not found, the function will return boolean FALSE. 385 */ 386 public function ustrpos($haystack, $needle, $offset = 0) 387 { 388 switch($this->utfAction) 389 { 390 case 0: 391 return strpos($haystack, $needle, $offset); 392 case 1: 393 return mb_strpos($haystack, $needle, $offset); 394 } 395 return utf8_strpos($haystack, $needle, $offset); 396 } 397 398 399 /** 400 * Unicode (UTF-8) analogue of standard @link http://php.net/strrpos strrpos PHP function. 401 * Find the position of the last occurrence of a case-sensitive UTF-8 encoded string. 402 * Returns the numeric position (offset in amount of UTF-8 characters) 403 * of the last occurrence of needle in the haystack string. 404 * 405 * @param string $haystack The UTF-8 encoded string being searched in. 406 * @param integer $needle The UTF-8 encoded string being searched for. 407 * @param integer $offset [optional] - The optional offset parameter allows you to specify which character in haystack to start searching. 408 * The position returned is still relative to the beginning of haystack. 409 * @return integer|boolean Returns the position as an integer. If needle is not found, the function will return boolean FALSE. 410 */ 411 public function ustrrpos($haystack, $needle, $offset = 0) 412 { 413 switch($this->utfAction) 414 { 415 case 0: 416 return strrpos($haystack, $needle, $offset); 417 case 1: 418 return mb_strrpos($haystack, $needle, $offset); 419 } 420 return utf8_strrpos($haystack, $needle, $offset); 421 } 422 423 424 /** 425 * Unicode (UTF-8) analogue of standard @link http://php.net/stristr stristr PHP function. 426 * Returns all of haystack starting from and including the first occurrence of needle to the end. 427 * 428 * @param string $haystack The UTF-8 encoded string to search in. 429 * @param mixed $needle If needle is not a string, it is converted to an integer and applied as the ordinal value of a character. 430 * @param integer $length [optional] (PHP 5.3+) If TRUE, returns the part of the haystack before the first occurrence of the needle (excluding needle). 431 * @return string Returns the matched substring. If needle is not found, returns FALSE. 432 */ 433 public function ustristr($haystack, $needle, $before_needle = false) 434 { 435 switch($this->utfAction) 436 { 437 case 0: 438 return stristr($haystack, $needle, $before_needle); 439 case 1: 440 //return mb_substr($haystack, $needle, $before_needle); 441 return mb_stristr($haystack, $needle, $before_needle); 442 } 443 // No utf8 pack backup 444 return stristr($haystack, $needle, $before_needle); 445 } 446 447 /** 448 * Unicode (UTF-8) analogue of standard @link http://php.net/substr substr PHP function. 449 * Returns the portion of string specified by the start and length parameters. 450 * 451 * NOTE: May be subtle differences in return values dependent on which routine is used. 452 * Native substr() routine can return FALSE. mb_substr() and utf8_substr() just return an empty string. 453 * 454 * @param string $str The UTF-8 encoded string. 455 * @param integer $start Start of portion to be returned. Position is counted in amount of UTF-8 characters from the beginning of str. 456 * First character's position is 0. Second character position is 1, and so on. 457 * @param integer $length [optional] If length is given, the string returned will contain at most length characters beginning from start 458 * (depending on the length of string). If length is omitted, the rest of string from start will be returned. 459 * @return string The extracted UTF-8 encoded part of input string. 460 */ 461 public function usubstr($str, $start, $length = NULL) 462 { 463 switch($this->utfAction) 464 { 465 case 0: 466 return substr($str, $start, $length); 467 case 1: 468 if(is_null($length)) 469 { 470 return mb_substr($str, $start); 471 } 472 else 473 { 474 return mb_substr($str, $start, $length); 475 } 476 } 477 return utf8_substr($str, $start, $length); 478 } 479 480 /** 481 * Converts the supplied text (presumed to be from user input) to a format suitable for storing in a database table. 482 * 483 * @param mixed $data 484 * @param boolean $nostrip [optional] Assumes all data is GPC ($_GET, $_POST, $_COOKIE) unless indicate otherwise by setting this var to TRUE. 485 * If magic quotes is enabled on the server and you do not tell toDB() that the data is non GPC then slashes will be stripped when they should not be. 486 * @param boolean $no_encode [optional] This parameter should nearly always be FALSE. It is used by the save_prefs() function to preserve HTML content within prefs even when 487 * the save_prefs() function has been called by a non admin user / user without html posting permissions. 488 * @param boolean|string $mod [optional] model = admin-ui usage. The 'no_html' and 'no_php' modifiers blanket prevent HTML and PHP posting regardless of posting permissions. (used in logging) 489 * The 'pReFs' value is for internal use only, when saving prefs, to prevent sanitisation of HTML. 490 * @param mixed $parm [optional] 491 * @return string|array 492 * @todo complete the documentation of this essential method 493 */ 494 public function toDB($data = null, $nostrip =false, $no_encode = false, $mod = false, $parm = null) 495 { 496 if($data === null) 497 { 498 return null; 499 } 500 501 if (is_array($data)) 502 { 503 $ret = array(); 504 505 foreach ($data as $key => $var) 506 { 507 //Fix - sanitize keys as well 508 $key = filter_var($key,FILTER_SANITIZE_STRING); 509 $ret[$key] = $this->toDB($var, $nostrip, $no_encode, $mod, $parm); 510 } 511 512 return $ret; 513 } 514 515 if (MAGIC_QUOTES_GPC == true && $nostrip == false) 516 { 517 $data = stripslashes($data); 518 } 519 520 if(intval($data) === $data || $data === '0') // simple integer. 521 { 522 return $data; 523 } 524 525 $core_pref = e107::getConfig(); 526 527 if ($mod !== 'pReFs') //XXX We're not saving prefs. 528 { 529 530 $data = $this->preFilter($data); // used by bb_xxx.php toDB() functions. bb_code.php toDB() allows us to properly bypass HTML cleaning below. 531 $data = $this->cleanHtml($data); // clean it regardless of if it is text or html. (html could have missing closing tags) 532 533 if(($this->isHtml($data)) && strpos($mod, 'no_html') === false) 534 { 535 $this->isHtml = true; 536 // $data = $this->cleanHtml($data); // sanitize all html. (moved above to include everything) 537 538 $data = str_replace(array('%7B','%7D'),array('{','}'),$data); // fix for {e_XXX} paths. 539 } 540 else // caused double-encoding of '&' 541 { 542 // $data = str_replace('&','&',$data); 543 // $data = str_replace('<','<',$data); 544 // $data = str_replace('>','>',$data); 545 // $data = str_replace('&','&',$data); 546 547 } 548 549 550 if (!check_class($core_pref->get('post_html', e_UC_MAINADMIN))) 551 { 552 $data = strip_tags($data); // remove tags from cleaned html. 553 $data = str_replace(array('[html]','[/html]'),'',$data); 554 } 555 556 // $data = html_entity_decode($data, ENT_QUOTES, 'utf-8'); // Prevent double-entities. Fix for [code] - see bb_code.php toDB(); 557 } 558 559 560 561 if (check_class($core_pref->get('post_html'))) /*$core_pref->is('post_html') && XXX preformecd by cleanHtml() */ 562 { 563 $no_encode = true; 564 } 565 566 if($parm !== null && is_numeric($parm) && !check_class($core_pref->get('post_html'), '', $parm)) 567 { 568 $no_encode = false; 569 } 570 571 if ($no_encode === true && strpos($mod, 'no_html') === false) 572 { 573 $search = array('$', '"', "'", '\\', '<?'); 574 $replace = array('$', '"', ''', '\', '<?'); 575 $ret = str_replace($search, $replace, $data); 576 } 577 else // add entities for everything. we want to save the code. 578 { 579 580 $search = array('>', '<'); 581 $replace = array('>', '<'); 582 $data = str_replace($search, $replace, $data); // prevent &gt; etc. 583 584 $data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8'); 585 $data = str_replace('\\', '\', $data); 586 587 $ret = preg_replace("/&#(\d*?);/", "&#\\1;", $data); 588 } 589 590 // XXX - php_bbcode has been deprecated. 591 if ((strpos($mod, 'no_php') !== false) || !check_class($core_pref->get('php_bbcode'))) 592 { 593 $ret = preg_replace("#\[(php)#i", "[\\1", $ret); 594 } 595 596 // Don't allow hooks to mess with prefs. 597 if($mod !== 'model') 598 { 599 return $ret; 600 } 601 602 603 /** 604 * e_parse hook 605 */ 606 $eParseList = $core_pref->get('e_parse_list'); 607 if(!empty($eParseList)) 608 { 609 610 $opts = array( 611 'nostrip' => $nostrip, 612 'noencode' => $no_encode, 613 'type' => $parm['type'], 614 'field' => $parm['field'] 615 ); 616 617 foreach($eParseList as $plugin) 618 { 619 $hookObj = e107::getAddon($plugin, 'e_parse'); 620 if($tmp = e107::callMethod($hookObj, 'toDB', $ret, $opts)) 621 { 622 $ret = $tmp; 623 } 624 625 } 626 627 } 628 629 630 return $ret; 631 } 632 633 634 635 /** 636 * Check for umatched 'dangerous' HTML tags 637 * (these can destroy page layout where users are able to post HTML) 638 * @DEPRECATED 639 * @param string $data 640 * @param string $tagList - if empty, uses default list of input tags. Otherwise a CSV list of tags to check (any type) 641 * 642 * @return boolean TRUE if an unopened closing tag found 643 * FALSE if nothing found 644 */ 645 function htmlAbuseFilter($data, $tagList = '') 646 { 647 648 if ($tagList == '') 649 { 650 $checkTags = array('textarea', 'input', 'td', 'tr', 'table'); 651 } 652 else 653 { 654 $checkTags = explode(',', $tagList); 655 } 656 $tagArray = array_flip($checkTags); 657 foreach ($tagArray as &$v) { $v = 0; }; // Data fields become zero; keys are tag names. 658 $data = strtolower(preg_replace('#\[code\].*?\[\/code\]#i', '', $data)); // Ignore code blocks. All lower case simplifies the rest 659 $matches = array(); 660 if (!preg_match_all('#<(\/|)([^<>]*?[^\/])>#', $data, $matches, PREG_SET_ORDER)) 661 { 662 //echo "No tags found<br />"; 663 return TRUE; // No tags found; so all OK 664 } 665 //print_a($matches); 666 foreach ($matches as $m) 667 { 668 // $m[0] is the complete tag; $m[1] is '/' or empty; $m[2] is the tag and any attributes 669 list ($tag) = explode(' ', $m[2], 2); 670 if (!isset($tagArray[$tag])) continue; // Not a tag of interest 671 if ($m[1] == '/') 672 { // Closing tag 673 if ($tagArray[$tag] == 0) 674 { 675 //echo "Close before open: {$tag}<br />"; 676 return TRUE; // Closing tag before we've had an opening tag 677 } 678 $tagArray[$tag]--; // Obviously had at least one opening tag 679 } 680 else 681 { // Opening tag 682 $tagArray[$tag]++; 683 } 684 } 685 //print_a($tagArray); 686 foreach ($tagArray as $t) 687 { 688 if ($t > 0) return TRUE; // More opening tags than closing tags 689 } 690 return FALSE; // OK now 691 } 692 693 694 695 696 /** 697 * @DEPRECATED XXX TODO Remove this horrible thing which adds junk to a db. 698 * Checks a string for potentially dangerous HTML tags, including malformed tags 699 * 700 */ 701 public function dataFilter($data, $mode='bbcode') 702 { 703 704 705 $ans = ''; 706 $vetWords = array('<applet', '<body', '<embed', '<frame', '<script','%3Cscript', 707 '<frameset', '<html', '<iframe', '<style', '<layer', '<link', 708 '<ilayer', '<meta', '<object', '<plaintext', 'javascript:', 709 'vbscript:','data:text/html'); 710 711 $ret = preg_split('#(\[code.*?\[/code.*?])#mis', $data, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE ); 712 713 foreach ($ret as $s) 714 { 715 if (substr($s, 0, 5) != '[code') 716 { 717 $vl = array(); 718 $t = html_entity_decode(rawurldecode($s), ENT_QUOTES, CHARSET); 719 $t = str_replace(array("\r", "\n", "\t", "\v", "\f", "\0"), '', $t); 720 $t1 = strtolower($t); 721 foreach ($vetWords as $vw) 722 { 723 if (strpos($t1, $vw) !== FALSE) 724 { 725 $vl[] = $vw; // Add to list of words found 726 } 727 if (substr($vw, 0, 1) == '<') 728 { 729 $vw = '</'.substr($vw, 1); 730 if (strpos($t1, $vw) !== FALSE) 731 { 732 $vl[] = $vw; // Add to list of words found 733 } 734 } 735 } 736 // More checks here 737 if (count($vl)) 738 { // Do something 739 $s = preg_replace_callback('#('.implode('|', $vl).')#mis', array($this, 'modtag'), $t); 740 } 741 } 742 $s = preg_replace('#(?:onmouse.+?|onclick|onfocus)\s*?\=#', '[sanitised]$0[/sanitised]', $s); 743 $s = preg_replace_callback('#base64([,\(])(.+?)([\)\'\"])#mis', array($this, 'proc64'), $s); 744 $ans .= $s; 745 } 746 747 if($mode == 'link' && count($vl)) 748 { 749 return "#sanitized"; 750 } 751 752 return $ans; 753 } 754 755 756 /** 757 * Check base-64 encoded code 758 */ 759 private function proc64($match) 760 { 761 $decode = base64_decode($match[2]); 762 return 'base64'.$match[1].base64_encode($this->dataFilter($decode)).$match[3]; 763 } 764 765 766 // XXX REmove ME. 767 private function modTag($match) 768 { 769 $ans = ''; 770 if (isset($match[1])) 771 { 772 $chop = intval(strlen($match[1]) / 2); 773 $ans = substr($match[1], 0, $chop).'##xss##'.substr($match[1], $chop); 774 } 775 else 776 { 777 $ans = '?????'; 778 } 779 return '[sanitised]'.$ans.'[/sanitised]'; 780 781 } 782 783 784 785 /** 786 * Processes data as needed before its written to the DB. 787 * Currently gives bbcodes the opportunity to do something 788 * 789 * @param $data string - data about to be written to DB 790 * @return string - modified data 791 */ 792 public function preFilter($data) 793 { 794 if (!is_object($this->e_bb)) 795 { 796 require_once(e_HANDLER.'bbcode_handler.php'); 797 $this->e_bb = new e_bbcode; 798 } 799 $ret = $this->e_bb->parseBBCodes($data, USERID, 'default', 'PRE'); // $postID = logged in user here 800 return $ret; 801 } 802 803 804 805 806 function toForm($text) 807 { 808 809 if(empty($text)) // fix - handle proper 0, Space etc values. 810 { 811 return $text; 812 } 813 814 815 if(is_string($text) && substr($text,0,6) == '[html]') 816 { 817 // $text = $this->toHTML($text,true); 818 $search = array('"',''','\', '&',); // '&' must be last. 819 $replace = array('"',"'","\\", '&'); 820 821 // return htmlspecialchars_decode($text); 822 $text = str_replace($search,$replace,$text); 823 // return $text; 824 //$text = htmlentities($text,ENT_NOQUOTES, "UTF-8"); 825 826 // return $text; 827 828 } 829 // return htmlentities($text); 830 831 $search = array('$', '"', '<', '>', '+'); 832 $replace = array('$', '"', '<', '>', '%2B'); 833 $text = str_replace($search, $replace, $text); 834 if (e107::wysiwyg() !== true && is_string($text)) 835 { 836 // fix for utf-8 issue with html_entity_decode(); ??? 837 $text = urldecode($text); 838 // $text = str_replace(" ", " ", $text); 839 } 840 return $text; 841 } 842 843 844 function post_toForm($text) 845 { 846 if(is_array($text)) 847 { 848 foreach ($text as $key=>$value) 849 { 850 $text[$this->post_toForm($key)] = $this->post_toForm($value); 851 } 852 return $text; 853 } 854 if(MAGIC_QUOTES_GPC == TRUE) 855 { 856 $text = stripslashes($text); 857 } 858 return str_replace(array("'", '"', "<", ">"), array("'", """, "<", ">"), $text); 859 } 860 861 862 function post_toHTML($text, $original_author = FALSE, $extra = '', $mod = FALSE) 863 { 864 $text = $this->toDB($text, FALSE, FALSE, $mod, $original_author); 865 return $this->toHTML($text, TRUE, $extra); 866 } 867 868 /** 869 * @param $text - template to parse. 870 * @param boolean $parseSCFiles - parse core 'single' shortcodes 871 * @param object|array $extraCodes - shortcode class containing sc_xxxxx methods or an array of key/value pairs or legacy shortcode content (eg. content within .sc) 872 * @param object $eVars - XXX more info needed. 873 * @return string 874 */ 875 function parseTemplate($text, $parseSCFiles = true, $extraCodes = null, $eVars = null) 876 { 877 878 if(!is_bool($parseSCFiles)) 879 { 880 trigger_error("\$parseSCFiles in parseTemplate() was given incorrect data"); 881 } 882 883 return e107::getScParser()->parseCodes($text, $parseSCFiles, $extraCodes, $eVars); 884 } 885 886 887 /** 888 * Check if we are using the simple-Parse array format, or a legacy .sc format which contains 'return ' 889 * @param array $extraCodes 890 */ 891 private function isSimpleParse($extraCodes) 892 { 893 894 if(!is_array($extraCodes)) 895 { 896 return false; 897 } 898 899 foreach ($extraCodes as $sc => $code) 900 { 901 if(preg_match('/return(.*);/',$code)) // still problematic. 'return;' Might be used in common speech. 902 { 903 return false; 904 } 905 else 906 { 907 return true; 908 } 909 /* if(!strpos($code, 'return ')) 910 { 911 return true; 912 } 913 else 914 { 915 return false; 916 }*/ 917 } 918 } 919 920 921 922 /** 923 * Simple parser 924 * 925 * @param string $template 926 * @param e_vars|array $vars 927 * @param string $replaceUnset string to be used if replace variable is not set, false - don't replace 928 * @return string parsed content 929 */ 930 function simpleParse($template, $vars, $replaceUnset='') 931 { 932 $this->replaceVars = $vars; 933 $this->replaceUnset = $replaceUnset; 934 return preg_replace_callback("#\{([a-zA-Z0-9_]+)\}#", array($this, 'simpleReplace'), $template); 935 } 936 937 protected function simpleReplace($tmp) 938 { 939 940 $unset = ($this->replaceUnset !== false ? $this->replaceUnset : $tmp[0]); 941 942 if(is_array($this->replaceVars)) 943 { 944 $this->replaceVars = new e_vars($this->replaceVars); 945 //return ($this->replaceVars[$key] !== null ? $this->replaceVars[$key]: $unset); 946 } 947 $key = $tmp[1]; // PHP7 fix. 948 return ($this->replaceVars->$key !== null ? $this->replaceVars->$key : $unset); // Doesn't work. 949 } 950 951 952 function htmlwrap($str, $width, $break = "\n", $nobreak = "a", $nobr = "pre", $utf = FALSE) 953 { 954 /* 955 Pretty well complete rewrite to try and handle utf-8 properly. 956 Breaks each utf-8 'word' every $width characters max. If possible, breaks after 'safe' characters. 957 $break is the character inserted to flag the break. 958 $nobreak is a list of tags within which word wrap is to be inactive 959 */ 960 961 //TODO handle htmlwrap somehow 962 //return $str; 963 964 // Don't wrap if non-numeric width 965 $width = intval($width); 966 // And trap stupid wrap counts 967 if ($width < 6) 968 return $str; 969 970 // Transform protected element lists into arrays 971 $nobreak = explode(" ", strtolower($nobreak)); 972 973 // Variable setup 974 $intag = FALSE; 975 $innbk = array(); 976 $drain = ""; 977 978 // List of characters it is "safe" to insert line-breaks at 979 // It is not necessary to add < and > as they are automatically implied 980 $lbrks = "/?!%)-}]\\\"':;&"; 981 982 // Is $str a UTF8 string? 983 if ($utf || strtolower(CHARSET) == 'utf-8') 984 { 985 // 0x1680, 0x180e, 0x2000-0x200a, 0x2028, 0x205f, 0x3000 are 'non-ASCII' Unicode UCS-4 codepoints - see http://www.unicode.org/Public/UNIDATA/UnicodeData.txt 986 // All convert to 3-byte utf-8 sequences: 987 // 0x1680 0xe1 0x9a 0x80 988 // 0x180e 0xe1 0xa0 0x8e 989 // 0x2000 0xe2 0x80 0x80 990 // - 991 // 0x200a 0xe2 0x80 0x8a 992 // 0x2028 0xe2 0x80 0xa8 993 // 0x205f 0xe2 0x81 0x9f 994 // 0x3000 0xe3 0x80 0x80 995 $utf8 = 'u'; 996 $whiteSpace = '#([\x20|\x0c]|[\xe1][\x9a][\x80]|[\xe1][\xa0][\x8e]|[\xe2][\x80][\x80-\x8a,\xa8]|[\xe2][\x81][\x9f]|[\xe3][\x80][\x80]+)#'; 997 // Have to explicitly enumerate the whitespace chars, and use non-utf-8 mode, otherwise regex fails on badly formed utf-8 998 } 999 else 1000 { 1001 $utf8 = ''; 1002 // For non-utf-8, can use a simple match string 1003 $whiteSpace = '#(\s+)#'; 1004 } 1005 1006 1007 // Start of the serious stuff - split into HTML tags and text between 1008 $content = preg_split('#(<.*?'.'>)#mis', $str, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE ); 1009 foreach($content as $value) 1010 { 1011 if ($value[0] == "<") 1012 { 1013 // We are within an HTML tag 1014 // Create a lowercase copy of this tag's contents 1015 $lvalue = strtolower(substr($value, 1, -1)); 1016 if ($lvalue) 1017 { 1018 // Tag of non-zero length 1019 // If the first character is not a / then this is an opening tag 1020 if ($lvalue[0] != "/") 1021 { 1022 // Collect the tag name 1023 preg_match("/^(\w*?)(\s|$)/", $lvalue, $t); 1024 1025 // If this is a protected element, activate the associated protection flag 1026 if(in_array($t[1], $nobreak)) 1027 array_unshift($innbk, $t[1]); 1028 } 1029 else 1030 { 1031 // Otherwise this is a closing tag 1032 // If this is a closing tag for a protected element, unset the flag 1033 if (in_array(substr($lvalue, 1), $nobreak)) 1034 { 1035 reset($innbk); 1036 while (list($key, $tag) = each($innbk)) 1037 { 1038 if (substr($lvalue, 1) == $tag) 1039 { 1040 unset($innbk[$key]); 1041 break; 1042 } 1043 } 1044 $innbk = array_values($innbk); 1045 } 1046 } 1047 } 1048 else 1049 { 1050 // Eliminate any empty tags altogether 1051 $value = ''; 1052 } 1053 // Else if we're outside any tags, and with non-zero length string... 1054 } 1055 elseif ($value) 1056 { 1057 // If unprotected... 1058 if (!count($innbk)) 1059 { 1060 // Use the ACK (006) ASCII symbol to replace all HTML entities temporarily 1061 $value = str_replace("\x06", "", $value); 1062 preg_match_all("/&([a-z\d]{2,7}|#\d{2,5});/i", $value, $ents); 1063 $value = preg_replace("/&([a-z\d]{2,7}|#\d{2,5});/i", "\x06", $value); 1064 // echo "Found block length ".strlen($value).': '.substr($value,20).'<br />'; 1065 // Split at spaces - note that this will fail if presented with invalid utf-8 when doing the regex whitespace search 1066 // $split = preg_split('#(\s)#'.$utf8, $value, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE ); 1067 $split = preg_split($whiteSpace, $value, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE ); 1068 $value = ''; 1069 foreach ($split as $sp) 1070 { 1071 // echo "Split length ".strlen($sp).': '.substr($sp,20).'<br />'; 1072 $loopCount = 0; 1073 while (strlen($sp) > $width) 1074 { 1075 // Enough characters that we may need to do something. 1076 $pulled = ''; 1077 if ($utf8) 1078 { 1079 // Pull out a piece of the maximum permissible length 1080 if (preg_match('#^((?:[\x00-\x7F]|[\xC0-\xFF][\x80-\xBF]+){0,'.$width.'})(.{0,1}).*#s',$sp,$matches) == 0) 1081 { 1082 // Make any problems obvious for now 1083 $value .= '[!<b>invalid utf-8: '.$sp.'<b>!]'; 1084 $sp = ''; 1085 } 1086 elseif (empty($matches[2])) 1087 { 1088 // utf-8 length is less than specified - treat as a special case 1089 $value .= $sp; 1090 $sp = ''; 1091 } 1092 else 1093 { 1094 // Need to find somewhere to break the string 1095 for($i = strlen($matches[1]) - 1; $i >= 0; $i--) 1096 { 1097 if(strpos($lbrks, $matches[1][$i]) !== FALSE) 1098 break; 1099 } 1100 if($i < 0) 1101 { 1102 // No 'special' break character found - break at the word boundary 1103 $pulled = $matches[1]; 1104 } 1105 else 1106 { 1107 $pulled = substr($sp, 0, $i + 1); 1108 } 1109 } 1110 $loopCount++; 1111 if ($loopCount > 20) 1112 { 1113 // Make any problems obvious for now 1114 $value .= '[!<b>loop count exceeded: '.$sp.'</b>!]'; 1115 $sp = ''; 1116 } 1117 } 1118 else 1119 { 1120 for ($i = min($width, strlen($sp)); $i > 0; $i--) 1121 { 1122 // No speed advantage to defining match character 1123 if (strpos($lbrks, $sp[$i-1]) !== FALSE) 1124 break; 1125 } 1126 if ($i == 0) 1127 { 1128 // No 'special' break boundary character found - break at the word boundary 1129 $pulled = substr($sp, 0, $width); 1130 } 1131 else 1132 { 1133 $pulled = substr($sp, 0, $i); 1134 } 1135 } 1136 if ($pulled) 1137 { 1138 $value .= $pulled.$break; 1139 // Shorten $sp by whatever we've processed (will work even for utf-8) 1140 $sp = substr($sp, strlen($pulled)); 1141 } 1142 } 1143 // Add in any residue 1144 $value .= $sp; 1145 } 1146 // Put captured HTML entities back into the string 1147 foreach ($ents[0] as $ent) 1148 $value = preg_replace("/\x06/", $ent, $value, 1); 1149 } 1150 } 1151 // Send the modified segment down the drain 1152 $drain .= $value; 1153 } 1154 // Return contents of the drain 1155 return $drain; 1156 } 1157 1158 /** 1159 * CakePHP(tm) : Rapid Development Framework (http://www.cakephp.org) 1160 * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org) 1161 * 1162 * Truncate a HTML string 1163 * 1164 * Cuts a string to the length of $length and adds the value of $ending if the text is longer than length. 1165 * 1166 * @param string $text String to truncate. 1167 * @param integer $length Length of returned string, including ellipsis. 1168 * @param string $ending It will be used as Ending and appended to the trimmed string. 1169 * @param boolean $exact If false, $text will not be cut mid-word 1170 * @return string Trimmed string. 1171 */ 1172 function html_truncate($text, $length = 100, $ending = '...', $exact = true) 1173 { 1174 if($this->ustrlen(preg_replace('/<.*?>/', '', $text)) <= $length) 1175 { 1176 return $text; 1177 } 1178 $totalLength = 0; 1179 $openTags = array(); 1180 $truncate = ''; 1181 preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER); 1182 1183 foreach($tags as $tag) 1184 { 1185 if(!$tag[2] || !preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/si', $tag[2])) 1186 { 1187 if(preg_match('/<[\w]+[^>]*>/s', $tag[0])) 1188 { 1189 array_unshift($openTags, $tag[2]); 1190 } 1191 else if(preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag)) 1192 { 1193 $pos = array_search($closeTag[1], $openTags); 1194 if($pos !== false) 1195 { 1196 array_splice($openTags, $pos, 1); 1197 } 1198 } 1199 } 1200 $truncate .= $tag[1]; 1201 $contentLength = $this->ustrlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3])); 1202 1203 if($contentLength + $totalLength > $length) 1204 { 1205 $left = $length - $totalLength; 1206 $entitiesLength = 0; 1207 if(preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE)) 1208 { 1209 foreach($entities[0] as $entity) 1210 { 1211 if($entity[1] + 1 - $entitiesLength <= $left) 1212 { 1213 $left--; 1214 $entitiesLength += $this->ustrlen($entity[0]); 1215 } 1216 else 1217 { 1218 break; 1219 } 1220 } 1221 } 1222 1223 $truncate .= $this->usubstr($tag[3], 0, $left + $entitiesLength); 1224 break; 1225 } 1226 else 1227 { 1228 $truncate .= $tag[3]; 1229 $totalLength += $contentLength; 1230 } 1231 if($totalLength >= $length) 1232 { 1233 break; 1234 } 1235 } 1236 if(!$exact) 1237 { 1238 $spacepos = $this->ustrrpos($truncate, ' '); 1239 if(isset($spacepos)) 1240 { 1241 $bits = $this->usubstr($truncate, $spacepos); 1242 preg_match_all('/<\/([a-z]+)>/i', $bits, $droppedTags, PREG_SET_ORDER); 1243 if(!empty($droppedTags)) 1244 { 1245 foreach($droppedTags as $closingTag) 1246 { 1247 if(!in_array($closingTag[1], $openTags)) 1248 { 1249 array_unshift($openTags, $closingTag[1]); 1250 } 1251 } 1252 } 1253 $truncate = $this->usubstr($truncate, 0, $spacepos); 1254 } 1255 } 1256 $truncate .= $ending; 1257 foreach($openTags as $tag) 1258 { 1259 $truncate .= '</' . $tag . '>'; 1260 } 1261 return $truncate; 1262 } 1263 1264 /** 1265 * Truncate a HTML string to a maximum length $len append the string $more if it was truncated 1266 * 1267 * @param string $text String to process 1268 * @param integer $len [optional] Length of characters to be truncated - default 200 1269 * @param string $more [optional] String which will be added if truncation - default ' ... ' 1270 * @return string 1271 */ 1272 public function html_truncate_old ($text, $len = 200, $more = ' ... ') 1273 { 1274 $pos = 0; 1275 $curlen = 0; 1276 $tmp_pos = 0; 1277 $intag = FALSE; 1278 while($curlen < $len && $curlen < strlen($text)) 1279 { 1280 switch($text [$pos] ) 1281 { 1282 case "<": 1283 if($text [$pos + 1] == "/") 1284 { 1285 $closing_tag = TRUE; 1286 } 1287 $intag = TRUE; 1288 $tmp_pos = $pos - 1; 1289 $pos++; 1290 break; 1291 1292 1293 case ">": 1294 if($text [$pos - 1] == "/") 1295 { 1296 $closing_tag = TRUE; 1297 } 1298 if($closing_tag == TRUE) 1299 { 1300 $tmp_pos = 0; 1301 $closing_tag = FALSE; 1302 } 1303 $intag = FALSE; 1304 $pos++; 1305 break; 1306 1307 1308 case "&": 1309 if($text [$pos + 1] == "#") 1310 { 1311 $end = strpos(substr($text, $pos, 7), ";"); 1312 if($end !== FALSE) 1313 { 1314 $pos += ($end + 1); 1315 if(!$intag) 1316 { 1317 $curlen++; 1318 } 1319 break; 1320 } 1321 } 1322 else 1323 { 1324 $pos++; 1325 if(!$intag) 1326 { 1327 $curlen++; 1328 } 1329 break; 1330 } 1331 default: 1332 $pos++; 1333 if(!$intag) 1334 { 1335 $curlen++; 1336 } 1337 break; 1338 } 1339 } 1340 $ret = ($tmp_pos > 0 ? substr($text, 0, $tmp_pos+1) : substr($text, 0, $pos)); 1341 if($pos < strlen($text)) 1342 { 1343 $ret = $ret.$more; 1344 } 1345 return $ret; 1346 } 1347 1348 1349 /** 1350 * Truncate a string of text to a maximum length $len append the string $more if it was truncated 1351 * Uses current CHARSET for utf-8, returns $len characters rather than $len bytes 1352 * 1353 * @param string $text string to process 1354 * @param integer $len length of characters to be truncated 1355 * @param string $more string which will be added if truncation 1356 * @return string 1357 */ 1358 public function text_truncate($text, $len = 200, $more = ' ... ') 1359 { 1360 // Always valid 1361 1362 if($this->ustrlen($text) <= $len) 1363 { 1364 return $text; 1365 } 1366 1367 $text = html_entity_decode($text,ENT_QUOTES,'utf-8'); 1368 1369 if(function_exists('mb_strimwidth')) 1370 { 1371 return mb_strimwidth($text, 0, $len, $more); 1372 } 1373 1374 $ret = $this->usubstr($text, 0, $len); 1375 1376 // search for possible broken html entities 1377 // - if an & is in the last 8 chars, removing it and whatever follows shouldn't hurt 1378 // it should work for any characters encoding 1379 1380 $leftAmp = $this->ustrrpos($this->usubstr($ret, -8), '&'); 1381 if($leftAmp) 1382 { 1383 $ret = $this->usubstr($ret, 0, $this->ustrlen($ret) - 8 + $leftAmp); 1384 } 1385 1386 return $ret.$more; 1387 1388 } 1389 1390 1391 function textclean ($text, $wrap = 100) 1392 { 1393 $text = str_replace("\n\n\n", "\n\n", $text); 1394 $text = $this->htmlwrap($text, $wrap); 1395 $text = str_replace(array('<br /> ', ' <br />', ' <br /> '), '<br />', $text); 1396 /* we can remove any linebreaks added by htmlwrap function as any \n's will be converted later anyway */ 1397 return $text; 1398 } 1399 1400 1401 // Test for text highlighting, and determine the text highlighting transformation 1402 // Returns TRUE if highlighting is active for this page display 1403 function checkHighlighting() 1404 { 1405 global $pref; 1406 1407 if (!defined('e_SELF')) 1408 { 1409 // Still in startup, so can't calculate highlighting 1410 return FALSE; 1411 } 1412 1413 if(!isset($this->e_highlighting)) 1414 { 1415 $this->e_highlighting = FALSE; 1416 $shr = (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ""); 1417 if($pref['search_highlight'] && (strpos(e_SELF, 'search.php') === FALSE) && ((strpos($shr, 'q=') !== FALSE) || (strpos($shr, 'p=') !== FALSE))) 1418 { 1419 $this->e_highlighting = TRUE; 1420 if(!isset($this->e_query)) 1421 { 1422 $query = preg_match('#(q|p)=(.*?)(&|$)#', $shr, $matches); 1423 $this->e_query = str_replace(array('+', '*', '"', ' '), array('', '.*?', '', '\b|\b'), trim(urldecode($matches[2]))); 1424 } 1425 } 1426 } 1427 return $this->e_highlighting; 1428 } 1429 1430 1431 /** 1432 * Replace text represenation of website urls and email addresses with clickable equivalents. 1433 * @param string $text 1434 * @param string $type email|url 1435 * @param array $opts options. (see below) 1436 * @param string $opts['sub'] substitute text within links 1437 * @param bool $opts['ext'] load link in new window (not for email) 1438 * @return string 1439 */ 1440 public function makeClickable($text='', $type='email', $opts=array()) 1441 { 1442 1443 if(empty($text)) 1444 { 1445 return ''; 1446 } 1447 1448 $textReplace = (!empty($opts['sub'])) ? $opts['sub'] : ''; 1449 1450 if(substr($textReplace,-6) === '.glyph') 1451 { 1452 $textReplace = $this->toGlyph($textReplace,''); 1453 } 1454 1455 switch($type) 1456 { 1457 default: 1458 case "email": 1459 1460 preg_match_all("#(?:[\n\r ]|^)?([a-z0-9\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i", $text, $match); 1461 1462 if(!empty($match[0])) 1463 { 1464 1465 $srch = array(); 1466 $repl = array(); 1467 1468 foreach($match[0] as $eml) 1469 { 1470 $email = trim($eml); 1471 $srch[] = $email; 1472 $repl[] = $this->emailObfuscate($email,$textReplace); 1473 } 1474 $text = str_replace($srch,$repl,$text); 1475 } 1476 break; 1477 1478 case "url": 1479 1480 $linktext = (!empty($textReplace)) ? $textReplace : '$3'; 1481 $external = (!empty($opts['ext'])) ? 'target="_blank"' : ''; 1482 1483 $text= preg_replace("/(^|[\n \(])([\w]*?)([\w]*?:\/\/[\w]+[^ \,\"\n\r\t<]*)/is", "$1$2<a class=\"e-url\" href=\"$3\" ".$external.">".$linktext."</a>", $text); 1484 $text= preg_replace("/(^|[\n \(])([\w]*?)((www)\.[^ \,\"\t\n\r\)<]*)/is", "$1$2<a class=\"e-url\" href=\"http://$3\" ".$external.">".$linktext."</a>", $text); 1485 $text= preg_replace("/(^|[\n ])([\w]*?)((ftp)\.[^ \,\"\t\n\r<]*)/is", "$1$2<a class=\"e-url\" href=\"$4://$3\" ".$external.">".$linktext."</a>", $text); 1486 1487 break; 1488 1489 } 1490 1491 return $text; 1492 1493 1494 1495 } 1496 1497 1498 1499 function parseBBCodes($text, $postID) 1500 { 1501 if (!is_object($this->e_bb)) 1502 { 1503 require_once(e_HANDLER.'bbcode_handler.php'); 1504 $this->e_bb = new e_bbcode; 1505 } 1506 1507 1508 $text = $this->e_bb->parseBBCodes($text, $postID); 1509 1510 return $text; 1511 } 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 /** 1526 * Converts the text (presumably retrieved from the database) for HTML output. 1527 * 1528 * @param string $text 1529 * @param boolean $parseBB [optional] 1530 * @param string $modifiers [optional] TITLE|SUMMARY|DESCRIPTION|BODY|RAW|LINKTEXT etc. 1531 * Comma-separated list, no spaces allowed 1532 * first modifier must be a CONTEXT modifier, in UPPER CASE. 1533 * subsequent modifiers are lower case - see $this->e_Modifiers for possible values 1534 * @param mixed $postID [optional] 1535 * @param boolean $wrap [optional] 1536 * @return string 1537 * @todo complete the documentation of this essential method 1538 */ 1539 public function toHTML($text, $parseBB = FALSE, $modifiers = '', $postID = '', $wrap = FALSE) 1540 { 1541 if($text == '') 1542 { 1543 return $text; 1544 } 1545 1546 $pref = e107::getPref(); 1547 1548 global $fromadmin; 1549 1550 // Set default modifiers to start 1551 $opts = $this->e_optDefault; 1552 1553 1554 // Now process any modifiers that are specified 1555 if ($modifiers) 1556 { 1557 $aMods = explode(',', $modifiers); 1558 1559 // If there's a supermodifier, it must be first, and in uppercase 1560 $psm = trim($aMods[0]); 1561 if (isset($this->e_SuperMods[$psm])) 1562 { 1563 // Supermodifier found - override default values where necessary 1564 $opts = array_merge($opts,$this->e_SuperMods[$psm]); 1565 $opts['context'] = $psm; 1566 unset($aMods[0]); 1567 } 1568 1569 // Now find any regular modifiers; use them to modify the context 1570 // (there should only be one or two out of the list of possibles) 1571 foreach ($aMods as $mod) 1572 { 1573 // Slight concession to varying coding styles - stripping spaces is a waste of CPU cycles! 1574 $mod = trim($mod); 1575 if (isset($this->e_Modifiers[$mod])) 1576 { 1577 // This is probably quicker than array_merge 1578 // - especially as usually only one or two loops 1579 foreach ($this->e_Modifiers[$mod] as $k => $v) 1580 { 1581 // Update our context-specific options 1582 $opts[$k] = $v; 1583 } 1584 } 1585 } 1586 } 1587 1588 // Turn off a few things if not enabled in options 1589 if(empty($pref['smiley_activate'])) 1590 { 1591 $opts['emotes'] = false; 1592 } 1593 1594 if(empty($pref['make_clickable'])) 1595 { 1596 $opts['link_click'] = false; 1597 } 1598 1599 if(empty($pref['link_replace'])) 1600 { 1601 $opts['link_replace'] = false; 1602 } 1603 1604 if($this->isHtml($text)) //BC FIx for when HTML is saved without [html][/html] 1605 { 1606 $opts['nobreak'] = true; 1607 $text = trim($text); 1608 1609 if(strpos($text,'[center]') === 0) // quick bc fix TODO Find a better solution. [center][/center] containing HTML. 1610 { 1611 $text = str_replace("[center]","<div style='text-align:center'>",$text); 1612 $text = str_replace("[/center]","</div>",$text); 1613 } 1614 } 1615 1616 $fromadmin = $opts['fromadmin']; 1617 1618 // Convert defines(constants) within text. eg. Lan_XXXX - must be the entire text string (i.e. not embedded) 1619 // The check for '::' is a workaround for a bug in the Zend Optimiser 3.3.0 and PHP 5.2.4 combination 1620 // - causes crashes if '::' in site name 1621 1622 if($opts['defs'] && (strlen($text) < 35) && ((strpos($text, '::') === FALSE) && defined(trim($text)))) 1623 { 1624 $text = constant(trim($text)); // don't return yet, words could be hooked with linkwords etc. 1625 } 1626 1627 if ($opts['no_tags']) 1628 { 1629 $text = strip_tags($text); 1630 } 1631 1632 if (MAGIC_QUOTES_GPC === true) // precaution for badly saved data. 1633 { 1634 $text = stripslashes($text); 1635 } 1636 1637 1638 // Make sure we have a valid count for word wrapping 1639 if (!$wrap && !empty($pref['main_wordwrap'])) 1640 { 1641 $wrap = $pref['main_wordwrap']; 1642 } 1643// $text = " ".$text; 1644 1645 1646 // Now get on with the parsing 1647 $ret_parser = ''; 1648 $last_bbcode = ''; 1649 // So we can change them on each loop 1650 $saveOpts = $opts; 1651 1652 1653 1654 if ($parseBB == false) 1655 { 1656 $content = array($text); 1657 } 1658 else 1659 { 1660 // Split each text block into bits which are either within one of the 'key' bbcodes, or outside them 1661 // (Because we have to match end words, the 'extra' capturing subpattern gets added to output array. We strip it later) 1662 $content = preg_split('#(\[(table|html|php|code|scode|hide).*?\[/(?:\\2)\])#mis', $text, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE ); 1663 } 1664 1665 1666 // Use $full_text variable so its available to special bbcodes if required 1667 foreach ($content as $full_text) 1668 { 1669 $proc_funcs = TRUE; 1670 $convertNL = TRUE; 1671 1672 // We may have 'captured' a bbcode word - strip it if so 1673 if ($last_bbcode == $full_text) 1674 { 1675 $last_bbcode = ''; 1676 $proc_funcs = FALSE; 1677 $full_text = ''; 1678 } 1679 else 1680 { 1681 // Set the options for this pass 1682 $opts = $saveOpts; 1683 1684 // Have to have a good test in case a 'non-key' bbcode starts the block 1685 // - so pull out the bbcode parameters while we're there 1686 if (($parseBB !== FALSE) && preg_match('#(^\[(table|html|php|code|scode|hide)(.*?)\])(.*?)(\[/\\2\]$)#is', $full_text, $matches )) 1687 { 1688 // It's one of the 'key' bbcodes 1689 // Usually don't want 'normal' processing if its a 'special' bbcode 1690 $proc_funcs = FALSE; 1691 // $matches[0] - complete block from opening bracket of opening tag to closing bracket of closing tag 1692 // $matches[1] - complete opening tag (inclusive of brackets) 1693 // $matches[2] - bbcode word 1694 // $matches[3] - parameter, including '=' 1695 // $matches[4] - bit between the tags (i.e. text to process) 1696 // $matches[5] - closing tag 1697 // In case we decide to load a file 1698 1699 $bbPath = e_CORE.'bbcodes/'; 1700 $bbFile = strtolower(str_replace('_', '', $matches[2])); 1701 $bbcode = ''; 1702 $className = ''; 1703 $full_text = ''; 1704 $code_text = $matches[4]; 1705 $parm = $matches[3] ? substr($matches[3],1) : ''; 1706 $last_bbcode = $matches[2]; 1707 1708 switch ($matches[2]) 1709 { 1710 case 'php' : 1711 // Probably run the output through the normal processing functions - but put here so the PHP code can disable if desired 1712 $proc_funcs = TRUE; 1713 1714 // This is just the contents of the php.bb file pulled in - its short, so will be quicker 1715 // $search = array(""", "'", "$", '<br />', E_NL, "->", "<br />"); 1716 // $replace = array('"', "'", "$", "\n", "\n", "->", "<br />"); 1717 // Shouldn't have any parameter on this bbcode 1718 // Not sure whether checks are necessary now we've reorganised 1719 // if (!$matches[3]) $bbcode = str_replace($search, $replace, $matches[4]); 1720 // Because we're bypassing most of the initial parser processing, we should be able to just reverse the effects of toDB() and execute the code 1721 // [SecretR] - avoid php code injections, missing php.bb will completely disable user posted php blocks 1722 $bbcode = file_get_contents($bbPath.$bbFile.'.bb'); 1723 if (!$matches[3]) 1724 { 1725 $code_text = html_entity_decode($matches[4], ENT_QUOTES, 'UTF-8'); 1726 } 1727 break; 1728 1729 case 'html' : // This overrides and deprecates html.bb 1730 $proc_funcs = TRUE; 1731 1732 1733 // $code_text = str_replace("\r\n", " ", $code_text); 1734 // $code_text = html_entity_decode($code_text, ENT_QUOTES, CHARSET); 1735 // $code_text = str_replace('&','&',$code_text); // validation safe. 1736 $html_start = "<!-- bbcode-html-start -->"; // markers for html-to-bbcode replacement. 1737 $html_end = "<!-- bbcode-html-end -->"; 1738 $full_text = str_replace(array("[html]","[/html]"), "",$code_text); // quick fix.. security issue? 1739 1740 $full_text = $this->parseBBCodes($full_text, $postID); // parse any embedded bbcodes eg. [img] 1741 $full_text = $this->replaceConstants($full_text,'abs'); // parse any other paths using {e_.... 1742 $full_text = $html_start.$full_text.$html_end; 1743 $full_text = $this->parseBBTags($full_text); // strip <bbcode> tags. 1744 $opts['nobreak'] = true; 1745 $parseBB = false; // prevent further bbcode processing. 1746 1747 1748 break; 1749 1750 case 'table' : // strip <br /> from inside of <table> 1751 1752 $convertNL = FALSE; 1753 // break; 1754 1755 case 'hide' : 1756 $proc_funcs = TRUE; 1757 1758 default : // Most bbcodes will just execute their normal file 1759 // @todo should we cache these bbcodes? require_once should make class-related codes quite efficient 1760 if (file_exists($bbPath.'bb_'.$bbFile.'.php')) 1761 { // Its a bbcode class file 1762 require_once($bbPath.'bb_'.$bbFile.'.php'); 1763 $className = 'bb_'.$last_bbcode; 1764 1765 $this->bbList[$last_bbcode] = new $className(); 1766 } 1767 elseif(file_exists($bbPath.$bbFile.'.bb')) 1768 { 1769 $bbcode = file_get_contents($bbPath.$bbFile.'.bb'); 1770 } 1771 } // end - switch ($matches[2]) 1772 1773 if ($className) 1774 { 1775 /** @var e_bb_base $tempCode */ 1776 $tempCode = new $className(); 1777 1778 $full_text = $tempCode->bbPreDisplay($matches[4], $parm); 1779 } 1780 elseif ($bbcode) 1781 { // Execute the file 1782 $full_text = eval($bbcode); // Require output of bbcode to be returned 1783 // added to remove possibility of nested bbcode exploits ... 1784 // (same as in bbcode_handler - is it right that it just operates on $bbcode_return and not on $bbcode_output? - QUERY XXX-02 1785 } 1786 if(strpos($full_text, '[') !== FALSE) 1787 { 1788 $exp_search = array('eval', 'expression'); 1789 $exp_replace = array('ev<b></b>al', 'expres<b></b>sion'); 1790 $bbcode_return = str_replace($exp_search, $exp_replace, $full_text); 1791 } 1792 } 1793 } 1794 1795 1796 // Do the 'normal' processing - in principle, as previously - but think about the order. 1797 if ($proc_funcs && !empty($full_text)) // some more speed 1798 { 1799 // Split out and ignore any scripts and style blocks. With just two choices we can match the closing tag in the regex 1800 $subcon = preg_split('#((?:<s)(?:cript[^>]+>.*?</script>|tyle[^>]+>.*?</style>))#mis', $full_text, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE ); 1801 foreach ($subcon as $sub_blk) 1802 { 1803 1804 if(strpos($sub_blk,'<script') === 0) // Strip scripts unless permitted 1805 { 1806 if($opts['scripts']) 1807 { 1808 $ret_parser .= html_entity_decode($sub_blk, ENT_QUOTES); 1809 } 1810 } 1811 elseif(strpos($sub_blk,'<style') === 0) 1812 { 1813 // Its a style block - just pass it through unaltered - except, do we need the line break stuff? - QUERY XXX-01 1814 if(defined('DB_INF_SHOW')) 1815 { 1816 echo "Processing stylesheet: {$sub_blk}<br />"; 1817 } 1818 1819 $ret_parser .= $sub_blk; 1820 } 1821 else 1822 { 1823 // Do 'normal' processing on a chunk 1824 1825 1826 // Could put tag stripping in here 1827 1828/* 1829 // Line break compression - filter white space after HTML tags - among other things, ensures HTML tables display properly 1830 // Hopefully now achieved by other means 1831 if ($convertNL && !$opts['nobreak']) 1832 { 1833 $sub_blk = preg_replace("#>\s*[\r]*\n[\r]*#", ">", $sub_blk); 1834 } 1835*/ 1836 1837 // Link substitution 1838 // Convert URL's to clickable links, unless modifiers or prefs override 1839 if ($opts['link_click']) 1840 { 1841 if ($opts['link_replace'] && defset('ADMIN_AREA') !== true) 1842 { 1843 1844 $link_text = $pref['link_text']; 1845 $email_text = ($pref['email_text']) ? $this->replaceConstants($pref['email_text']) : LAN_EMAIL_SUBS; 1846 1847 $sub_blk = $this->makeClickable($sub_blk, 'url', array('sub'=> $link_text,'ext'=>$pref['links_new_window'])); 1848 $sub_blk = $this->makeClickable($sub_blk, 'email', array('sub'=> $email_text)); 1849 } 1850 else 1851 { 1852 1853 $sub_blk = $this->makeClickable($sub_blk, 'url', array('ext'=>true)); 1854 $sub_blk = $this->makeClickable($sub_blk, 'email'); 1855 1856 } 1857 } 1858 1859 1860 // Convert emoticons to graphical icons, if enabled 1861 if ($opts['emotes']) 1862 { 1863 if (!is_object($this->e_emote)) 1864 { 1865 // require_once(e_HANDLER.'emote_filter.php'); 1866 $this->e_emote = new e_emoteFilter; 1867 } 1868 $sub_blk = $this->e_emote->filterEmotes($sub_blk); 1869 } 1870 1871 1872 // Reduce newlines in all forms to a single newline character (finds '\n', '\r\n', '\n\r') 1873 if (!$opts['nobreak']) 1874 { 1875 if ($convertNL && ($this->preformatted($sub_blk) === false)) // eg. html or markdown 1876 { 1877 // We may need to convert to <br /> later 1878 $sub_blk = preg_replace("#[\r]*\n[\r]*#", E_NL, $sub_blk); 1879 } 1880 else 1881 { 1882 // Not doing any more - its HTML or Markdown so keep it as is. 1883 $sub_blk = preg_replace("#[\r]*\n[\r]*#", "\n", $sub_blk); 1884 } 1885 } 1886 1887 1888 // Entity conversion 1889 // Restore entity form of quotes and such to single characters, except for text destined for tag attributes or JS. 1890 if($opts['value']) 1891 { 1892 // output used for attribute values. 1893 $sub_blk = str_replace($this->replace, $this->search, $sub_blk); 1894 } 1895 else 1896 { 1897 // output not used for attribute values. 1898 $sub_blk = str_replace($this->search, $this->replace, $sub_blk); 1899 } 1900 1901 1902 // BBCode processing (other than the four already done, which shouldn't appear at all in the text) 1903 if ($parseBB !== FALSE) 1904 { 1905 if (!is_object($this->e_bb)) 1906 { 1907 require_once(e_HANDLER.'bbcode_handler.php'); 1908 $this->e_bb = new e_bbcode; 1909 } 1910 if ($parseBB === TRUE) 1911 { 1912 // 'Normal' or 'legacy' processing 1913 if($modifiers == "WYSIWYG") 1914 { 1915 $sub_blk = $this->e_bb->parseBBCodes($sub_blk, $postID, 'wysiwyg'); 1916 } 1917 else 1918 { 1919 $sub_blk = $this->e_bb->parseBBCodes($sub_blk, $postID); 1920 } 1921 1922 } 1923 elseif ($parseBB === 'STRIP') 1924 { 1925 // Need to strip all BBCodes 1926 $sub_blk = $this->e_bb->parseBBCodes($sub_blk, $postID, 'default', TRUE); 1927 } 1928 else 1929 { 1930 // Need to strip just some BBCodes 1931 $sub_blk = $this->e_bb->parseBBCodes($sub_blk, $postID, 'default', $parseBB); 1932 } 1933 } 1934 1935 1936 // replace all {e_XXX} constants with their e107 value. modifier determines relative/absolute conversion 1937 // (Moved to after bbcode processing by Cameron) 1938 if ($opts['constants']) 1939 { 1940 $sub_blk = $this->replaceConstants($sub_blk, $opts['constants']); // Now decodes text values 1941 } 1942 1943 1944 // profanity filter 1945 if (!empty($pref['profanity_filter'])) 1946 { 1947 if (!is_object($this->e_pf)) 1948 { 1949 // require_once(e_HANDLER."profanity_filter.php"); 1950 $this->e_pf = new e_profanityFilter; 1951 } 1952 $sub_blk = $this->e_pf->filterProfanities($sub_blk); 1953 } 1954 1955 1956 // Shortcodes 1957 // Optional short-code conversion 1958 if ($opts['parse_sc']) 1959 { 1960 $sub_blk = $this->parseTemplate($sub_blk, TRUE); 1961 } 1962 1963 1964 /** 1965 * / @deprecated 1966 */ 1967 if ($opts['hook']) //Run any hooked in parsers 1968 { 1969 if ( varset($pref['tohtml_hook'])) 1970 { 1971 //Process the older tohtml_hook pref (deprecated) 1972 foreach(explode(",", $pref['tohtml_hook']) as $hook) 1973 { 1974 if (!is_object($this->e_hook[$hook])) 1975 { 1976 if(is_readable(e_PLUGIN.$hook."/".$hook.".php")) 1977 { 1978 require_once(e_PLUGIN.$hook."/".$hook.".php"); 1979 $hook_class = "e_".$hook; 1980 $this->e_hook[$hook] = new $hook_class; 1981 } 1982 1983 } 1984 1985 if(is_object($this->e_hook[$hook])) // precaution for old plugins. 1986 { 1987 $sub_blk = $this->e_hook[$hook]->$hook($sub_blk,$opts['context']); 1988 } 1989 } 1990 } 1991 1992 /** 1993 * / @deprecated 1994 */ 1995 if(isset($pref['e_tohtml_list']) && is_array($pref['e_tohtml_list'])) 1996 { 1997 foreach($pref['e_tohtml_list'] as $hook) 1998 { 1999 if (!is_object($this->e_hook[$hook])) 2000 { 2001 if(is_readable(e_PLUGIN.$hook."/e_tohtml.php")) 2002 { 2003 require_once(e_PLUGIN.$hook."/e_tohtml.php"); 2004 2005 $hook_class = "e_tohtml_".$hook; 2006 2007 $this->e_hook[$hook] = new $hook_class; 2008 } 2009 } 2010 2011 if(is_object( $this->e_hook[$hook])) 2012 { 2013 /** @var e_tohtml_linkwords $deprecatedHook */ 2014 $deprecatedHook = $this->e_hook[$hook]; 2015 $sub_blk = $deprecatedHook->to_html($sub_blk, $opts['context']); 2016 } 2017 } 2018 } 2019 2020 /** 2021 * / Preferred 'hook' 2022 */ 2023 if(!empty($pref['e_parse_list'])) 2024 { 2025 foreach($pref['e_parse_list'] as $plugin) 2026 { 2027 $hookObj = e107::getAddon($plugin,'e_parse'); 2028 if($tmp = e107::callMethod($hookObj, 'toHTML', $sub_blk, $opts['context'])) 2029 { 2030 $sub_blk = $tmp; 2031 } 2032 2033 } 2034 2035 } 2036 2037 2038 2039 2040 2041 } 2042 2043 2044 // Word wrap 2045 if ($wrap && !$opts['nobreak']) 2046 { 2047 $sub_blk = $this->textclean($sub_blk, $wrap); 2048 } 2049 2050 2051 // Search highlighting 2052 if ($opts['emotes']) // Why?? 2053 { 2054 if ($this->checkHighlighting()) 2055 { 2056 $sub_blk = $this->e_highlight($sub_blk, $this->e_query); 2057 } 2058 } 2059 2060 2061 2062 2063 if($convertNL == true) 2064 { 2065 // Default replaces all \n with <br /> for HTML display 2066 $nl_replace = '<br />'; 2067 if ($opts['nobreak']) 2068 { 2069 $nl_replace = ''; 2070 } 2071 elseif ($opts['retain_nl']) 2072 { 2073 $nl_replace = "\n"; 2074 } 2075 2076 $sub_blk = str_replace(E_NL, $nl_replace, $sub_blk); 2077 } 2078 2079 $ret_parser .= $sub_blk; 2080 } // End of 'normal' processing for a block of text 2081 2082 } // End of 'foreach() on each block of non-script text 2083 2084 } // End of 'normal' parsing (non-script text) 2085 else 2086 { 2087 // Text block that needed no processing at all 2088 $ret_parser .= $full_text; 2089 } 2090 } 2091 2092 // Quick Fix - Remove trailing <br /> on block-level elements (eg. div, pre, table, etc. ) 2093 $srch = array(); 2094 $repl = array(); 2095 2096 foreach($this->blockTags as $val) 2097 { 2098 $srch[] = "</".$val."><br />"; 2099 $repl[] = "</".$val.">"; 2100 } 2101 2102 $ret_parser = str_replace($srch, $repl, $ret_parser); 2103 2104 return trim($ret_parser); 2105 } 2106 2107 2108 /** 2109 * Check if a string begins with a preformatter flag. 2110 * @param $str 2111 * @return bool 2112 */ 2113 private function preformatted($str) 2114 { 2115 foreach($this->preformatted as $type) 2116 { 2117 $code = '['.$type.']'; 2118 if(strpos($str, $code) === 0) 2119 { 2120 return true; 2121 } 2122 2123 } 2124 2125 return false; 2126 } 2127 2128 2129 /** 2130 * @param $mixed 2131 * @return array|false|string 2132 */ 2133 public function toUTF8($mixed) 2134 { 2135 2136 if(is_array($mixed)) 2137 { 2138 foreach($mixed as $k => $v) 2139 { 2140 unset($mixed[$k]); 2141 $mixed[$this->toUTF8($k)] = $this->toUTF8($v); 2142 } 2143 } 2144 elseif(is_object($mixed)) 2145 { 2146 $objVars = get_object_vars($mixed); 2147 foreach($objVars as $key => $value) 2148 { 2149 $mixed->$key = $this->toUTF8($value); 2150 } 2151 } 2152 elseif(is_string($mixed)) 2153 { 2154 return iconv('UTF-8', 'UTF-8//IGNORE', utf8_encode($mixed)); 2155 } 2156 2157 return $mixed; 2158 } 2159 2160 2161 function toASCII($text) 2162 { 2163 2164 $char_map = array( 2165 // Latin 2166 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Æ' => 'AE', 'Ç' => 'C', 2167 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I', 2168 'Ð' => 'D', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', 'Ő' => 'O', 2169 'Ø' => 'O', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ű' => 'U', 'Ý' => 'Y', 'Þ' => 'TH', 2170 'ß' => 'ss', 2171 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', 'æ' => 'ae', 'ç' => 'c', 2172 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 2173 'ð' => 'd', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ő' => 'o', 2174 'ø' => 'o', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u', 'ű' => 'u', 'ý' => 'y', 'þ' => 'th', 2175 'ÿ' => 'y', 2176 // Latin symbols 2177 '©' => '(c)', 2178 // Greek 2179 'Α' => 'A', 'Β' => 'B', 'Γ' => 'G', 'Δ' => 'D', 'Ε' => 'E', 'Ζ' => 'Z', 'Η' => 'H', 'Θ' => '8', 2180 'Ι' => 'I', 'Κ' => 'K', 'Λ' => 'L', 'Μ' => 'M', 'Ν' => 'N', 'Ξ' => '3', 'Ο' => 'O', 'Π' => 'P', 2181 'Ρ' => 'R', 'Σ' => 'S', 'Τ' => 'T', 'Υ' => 'Y', 'Φ' => 'F', 'Χ' => 'X', 'Ψ' => 'PS', 'Ω' => 'W', 2182 'Ά' => 'A', 'Έ' => 'E', 'Ί' => 'I', 'Ό' => 'O', 'Ύ' => 'Y', 'Ή' => 'H', 'Ώ' => 'W', 'Ϊ' => 'I', 2183 'Ϋ' => 'Y', 2184 'α' => 'a', 'β' => 'b', 'γ' => 'g', 'δ' => 'd', 'ε' => 'e', 'ζ' => 'z', 'η' => 'h', 'θ' => '8', 2185 'ι' => 'i', 'κ' => 'k', 'λ' => 'l', 'μ' => 'm', 'ν' => 'n', 'ξ' => '3', 'ο' => 'o', 'π' => 'p', 2186 'ρ' => 'r', 'σ' => 's', 'τ' => 't', 'υ' => 'y', 'φ' => 'f', 'χ' => 'x', 'ψ' => 'ps', 'ω' => 'w', 2187 'ά' => 'a', 'έ' => 'e', 'ί' => 'i', 'ό' => 'o', 'ύ' => 'y', 'ή' => 'h', 'ώ' => 'w', 'ς' => 's', 2188 'ϊ' => 'i', 'ΰ' => 'y', 'ϋ' => 'y', 'ΐ' => 'i', 2189 // Turkish 2190 'Ş' => 'S', 'İ' => 'I', 'Ç' => 'C', 'Ü' => 'U', 'Ö' => 'O', 'Ğ' => 'G', 2191 'ş' => 's', 'ı' => 'i', 'ç' => 'c', 'ü' => 'u', 'ö' => 'o', 'ğ' => 'g', 2192 // Russian 2193 'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D', 'Е' => 'E', 'Ё' => 'Yo', 'Ж' => 'Zh', 2194 'З' => 'Z', 'И' => 'I', 'Й' => 'J', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N', 'О' => 'O', 2195 'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T', 'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'C', 2196 'Ч' => 'Ch', 'Ш' => 'Sh', 'Щ' => 'Sh', 'Ъ' => '', 'Ы' => 'Y', 'Ь' => '', 'Э' => 'E', 'Ю' => 'Yu', 2197 'Я' => 'Ya', 2198 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e', 'ё' => 'yo', 'ж' => 'zh', 2199 'з' => 'z', 'и' => 'i', 'й' => 'j', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n', 'о' => 'o', 2200 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't', 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c', 2201 'ч' => 'ch', 'ш' => 'sh', 'щ' => 'sh', 'ъ' => '', 'ы' => 'y', 'ь' => '', 'э' => 'e', 'ю' => 'yu', 2202 'я' => 'ya', 2203 // Ukrainian 2204 'Є' => 'Ye', 'І' => 'I', 'Ї' => 'Yi', 'Ґ' => 'G', 2205 'є' => 'ye', 'і' => 'i', 'ї' => 'yi', 'ґ' => 'g', 2206 // Czech 2207 'Č' => 'C', 'Ď' => 'D', 'Ě' => 'E', 'Ň' => 'N', 'Ř' => 'R', 'Š' => 'S', 'Ť' => 'T', 'Ů' => 'U', 2208 'Ž' => 'Z', 2209 'č' => 'c', 'ď' => 'd', 'ě' => 'e', 'ň' => 'n', 'ř' => 'r', 'š' => 's', 'ť' => 't', 'ů' => 'u', 2210 'ž' => 'z', 2211 // Polish 2212 'Ą' => 'A', 'Ć' => 'C', 'Ę' => 'e', 'Ł' => 'L', 'Ń' => 'N', 'Ó' => 'o', 'Ś' => 'S', 'Ź' => 'Z', 2213 'Ż' => 'Z', 2214 'ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l', 'ń' => 'n', 'ó' => 'o', 'ś' => 's', 'ź' => 'z', 2215 'ż' => 'z', 2216 // Latvian 2217 'Ā' => 'A', 'Č' => 'C', 'Ē' => 'E', 'Ģ' => 'G', 'Ī' => 'i', 'Ķ' => 'k', 'Ļ' => 'L', 'Ņ' => 'N', 2218 'Š' => 'S', 'Ū' => 'u', 'Ž' => 'Z', 2219 'ā' => 'a', 'č' => 'c', 'ē' => 'e', 'ģ' => 'g', 'ī' => 'i', 'ķ' => 'k', 'ļ' => 'l', 'ņ' => 'n', 2220 'š' => 's', 'ū' => 'u', 'ž' => 'z', 2221 2222 'ľ' => 'l', 'ŕ'=>'r', 2223 ); 2224 2225 return str_replace(array_keys($char_map), $char_map, $text); 2226 2227 } 2228 2229 2230 2231 /** 2232 * Use it on html attributes to avoid breaking markup . 2233 * @example echo "<a href='#' title='".$tp->toAttribute($text)."'>Hello</a>"; 2234 */ 2235 function toAttribute($text) 2236 { 2237 // URLs posted without HTML access may have an & in them. 2238 2239 // Xhtml compliance. 2240 $text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); 2241 2242 if(!preg_match('/&#|\'|"|<|>/s', $text)) 2243 { 2244 $text = $this->replaceConstants($text); 2245 return $text; 2246 } 2247 else 2248 { 2249 return $text; 2250 } 2251 } 2252 2253 2254 /** 2255 * Convert text blocks which are to be embedded within JS 2256 * 2257 * @param string|array $stringarray 2258 * @return string 2259 */ 2260 public function toJS($stringarray) 2261 { 2262 $search = array("\r\n", "\r", "<br />", "'"); 2263 $replace = array("\\n", "", "\\n", "\'"); 2264 $stringarray = str_replace($search, $replace, $stringarray); 2265 $stringarray = strip_tags($stringarray); 2266 2267 $trans_tbl = get_html_translation_table(HTML_ENTITIES); 2268 $trans_tbl = array_flip($trans_tbl); 2269 2270 return strtr($stringarray, $trans_tbl); 2271 } 2272 2273 2274 /** 2275 * Converts a PHP variable into its JavaScript equivalent. 2276 * We use HTML-safe strings, with several characters escaped. 2277 * 2278 * @param mixed $var 2279 * PHP variable. 2280 * @param bool $force_object 2281 * True: Outputs an object rather than an array when a non-associative 2282 * array is used. Especially useful when the recipient of the output 2283 * is expecting an object and the array is empty. 2284 * 2285 * @return string 2286 */ 2287 public function toJSON($var, $force_object = false) 2288 { 2289 // The PHP version cannot change within a request. 2290 static $php530; 2291 2292 if(!isset($php530)) 2293 { 2294 $php530 = version_compare(PHP_VERSION, '5.3.0', '>='); 2295 } 2296 2297 if($php530) 2298 { 2299 if($force_object === true) 2300 { 2301 // Encode <, >, ', &, and " using the json_encode() options parameter. 2302 return json_encode($var, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_FORCE_OBJECT); 2303 } 2304 2305 // Encode <, >, ', &, and " using the json_encode() options parameter. 2306 return json_encode($var, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); 2307 } 2308 2309 return $this->toJSONhelper($var); 2310 } 2311 2312 2313 /** 2314 * Encodes a PHP variable to HTML-safe JSON for PHP versions below 5.3.0. 2315 * 2316 * @param mixed $var 2317 * @return string 2318 */ 2319 public function toJSONhelper($var) 2320 { 2321 switch(gettype($var)) 2322 { 2323 case 'boolean': 2324 return $var ? 'true' : 'false'; // Lowercase necessary! 2325 2326 case 'integer': 2327 case 'double': 2328 return $var; 2329 2330 case 'resource': 2331 case 'string': 2332 // Always use Unicode escape sequences (\u0022) over JSON escape 2333 // sequences (\") to prevent browsers interpreting these as 2334 // special characters. 2335 $replace_pairs = array( 2336 // ", \ and U+0000 - U+001F must be escaped according to RFC 4627. 2337 '\\' => '\u005C', 2338 '"' => '\u0022', 2339 "\x00" => '\u0000', 2340 "\x01" => '\u0001', 2341 "\x02" => '\u0002', 2342 "\x03" => '\u0003', 2343 "\x04" => '\u0004', 2344 "\x05" => '\u0005', 2345 "\x06" => '\u0006', 2346 "\x07" => '\u0007', 2347 "\x08" => '\u0008', 2348 "\x09" => '\u0009', 2349 "\x0a" => '\u000A', 2350 "\x0b" => '\u000B', 2351 "\x0c" => '\u000C', 2352 "\x0d" => '\u000D', 2353 "\x0e" => '\u000E', 2354 "\x0f" => '\u000F', 2355 "\x10" => '\u0010', 2356 "\x11" => '\u0011', 2357 "\x12" => '\u0012', 2358 "\x13" => '\u0013', 2359 "\x14" => '\u0014', 2360 "\x15" => '\u0015', 2361 "\x16" => '\u0016', 2362 "\x17" => '\u0017', 2363 "\x18" => '\u0018', 2364 "\x19" => '\u0019', 2365 "\x1a" => '\u001A', 2366 "\x1b" => '\u001B', 2367 "\x1c" => '\u001C', 2368 "\x1d" => '\u001D', 2369 "\x1e" => '\u001E', 2370 "\x1f" => '\u001F', 2371 // Prevent browsers from interpreting these as as special. 2372 "'" => '\u0027', 2373 '<' => '\u003C', 2374 '>' => '\u003E', 2375 '&' => '\u0026', 2376 // Prevent browsers from interpreting the solidus as special and 2377 // non-compliant JSON parsers from interpreting // as a comment. 2378 '/' => '\u002F', 2379 // While these are allowed unescaped according to ECMA-262, section 2380 // 15.12.2, they cause problems in some JSON parsers. 2381 "\xe2\x80\xa8" => '\u2028', // U+2028, Line Separator. 2382 "\xe2\x80\xa9" => '\u2029', // U+2029, Paragraph Separator. 2383 ); 2384 2385 return '"' . strtr($var, $replace_pairs) . '"'; 2386 2387 case 'array': 2388 // Arrays in JSON can't be associative. If the array is empty or if it 2389 // has sequential whole number keys starting with 0, it's not associative 2390 // so we can go ahead and convert it as an array. 2391 if(empty($var) || array_keys($var) === range(0, sizeof($var) - 1)) 2392 { 2393 $output = array(); 2394 foreach($var as $v) 2395 { 2396 $output[] = $this->toJSONhelper($v); 2397 } 2398 return '[ ' . implode(', ', $output) . ' ]'; 2399 } 2400 break; 2401 2402 // Otherwise, fall through to convert the array as an object. 2403 case 'object': 2404 $output = array(); 2405 foreach($var as $k => $v) 2406 { 2407 $output[] = $this->toJSONhelper(strval($k)) . ':' . $this->toJSONhelper($v); 2408 } 2409 return '{' . implode(', ', $output) . '}'; 2410 2411 default: 2412 return 'null'; 2413 } 2414 } 2415 2416 2417 /** 2418 * Convert Text for RSS/XML use. 2419 * 2420 * @param string $text 2421 * @param boolean $tags [optional] 2422 * @return string 2423 */ 2424 function toRss($text, $tags = false) 2425 { 2426 if($tags != true) 2427 { 2428 $text = $this->toHTML($text, true); 2429 $text = strip_tags($text); 2430 } 2431 2432 $text = $this->toEmail($text); 2433 2434 $search = array("&#039;", "&#036;", "'", "$", e_BASE, "href='request.php","<!-- bbcode-html-start -->","<!-- bbcode-html-end -->"); 2435 $replace = array("'", '$', "'", '$', SITEURL, "href='".SITEURL."request.php", '', '' ); 2436 $text = str_replace($search, $replace, $text); 2437 2438 $text = $this->ampEncode($text); 2439 2440 if($tags == true && ($text)) 2441 { 2442 $text = "<![CDATA[".$text."]]>"; 2443 } 2444 2445 return $text; 2446 } 2447 2448 2449 /** 2450 * Convert a string to a number (int/float) 2451 * 2452 * @param string $value 2453 * @return int|float 2454 */ 2455 function toNumber($value) 2456 { 2457 // adapted from: https://secure.php.net/manual/en/function.floatval.php#114486 2458 $dotPos = strrpos($value, '.'); 2459 $commaPos = strrpos($value, ','); 2460 $sep = (($dotPos > $commaPos) && $dotPos) ? $dotPos : 2461 ((($commaPos > $dotPos) && $commaPos) ? $commaPos : false); 2462 2463 if (!$sep) { 2464 return preg_replace("/[^-0-9]/", "", $value); 2465 } 2466 2467 return ( 2468 preg_replace("/[^-0-9]/", "", substr($value, 0, $sep)) . '.' . 2469 preg_replace("/[^0-9]/", "", substr($value, $sep+1, strlen($value))) 2470 ); 2471 } 2472 2473 2474 2475 /** 2476 * Clean and Encode Ampersands '&' for output to browser. 2477 * @param string $text 2478 * @return mixed|string 2479 */ 2480 function ampEncode($text='') 2481 { 2482 // Fix any left-over '&' 2483 $text = str_replace('&', '&', $text); //first revert any previously converted. 2484 $text = str_replace('&', '&', $text); 2485 2486 return $text; 2487 } 2488 2489 2490 /** 2491 * Convert any string back to plain text. 2492 * @param $text 2493 * @return mixed|string 2494 */ 2495 function toText($text) 2496 { 2497 2498 if($this->isBBcode($text) === true) // convert any bbcodes to html 2499 { 2500 $text = $this->toHTML($text,true); 2501 } 2502 2503 if($this->isHtml($text) === true) // strip any html. 2504 { 2505 $text = $this->toHTML($text,true); 2506 $text = str_replace("\n","",$text); // clean-out line-breaks. 2507 $text = str_ireplace( array("<br>","<br />","<br/>"), "\n", $text); 2508 $text = strip_tags($text); 2509 } 2510 2511 $search = array("&#039;", "&#036;", "'", "$", "\", "&#092;"); 2512 $replace = array("'", '$', "'", '$', "\\", "\\"); 2513 $text = str_replace($search, $replace, $text); 2514 return $text; 2515 } 2516 2517 2518 /** 2519 * Set the dimensions of a thumbNail (generated by thumbUrl) 2520 */ 2521 public function setThumbSize($w=null,$h=null,$crop=null) 2522 { 2523 if($w !== null) 2524 { 2525 $this->thumbWidth = intval($w); 2526 } 2527 2528 if($h !== null) 2529 { 2530 $this->thumbHeight = intval($h); 2531 } 2532 2533 if($crop !== null) 2534 { 2535 $this->thumbCrop = intval($crop); 2536 } 2537 2538 } 2539 2540 public function thumbEncode($val = null) 2541 { 2542 2543 if($val !== null) 2544 { 2545 $this->thumbEncode = intval($val); 2546 return null; 2547 } 2548 2549 return $this->thumbEncode; 2550 } 2551 2552 2553 /** 2554 * Retrieve img tag width and height attributes for current thumbnail. 2555 * @return string 2556 */ 2557 public function thumbDimensions($type = 'single') 2558 { 2559 if(!empty($this->thumbCrop) && !empty($this->thumbWidth) && !empty($this->thumbHeight)) // dimensions are known. 2560 { 2561 return ($type === 'double') ? 'width="'.$this->thumbWidth.'" height="'.$this->thumbHeight.'"' : "width='".$this->thumbWidth."' height='".$this->thumbHeight."'"; 2562 } 2563 2564 return null; 2565 } 2566 2567 2568 /** 2569 * Set or Get the value of the thumbNail Width. 2570 * @param $width (optional) 2571 */ 2572 public function thumbWidth($width=null) 2573 { 2574 if($width !== null) 2575 { 2576 $this->thumbWidth = intval($width); 2577 } 2578 2579 return $this->thumbWidth; 2580 } 2581 2582 /** 2583 * Set or Get the value of the thumbNailbCrop. 2584 * @param bool $status = true/false 2585 */ 2586 public function thumbCrop($status=false) 2587 { 2588 if($status !== false) 2589 { 2590 $this->thumbCrop = intval($status); 2591 } 2592 2593 return $this->thumbCrop; 2594 } 2595 2596 2597 2598 /** 2599 * Set or Get the value of the thumbNail height. 2600 * @param $height (optional) 2601 */ 2602 public function thumbHeight($height= null) 2603 { 2604 if($height !== null) 2605 { 2606 $this->thumbHeight = intval($height); 2607 } 2608 2609 return $this->thumbHeight; 2610 2611 } 2612 2613 /** 2614 * Generated a Thumb Cache File Name from path and options. 2615 * @param string $path 2616 * @param array $options 2617 * @return null|string 2618 */ 2619 public function thumbCacheFile($path, $options=null, $log=null) 2620 { 2621 if(empty($path)) 2622 { 2623 return null; 2624 } 2625 2626 if(is_string($options)) 2627 { 2628 parse_str($options,$options); 2629 } 2630 2631 $path = str_replace($this->getUrlConstants('raw'), $this->getUrlConstants('sc'), $path); 2632 $path = $this->replaceConstants(str_replace('..', '', $path)); 2633 2634 $filename = basename($path); 2635 $tmp = explode('.',$filename); 2636 $ext = end($tmp); 2637 $len = strlen($ext) + 1; 2638 $start = substr($filename,0,- $len); 2639 2640 2641 // cleanup. 2642 $newOpts = array( 2643 'w' => (string) intval($options['w']), 2644 'h' => (string) intval($options['h']), 2645 'aw' => (string) intval($options['aw']), 2646 'ah' => (string) intval($options['ah']), 2647 'c' => strtoupper(vartrue($options['c'],'0')) 2648 ); 2649 2650 if($log !== null) 2651 { 2652 file_put_contents(e_LOG.$log, "\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\n", FILE_APPEND); 2653 $message = $path."\n".print_r($newOpts,true)."\n\n\n"; 2654 file_put_contents(e_LOG.$log, $message, FILE_APPEND); 2655 2656 // file_put_contents(e_LOG.$log, "\t\tFOUND!!\n\n\n", FILE_APPEND); 2657 } 2658 2659 2660 if(!empty($options['aw'])) 2661 { 2662 $options['w'] = $options['aw']; 2663 } 2664 2665 if(!empty($options['ah'])) 2666 { 2667 $options['h'] = $options['ah']; 2668 } 2669 2670 2671 $size = varset($options['w'],0).'x'.varset($options['h'],0); 2672 2673 $thumbQuality = e107::getPref('thumbnail_quality',65); 2674 2675 $cache_str = md5(serialize($newOpts).$path. $thumbQuality); 2676 2677 $pre = 'thumb_'; 2678 $post = '.cache.bin'; 2679 2680 // $cache_str = http_build_query($newOpts,null,'_'); // testing files. 2681 2682 if(defined('e_MEDIA_STATIC')) // experimental - subject to change. 2683 { 2684 $pre = ''; 2685 $post = ''; 2686 } 2687 2688 $fname = $pre.strtolower($start.'_'.$cache_str.'_'.$size.'.'.$ext).$post; 2689 2690 return $fname; 2691 } 2692 2693 2694 2695 private function staticCount($val=false) 2696 { 2697 2698 $count = $this->staticCount; 2699 2700 if($val === 0) 2701 { 2702 $this->staticCount = 0; 2703 } 2704 elseif($val !== false) 2705 { 2706 $this->staticCount = $this->staticCount + (int) $val; 2707 } 2708 2709 return (int) $count; 2710 2711 } 2712 2713 2714 /** 2715 * @todo Move to e107_class ? 2716 * @param string $path - absolute path 2717 * @return string - static path. 2718 */ 2719 public function staticUrl($path=null, $opts=array()) 2720 { 2721 if(!defined('e_HTTP_STATIC') || deftrue('e_ADMIN_AREA')) 2722 { 2723 // e107::getDebug()->log("e_HTTP_STATIC not defined"); 2724 if($path === null) 2725 { 2726 return !empty($opts['full']) ? SITEURL : e_HTTP; 2727 } 2728 else 2729 { 2730 return $path; 2731 } 2732 } 2733 2734 2735 $staticArray = e_HTTP_STATIC; 2736 2737 if(is_array($staticArray)) 2738 { 2739 $cnt = count($staticArray); 2740 $staticCount = $this->staticCount(); 2741 if($staticCount > ($cnt -1)) 2742 { 2743 $staticCount = 0; 2744 $this->staticCount(0); 2745 } 2746 2747 $http = !empty($staticArray[$staticCount]) ? $staticArray[$staticCount] : e_HTTP; 2748 2749 } 2750 else 2751 { 2752 $http = e_HTTP_STATIC; 2753 } 2754 2755 $this->staticCount(1); 2756 2757 if(empty($path)) 2758 { 2759 return $http; 2760 } 2761 2762 $base = ''; 2763 2764 $srch = array( 2765 // 2766 e_PLUGIN_ABS, 2767 e_THEME_ABS, 2768 e_WEB_ABS, 2769 e_CACHE_IMAGE_ABS, 2770 ); 2771 2772 2773 $repl = array( 2774 2775 $http.$base.e107::getFolder('plugins'), 2776 $http.$base.e107::getFolder('themes'), 2777 $http.$base.e107::getFolder('web'), 2778 $http.$base.str_replace('../', '', e_CACHE_IMAGE), 2779 ); 2780 2781 $ret = str_replace($srch,$repl,$path); 2782 2783 if(strpos($ret, 'http') !== 0) // if not converted, check media folder also. 2784 { 2785 $ret = str_replace(e_MEDIA_ABS,$http.$base.e107::getFolder('media'),$ret); 2786 } 2787 2788 return $ret; 2789 2790 } 2791 2792 2793 /** 2794 * Generate an auto-sized Image URL. 2795 * @param $url - path to image or leave blank for a placeholder. eg. {e_MEDIA}folder/my-image.jpg 2796 * @param array $options - width and height, but leaving this empty and using $this->thumbWidth() and $this->thumbHeight() is preferred. ie. {SETWIDTH: w=x&y=x} 2797 * @param int $options ['w'] width (optional) 2798 * @param int $options ['h'] height (optional) 2799 * @param bool|string $options ['crop'] true/false or A(auto) or T(op) or B(ottom) or C(enter) or L(eft) or R(right) 2800 * @param string $options ['scale'] '2x' (optional) 2801 * @param bool $options ['x'] encode/mask the url parms (optional) 2802 * @param bool $options ['nosef'] when set to true disabled SEF Url being returned (optional) 2803 * @param bool $raw set to true when the $url does not being with an e107 variable ie. "{e_XXXX}" eg. {e_MEDIA} (optional) 2804 * @param bool $full when true returns full http:// url. (optional) 2805 * @return string 2806 */ 2807 public function thumbUrl($url=null, $options = array(), $raw = false, $full = false) 2808 { 2809 $this->staticCount++; // increment counter. 2810 2811 $ext = pathinfo($url, PATHINFO_EXTENSION); 2812 2813 if($ext === 'svg') 2814 { 2815 return $this->replaceConstants($url, 'abs'); 2816 } 2817 2818 if(strpos($url,"{e_") === 0) // Fix for broken links that use {e_MEDIA} etc. 2819 { 2820 //$url = $this->replaceConstants($url,'abs'); 2821 // always switch to 'nice' urls when SC is used 2822 $url = str_replace($this->getUrlConstants('sc'), $this->getUrlConstants('raw'), $url); 2823 } 2824 2825 if(is_string($options)) 2826 { 2827 parse_str($options, $options); 2828 } 2829 2830 if(!empty($options['scale'])) // eg. scale the width height 2x 3x 4x. etc. 2831 { 2832 $options['return'] = 'src'; 2833 $options['size'] = $options['scale']; 2834 unset($options['scale']); 2835 return $this->thumbSrcSet($url,$options); 2836 } 2837 2838 2839 2840 2841 2842 if(strstr($url,e_MEDIA) || strstr($url,e_SYSTEM)) // prevent disclosure of 'hashed' path. 2843 { 2844 $raw = true; 2845 } 2846 2847 if($raw) $url = $this->createConstants($url, 'mix'); 2848 2849 $baseurl = ($full ? SITEURL : e_HTTP).'thumb.php?'; 2850 2851 if(defined('e_HTTP_STATIC')) 2852 { 2853 $baseurl = $this->staticUrl().'thumb.php?'; 2854 } 2855 2856 $thurl = 'src='.urlencode($url).'&'; 2857 2858 // e107::getDebug()->log("Thumb: ".basename($url). print_a($options,true), E107_DBG_BASIC); 2859 2860 if(!empty($options) && (isset($options['w']) || isset($options['aw']) || isset($options['h']))) 2861 { 2862 $options['w'] = varset($options['w']); 2863 $options['h'] = varset($options['h']); 2864 $options['crop'] = (isset($options['aw']) || isset($options['ah'])) ? 1 : varset($options['crop']); 2865 $options['aw'] = varset($options['aw']); 2866 $options['ah'] = varset($options['ah']); 2867 $options['x'] = varset($options['x']); 2868 } 2869 else 2870 { 2871 $options['w'] = $this->thumbWidth; 2872 $options['h'] = $this->thumbHeight; 2873 $options['crop'] = $this->thumbCrop; 2874 $options['aw'] = null; 2875 $options['ah'] = null; 2876 $options['x'] = $this->thumbEncode; 2877 2878 } 2879 2880 2881 if(!empty($options['crop'])) 2882 { 2883 if(!empty($options['aw']) || !empty($options['ah'])) 2884 { 2885 $options['w'] = $options['aw'] ; 2886 $options['h'] = $options['ah'] ; 2887 } 2888 2889 $thurl .= 'aw='.intval($options['w']).'&ah='.intval($options['h']); 2890 2891 if(!is_numeric($options['crop'])) 2892 { 2893 $thurl .= '&c='.$options['crop']; 2894 $options['nosef'] = true; 2895 } 2896 2897 } 2898 else 2899 { 2900 2901 $thurl .= 'w='.intval($options['w']).'&h='.intval($options['h']); 2902 2903 } 2904 2905 2906 if(defined('e_MEDIA_STATIC')) // experimental - subject to change. 2907 { 2908 $opts = str_replace('&', '&', $thurl); 2909 2910 $staticFile = $this->thumbCacheFile($url, $opts); 2911 2912 2913 2914 if(!empty($staticFile) && is_readable(e_CACHE_IMAGE.$staticFile)) 2915 { 2916 $staticImg = $this->staticUrl(e_CACHE_IMAGE_ABS.$staticFile); 2917 // var_dump($staticImg); 2918 return $staticImg; 2919 } 2920 2921 // echo "<br />static-not-found: ".$staticFile; 2922 2923 $options['nosef'] = true; 2924 $options['x'] = null; 2925 // file_put_contents(e_LOG."thumb.log", "\n++++++++++++++++++++++++++++++++++\n\n", FILE_APPEND); 2926 } 2927 2928 2929 if(e_MOD_REWRITE_MEDIA == true && empty($options['nosef']) )// Experimental SEF URL support. 2930 { 2931 $options['full'] = $full; 2932 $options['ext'] = substr($url,-3); 2933 $options['thurl'] = $thurl; 2934 // $options['x'] = $this->thumbEncode(); 2935 2936 if($sefUrl = $this->thumbUrlSEF($url,$options)) 2937 { 2938 return $sefUrl; 2939 } 2940 } 2941 2942 if(!empty($options['x'] ))//base64 encode url 2943 { 2944 $thurl = 'id='.base64_encode($thurl); 2945 } 2946 2947 return $baseurl.$thurl; 2948 } 2949 2950 2951 2952 /** 2953 * Split a thumb.php url into an array which can be parsed back into the thumbUrl method. . 2954 * @param $src 2955 * @return array 2956 */ 2957 function thumbUrlDecode($src) 2958 { 2959 list($url,$qry) = array_pad(explode("?",$src), 2, null); 2960 2961 $ret = array(); 2962 2963 if(strstr($url,"thumb.php") && !empty($qry)) // Regular 2964 { 2965 parse_str($qry,$val); 2966 $ret = $val; 2967 } 2968 elseif(preg_match('/media\/img\/(a)?([\d]*)x(a)?([\d]*)\/(.*)/',$url,$match)) // SEF 2969 { 2970 $wKey = $match[1].'w'; 2971 $hKey = $match[3].'h'; 2972 2973 $ret = array( 2974 'src'=> 'e_MEDIA_IMAGE/'.$match[5], 2975 $wKey => $match[2], 2976 $hKey => $match[4] 2977 ); 2978 } 2979 elseif(preg_match('/theme\/img\/(a)?([\d]*)x(a)?([\d]*)\/(.*)/', $url, $match)) // Theme-image SEF Urls 2980 { 2981 $wKey = $match[1].'w'; 2982 $hKey = $match[3].'h'; 2983 2984 $ret = array( 2985 'src'=> 'e_THEME/'.$match[5], 2986 $wKey => $match[2], 2987 $hKey => $match[4] 2988 ); 2989 2990 } 2991 elseif(defined('TINYMCE_DEBUG')) 2992 { 2993 print_a("thumbUrlDecode: No Matches"); 2994 2995 } 2996 2997 2998 return $ret; 2999 } 3000 3001 3002 3003 /** 3004 * Experimental: Generate a Thumb URL for use in the img srcset attribute. 3005 * @param string $src eg. {e_MEDIA_IMAGE}myimage.jpg 3006 * @param int|string|array $width - desired size in px or '2x' or '3x' or null for all or array ( 3007 * @return string 3008 */ 3009 function thumbSrcSet($src='', $width=null) 3010 { 3011 $multiply = null; 3012 $encode = false; 3013 3014 if(is_array($width)) 3015 { 3016 $parm = $width; 3017 $multiply = $width['size']; 3018 $encode = (!empty($width['x'])) ? $width['x'] : false; 3019 $width = $width['size']; 3020 } 3021 3022 3023 // $encode = $this->thumbEncode();; 3024 if($width == null || $width=='all') 3025 { 3026 $links = array(); 3027 $mag = ($width == null) ? array(1, 2) : array(160,320,460,600,780,920,1100); 3028 foreach($mag as $v) 3029 { 3030 $w = ($this->thumbWidth * $v); 3031 $h = ($this->thumbHeight * $v); 3032 3033 $att = (!empty($this->thumbCrop)) ? array('aw' => $w, 'ah' => $h) : array('w' => $w, 'h' => $h); 3034 $att['x'] = $encode; 3035 3036 $add = ($width == null) ? " ".$v."x" : " ".$v."w"; 3037 $links[] = $this->thumbUrl($src, $att).$add; // " w".$width; // 3038 } 3039 3040 return implode(", ",$links); 3041 3042 } 3043 elseif($multiply === '2x' || $multiply === '3x' || $multiply === '4x') 3044 { 3045 $multiInt = (int) $multiply; 3046 3047 if(empty($parm['w']) && isset($parm['h'])) 3048 { 3049 $parm['h'] = ($parm['h'] * $multiInt) ; 3050 return $this->thumbUrl($src, $parm)." ".$multiply; 3051 } 3052 3053 if(isset($parm['w']) && !isset($parm['h'])) // if w set, assume h value of 0 is set. 3054 { 3055 $parm['h'] = 0; 3056 } 3057 3058 $width = !empty($parm['w']) ? (intval($parm['w']) * $multiInt) : (intval($this->thumbWidth) * $multiInt); 3059 $height = isset($parm['h']) ? (intval($parm['h']) * $multiInt) : (intval($this->thumbHeight) * $multiInt); 3060 3061 } 3062 else 3063 { 3064 $height = (($this->thumbHeight * $width) / $this->thumbWidth); 3065 3066 } 3067 3068 3069 3070 if(!isset($parm['aw'])) 3071 { 3072 $parm['aw'] = null; 3073 } 3074 3075 if(!isset($parm['ah'])) 3076 { 3077 $parm['ah'] = null; 3078 } 3079 3080 if(!isset($parm['x'])) 3081 { 3082 $parm['x'] = null; 3083 } 3084 3085 if(!isset($parm['crop'])) 3086 { 3087 $parm['crop'] = null; 3088 } 3089 3090 $parms = array('w'=>$width,'h'=>$height,'crop'=> $parm['crop'],'x'=>$parm['x'], 'aw'=>$parm['aw'],'ah'=>$parm['ah']); 3091 3092 // $parms = !empty($this->thumbCrop) ? array('aw' => $width, 'ah' => $height, 'x'=>$encode) : array('w' => $width, 'h' => $height, 'x'=>$encode ); 3093 3094 // $parms['x'] = $encode; 3095 3096 if(!empty($parm['return']) && $parm['return'] === 'src') 3097 { 3098 return $this->thumbUrl($src, $parms); 3099 } 3100 3101 $ret = $this->thumbUrl($src, $parms); 3102 3103 $ret .= ($multiply) ? " ".$multiply : " ".$width."w"; 3104 3105 return $ret; 3106 3107 } 3108 3109 3110 public function thumbUrlScale($src,$parm) 3111 { 3112 3113 3114 3115 } 3116 3117 /** 3118 * Used by thumbUrl when SEF Image URLS is active. @see e107.htaccess 3119 * @param $url 3120 * @param array $options 3121 * @return string 3122 */ 3123 private function thumbUrlSEF($url='', $options=array()) 3124 { 3125 if(!empty($options['full'])) 3126 { 3127 $base = SITEURL; 3128 } 3129 else 3130 { 3131 $base = (!empty($options['ebase'])) ? '{e_BASE}' : e_HTTP; 3132 } 3133 3134 if(defined('e_HTTP_STATIC')) 3135 { 3136 $base = $this->staticUrl(); 3137 } 3138 // $base = (!empty($options['full'])) ? SITEURL : e_HTTP; 3139 3140 if(!empty($options['x']) && !empty($options['ext'])) // base64 encoded. Build URL for: RewriteRule ^media\/img\/([-A-Za-z0-9+/]*={0,3})\.(jpg|gif|png)?$ thumb.php?id=$1 3141 { 3142 $ext = strtolower($options['ext']); 3143 return $base.'media/img/'.base64_encode($options['thurl']).'.'.str_replace("jpeg", "jpg", $ext); 3144 } 3145 elseif(strstr($url, 'e_MEDIA_IMAGE')) // media images. 3146 { 3147 $sefPath = 'media/img/'; 3148 $clean = array('{e_MEDIA_IMAGE}','e_MEDIA_IMAGE/'); 3149 } 3150 elseif(strstr($url, 'e_AVATAR')) // avatars 3151 { 3152 $sefPath = 'media/avatar/'; 3153 $clean = array('{e_AVATAR}','e_AVATAR/'); 3154 } 3155 elseif(strstr($url, 'e_THEME')) // theme folder images. 3156 { 3157 $sefPath = 'theme/img/'; 3158 $clean = array('{e_THEME}','e_THEME/'); 3159 } 3160 else 3161 { 3162 // e107::getDebug()->log("SEF URL False: ".$url); 3163 return false; 3164 } 3165 3166 // Build URL for ReWriteRule ^media\/img\/(a)?([\d]*)x(a)?([\d]*)\/(.*)?$ thumb.php?src=e_MEDIA_IMAGE/$5&$1w=$2&$3h=$4 3167 $sefUrl = $base.$sefPath; 3168 3169 if(vartrue($options['aw']) || vartrue($options['ah'])) 3170 { 3171 $sefUrl .= 'a'.intval($options['aw']) .'xa'. intval($options['ah']); 3172 } 3173 elseif(!empty($options['crop'])) 3174 { 3175 3176 if(!is_numeric($options['crop'])) 3177 { 3178 $sefUrl .= strtolower($options['crop']).intval($options['w']) .'x'.strtolower($options['crop']). intval($options['h']); 3179 } 3180 else 3181 { 3182 $sefUrl .= 'a'.intval($options['w']) .'xa'. intval($options['h']); 3183 } 3184 3185 3186 } 3187 else 3188 { 3189 $sefUrl .= intval($options['w']) .'x'. intval($options['h']); 3190 } 3191 3192 $sefUrl .= '/'; 3193 $sefUrl .= str_replace($clean,'',$url); 3194 3195 return $sefUrl; 3196 3197 } 3198 3199 /** 3200 * Help for converting to more safe URLs 3201 * e.g. {e_MEDIA_FILE}path/to/video.flv => e_MEDIA_FILE/path/to/video.flv 3202 * 3203 * @todo support for ALL URL shortcodes (replacement methods) 3204 * @param string $type sc|raw|rev|all 3205 * @return array 3206 */ 3207 public function getUrlConstants($type = 'sc') 3208 { 3209 // sub-folders first! 3210 static $array = array( 3211 'e_MEDIA_FILE/' => '{e_MEDIA_FILE}', 3212 'e_MEDIA_VIDEO/' => '{e_MEDIA_VIDEO}', 3213 'e_MEDIA_IMAGE/' => '{e_MEDIA_IMAGE}', 3214 'e_MEDIA_ICON/' => '{e_MEDIA_ICON}', 3215 'e_AVATAR/' => '{e_AVATAR}', 3216 'e_AVATAR_DEFAULT/' => '{e_AVATAR_DEFAULT}', 3217 'e_AVATAR_UPLOAD/' => '{e_AVATAR_UPLOAD}', 3218 'e_WEB_JS/' => '{e_WEB_JS}', 3219 'e_WEB_CSS/' => '{e_WEB_CSS}', 3220 'e_WEB_IMAGE/' => '{e_WEB_IMAGE}', 3221 'e_IMPORT/' => '{e_IMPORT}', 3222 // 'e_WEB_PACK/' => '{e_WEB_PACK}', 3223 3224 'e_BASE/' => '{e_BASE}', 3225 'e_ADMIN/' => '{e_ADMIN}', 3226 'e_IMAGE/' => '{e_IMAGE}', 3227 'e_THEME/' => '{e_THEME}', 3228 'e_PLUGIN/' => '{e_PLUGIN}', 3229 'e_HANDLER/' => '{e_HANDLER}', // BC 3230 'e_MEDIA/' => '{e_MEDIA}', 3231 'e_WEB/' => '{e_ADMIN}', 3232 // 'THEME/' => '{THEME}', 3233 ); 3234 3235 3236 switch ($type) 3237 { 3238 case 'sc': 3239 return array_values($array); 3240 break; 3241 3242 case 'raw': 3243 return array_keys($array); 3244 break; 3245 3246 case 'rev': 3247 return array_reverse($array, true); 3248 break; 3249 3250 case 'all': 3251 return $array; 3252 break; 3253 } 3254 return array(); 3255 } 3256 3257 3258 function getEmotes() 3259 { 3260 return $this->e_emote->emotes; 3261 } 3262 3263 3264 /** 3265 * Replace e107 path constants 3266 * Note: only an ADMIN user can convert {e_ADMIN} 3267 * TODO - runtime cache of search/replace arrays (object property) when $mode !== '' 3268 * @param string $text 3269 * @param string $mode [optional] abs|full "full" = produce absolute URL path, e.g. http://sitename.com/e107_plugins/etc 3270 * 'abs' = produce truncated URL path, e.g. e107plugins/etc 3271 * "" (default) = URL's get relative path e.g. ../e107_plugins/etc 3272 * @param mixed $all [optional] if TRUE, then when $mode is "full" or TRUE, USERID is also replaced... 3273 * when $mode is "" (default), ALL other e107 constants are replaced 3274 * @return array|string 3275 */ 3276 public function replaceConstants($text, $mode = '', $all = FALSE) 3277 { 3278 if(is_array($text)) 3279 { 3280 $new = array(); 3281 foreach($text as $k=>$v) 3282 { 3283 $new[$k] = $this->replaceConstants($v,$mode,$all); 3284 } 3285 3286 return $new; 3287 } 3288 3289 if($mode != "") 3290 { 3291 $e107 = e107::getInstance(); 3292 3293 $replace_relative = array( 3294 $e107->getFolder('media_files'), 3295 $e107->getFolder('media_video'), 3296 $e107->getFolder('media_image'), 3297 $e107->getFolder('media_icon'), 3298 $e107->getFolder('avatars'), 3299 $e107->getFolder('web_js'), 3300 $e107->getFolder('web_css'), 3301 $e107->getFolder('web_image'), 3302 //$e107->getFolder('web_pack'), 3303 e_IMAGE_ABS, 3304 e_THEME_ABS, 3305 $e107->getFolder('images'), 3306 $e107->getFolder('plugins'), 3307 $e107->getFolder('files'), 3308 $e107->getFolder('themes'), 3309 // $e107->getFolder('downloads'), 3310 $e107->getFolder('handlers'), 3311 $e107->getFolder('media'), 3312 $e107->getFolder('web'), 3313 $e107->site_theme ? $e107->getFolder('themes').$e107->site_theme.'/' : '', 3314 defset('THEME_ABS'), 3315 (ADMIN ? $e107->getFolder('admin') : ''), 3316 '', 3317 $e107->getFolder('core'), 3318 $e107->getFolder('system'), 3319 ); 3320 3321 switch ($mode) 3322 { 3323 case 'abs': 3324 $replace_absolute = array( 3325 e_MEDIA_FILE_ABS, 3326 e_MEDIA_VIDEO_ABS, 3327 e_MEDIA_IMAGE_ABS, 3328 e_MEDIA_ICON_ABS, 3329 e_AVATAR_ABS, 3330 e_JS_ABS, 3331 e_CSS_ABS, 3332 e_WEB_IMAGE_ABS, 3333 // e_PACK_ABS, 3334 e_IMAGE_ABS, 3335 e_THEME_ABS, 3336 e_IMAGE_ABS, 3337 e_PLUGIN_ABS, 3338 e_FILE_ABS, 3339 e_THEME_ABS, 3340 // e_DOWNLOAD_ABS, //impossible when download is done via php. 3341 '', // handlers - no ABS path available 3342 e_MEDIA_ABS, 3343 e_WEB_ABS, 3344 defset('THEME_ABS'), 3345 defset('THEME_ABS'), 3346 (ADMIN ? e_ADMIN_ABS : ''), 3347 $e107->server_path, 3348 '', // no e_CORE absolute path 3349 '', // no e_SYSTEM absolute path 3350 ); 3351 break; 3352 3353 case 'full': 3354 $replace_absolute = array( 3355 SITEURLBASE.e_MEDIA_FILE_ABS, 3356 SITEURLBASE.e_MEDIA_VIDEO_ABS, 3357 SITEURLBASE.e_MEDIA_IMAGE_ABS, 3358 SITEURLBASE.e_MEDIA_ICON_ABS, 3359 SITEURLBASE.e_AVATAR_ABS, 3360 SITEURLBASE.e_JS_ABS, 3361 SITEURLBASE.e_CSS_ABS, 3362 SITEURLBASE.e_WEB_IMAGE_ABS, 3363 // SITEURLBASE.e_PACK_ABS, 3364 SITEURLBASE.e_IMAGE_ABS, 3365 SITEURLBASE.e_THEME_ABS, 3366 SITEURLBASE.e_IMAGE_ABS, 3367 SITEURLBASE.e_PLUGIN_ABS, 3368 SITEURLBASE.e_FILE_ABS, // deprecated 3369 SITEURLBASE.e_THEME_ABS, 3370 //SITEURL.$e107->getFolder('downloads'), 3371 '', // handlers - no ABS path available 3372 SITEURLBASE.e_MEDIA_ABS, 3373 SITEURLBASE.e_WEB_ABS, 3374 defset('THEME_ABS') ? SITEURLBASE.THEME_ABS : '', 3375 defset('THEME_ABS') ? SITEURLBASE.THEME_ABS : '', 3376 (ADMIN ? SITEURLBASE.e_ADMIN_ABS : ''), 3377 SITEURL, 3378 '', // no e_CORE absolute path 3379 '', // no e_SYSTEM absolute path 3380 ); 3381 break; 3382 } 3383 // sub-folders first! 3384 $search = array( 3385 '{e_MEDIA_FILE}', 3386 '{e_MEDIA_VIDEO}', 3387 '{e_MEDIA_IMAGE}', 3388 '{e_MEDIA_ICON}', 3389 '{e_AVATAR}', 3390 '{e_WEB_JS}', 3391 '{e_WEB_CSS}', 3392 '{e_WEB_IMAGE}', 3393 // '{e_WEB_PACK}', 3394 "{e_IMAGE_ABS}", 3395 "{e_THEME_ABS}", 3396 "{e_IMAGE}", 3397 "{e_PLUGIN}", 3398 "{e_FILE}", 3399 "{e_THEME}", 3400 //,"{e_DOWNLOAD}" 3401 "{e_HANDLER}", 3402 "{e_MEDIA}", 3403 "{e_WEB}", 3404 "{THEME}", 3405 "{THEME_ABS}", 3406 "{e_ADMIN}", 3407 "{e_BASE}", 3408 "{e_CORE}", 3409 "{e_SYSTEM}", 3410 ); 3411 3412 /*if (ADMIN) 3413 { 3414 $replace_relative[] = $e107->getFolder('admin'); 3415 $replace_absolute[] = SITEURL.$e107->getFolder('admin'); 3416 $search[] = "{e_ADMIN}"; 3417 }*/ 3418 3419 if ($all) 3420 { 3421 if (USER) 3422 { // Can only replace with valid number for logged in users 3423 $replace_relative[] = USERID; 3424 $replace_absolute[] = USERID; 3425 } 3426 else 3427 { 3428 $replace_relative[] = ''; 3429 $replace_absolute[] = ''; 3430 } 3431 $search[] = "{USERID}"; 3432 } 3433 3434 // current THEME 3435 /*if(!defined('THEME')) 3436 { 3437 //if not already parsed by doReplace 3438 $text = str_replace(array('{THEME}', '{THEME_ABS}'), '', $text); 3439 } 3440 else 3441 { 3442 $replace_relative[] = THEME; 3443 $replace_absolute[] = THEME_ABS; 3444 $search[] = "{THEME}"; 3445 $replace_relative[] = THEME; 3446 $replace_absolute[] = THEME_ABS; 3447 $search[] = "{THEME_ABS}"; 3448 }*/ 3449 3450 $replace = ((string)$mode == "full" || (string)$mode=='abs' ) ? $replace_absolute : $replace_relative; 3451 return str_replace($search,$replace,$text); 3452 } 3453 3454// $pattern = ($all ? "#\{([A-Za-z_0-9]*)\}#s" : "#\{(e_[A-Z]*)\}#s"); 3455 $pattern = ($all ? '#\{([A-Za-z_0-9]*)\}#s' : '#\{(e_[A-Z]*(?:_IMAGE|_VIDEO|_FILE|_CONTENT|_ICON|_AVATAR|_JS|_CSS|_PACK|_DB|_ABS){0,1})\}#s'); 3456 $text = preg_replace_callback($pattern, array($this, 'doReplace'), $text); 3457 3458 if(!defined('THEME')) 3459 { 3460 //if not already parsed by doReplace 3461 $text = str_replace(array('{THEME}', '{THEME_ABS}'), '', $text); 3462 } 3463 else 3464 { 3465 $srch = array('{THEME}', '{THEME_ABS}'); 3466 $repl = array(THEME, THEME_ABS); 3467 $text = str_replace($srch, $repl, $text); 3468 } 3469 3470 return $text; 3471 } 3472 3473 3474 function doReplace($matches) 3475 { 3476 if(defined($matches[1]) && (deftrue('ADMIN') || strpos($matches[1], 'ADMIN') === FALSE)) 3477 { 3478 return constant($matches[1]); 3479 } 3480 return $matches[1]; 3481 } 3482 3483 /** 3484 * Create and substitute e107 constants in passed URL 3485 * 3486 * @param string $url 3487 * @param integer $mode 0-folders, 1-relative ('rel'), 2-absolute ('abs'), 3-full ('full') (with domain), 4-absolute & relative ('mix') (combination of 1,2,3) 3488 * @return string 3489 */ 3490 public function createConstants($url, $mode = 0) 3491 { 3492 3493 //FIXME - create constants for absolute paths and site URL's 3494 if (!is_numeric($mode)) 3495 { 3496 switch ($mode) 3497 { 3498 case 'rel' : $mode = 1; break; 3499 case 'abs' : $mode = 2; break; 3500 case 'full' : $mode = 3; break; 3501 case 'mix' : $mode = 4; break; 3502 case 'nice': $mode = 5; break; 3503 } 3504 } 3505 $e107 = e107::getInstance(); 3506 switch($mode) 3507 { 3508 case 0: // folder name only. 3509 $tmp = array( 3510 '{e_MEDIA_FILE}' => $e107->getFolder('media_files'), 3511 '{e_MEDIA_VIDEO}' => $e107->getFolder('media_videos'), 3512 '{e_MEDIA_IMAGE}' => $e107->getFolder('media_images'), 3513 '{e_MEDIA_ICON}' => $e107->getFolder('media_icons'), 3514 '{e_AVATAR}' => $e107->getFolder('avatars'), 3515 '{e_WEB_JS}' => $e107->getFolder('web_js'), 3516 '{e_WEB_CSS}' => $e107->getFolder('web_css'), 3517 '{e_WEB_IMAGE}' => $e107->getFolder('web_images'), 3518 // '{e_WEB_PACK}' => $e107->getFolder('web_packs'), 3519 3520 '{e_IMAGE}' => $e107->getFolder('images'), 3521 '{e_PLUGIN}' => $e107->getFolder('plugins'), 3522 '{e_FILE}' => $e107->getFolder('files'), 3523 '{e_THEME}' => $e107->getFolder('themes'), 3524 '{e_DOWNLOAD}' => $e107->getFolder('downloads'), 3525 '{e_ADMIN}' => $e107->getFolder('admin'), 3526 '{e_HANDLER}' => $e107->getFolder('handlers'), 3527 '{e_MEDIA}' => $e107->getFolder('media'), 3528 '{e_WEB}' => $e107->getFolder('web'), 3529 '{e_UPLOAD}' => $e107->getFolder('uploads'), 3530 ); 3531 3532 break; 3533 3534 3535 3536 case 1: // relative path only 3537 $tmp = array( 3538 '{e_MEDIA_FILE}' => e_MEDIA_FILE, 3539 '{e_MEDIA_VIDEO}' => e_MEDIA_VIDEO, 3540 '{e_MEDIA_IMAGE}' => e_MEDIA_IMAGE, 3541 '{e_MEDIA_ICON}' => e_MEDIA_ICON, 3542 '{e_AVATAR}' => e_AVATAR, 3543 '{e_IMPORT}' => e_IMPORT, 3544 '{e_WEB_JS}' => e_WEB_JS, 3545 '{e_WEB_CSS}' => e_WEB_CSS, 3546 '{e_WEB_IMAGE}' => e_WEB_IMAGE, 3547 // '{e_WEB_PACK}' => e_WEB_PACK, 3548 3549 '{e_IMAGE}' => e_IMAGE, 3550 '{e_PLUGIN}' => e_PLUGIN, 3551 '{e_FILE}' => e_FILE, 3552 '{e_THEME}' => e_THEME, 3553 '{e_DOWNLOAD}' => e_DOWNLOAD, 3554 '{e_ADMIN}' => e_ADMIN, 3555 '{e_HANDLER}' => e_HANDLER, 3556 '{e_MEDIA}' => e_MEDIA, 3557 '{e_WEB}' => e_WEB, 3558 '{e_UPLOAD}' => e_UPLOAD, 3559 ); 3560 break; 3561 3562 case 2: // absolute path only 3563 $tmp = array( 3564 '{e_MEDIA_FILE}' => e_MEDIA_FILE_ABS, 3565 '{e_MEDIA_VIDEO}' => e_MEDIA_VIDEO_ABS, 3566 '{e_MEDIA_IMAGE}' => e_MEDIA_IMAGE_ABS, 3567 '{e_MEDIA_ICON}' => e_MEDIA_ICON_ABS, 3568 '{e_AVATAR}' => e_AVATAR_ABS, 3569 '{e_WEB_JS}' => e_JS_ABS, 3570 '{e_WEB_CSS}' => e_CSS_ABS, 3571 '{e_WEB_IMAGE}' => e_WEB_IMAGE_ABS, 3572 // '{e_WEB_PACK}' => e_PACK_ABS, 3573 3574 '{e_IMAGE}' => e_IMAGE_ABS, 3575 '{e_PLUGIN}' => e_PLUGIN_ABS, 3576 '{e_FILE}' => e_FILE_ABS, // deprecated 3577 '{e_THEME}' => e_THEME_ABS, 3578 '{e_DOWNLOAD}' => e_HTTP.'request.php?',// FIXME - we need solution! 3579 '{e_ADMIN}' => e_ADMIN_ABS, 3580 //'{e_HANDLER}' => e_HANDLER_ABS, - no ABS path available 3581 '{e_MEDIA}' => e_MEDIA_ABS, 3582 '{e_WEB}' => e_WEB_ABS, 3583 '{e_BASE}' => e_HTTP, 3584 ); 3585 break; 3586 3587 case 3: // full path (e.g http://domain.com/e107_images/) 3588 $tmp = array( 3589 '{e_MEDIA_FILE}' => SITEURLBASE.e_MEDIA_FILE_ABS, 3590 '{e_MEDIA_VIDEO}' => SITEURLBASE.e_MEDIA_VIDEO_ABS, 3591 '{e_MEDIA_IMAGE}' => SITEURLBASE.e_MEDIA_IMAGE_ABS, 3592 '{e_MEDIA_ICON}' => SITEURLBASE.e_MEDIA_ICON_ABS, 3593 '{e_AVATAR}' => SITEURLBASE.e_AVATAR_ABS, 3594 '{e_WEB_JS}' => SITEURLBASE.e_JS_ABS, 3595 '{e_WEB_CSS}' => SITEURLBASE.e_CSS_ABS, 3596 '{e_WEB_IMAGE}' => SITEURLBASE.e_WEB_IMAGE_ABS, 3597 // '{e_WEB_PACK}' => SITEURLBASE.e_PACK_ABS, 3598 3599 '{e_IMAGE}' => SITEURLBASE.e_IMAGE_ABS, 3600 '{e_PLUGIN}' => SITEURLBASE.e_PLUGIN_ABS, 3601 '{e_FILE}' => SITEURLBASE.e_FILE_ABS, // deprecated 3602 '{e_THEME}' => SITEURLBASE.e_THEME_ABS, 3603 '{e_DOWNLOAD}' => SITEURLBASE.e_HTTP.'request.php?',// FIXME - we need solution! 3604 '{e_ADMIN}' => SITEURLBASE.e_ADMIN_ABS, 3605 //'{e_HANDLER}' => e_HANDLER_ABS, - no ABS path available 3606 '{e_MEDIA}' => SITEURLBASE.e_MEDIA_ABS, 3607 '{e_WEB}' => SITEURLBASE.e_WEB_ABS, 3608 '{e_BASE}' => SITEURL, 3609 ); 3610 break; 3611 3612 case 4: // absolute & relative paths 3613 $url = $this->createConstants($url, 3); 3614 $url = $this->createConstants($url, 2); 3615 $url = $this->createConstants($url, 1); 3616 return $url; 3617 break; 3618 3619 case 5: // nice urls - e.g. e_MEDIA_VIDEO/mystream.flv 3620 $url = $this->createConstants($url, 4); 3621 return str_replace($this->getUrlConstants('sc'), $this->getUrlConstants('raw'), $url); 3622 break; 3623 3624 default: 3625 $tmp = array(); 3626 break; 3627 } 3628 3629 $hasCDN = strpos($url, '//') === 0; 3630 3631 foreach($tmp as $key=>$val) 3632 { 3633 // Fix - don't break the CDN '//cdn.com' URLs 3634 if ($hasCDN && $val === '/') { 3635 continue; 3636 } 3637 3638 $len = strlen($val); 3639 if(substr($url, 0, $len) == $val) 3640 { 3641 // replace the first instance only 3642 return substr_replace($url, $key, 0, $len); 3643 } 3644 } 3645 3646 return $url; 3647 } 3648 3649 3650 //FIXME - $match not used? 3651 function e_highlight($text, $match) 3652 { 3653 $tags = array(); 3654 preg_match_all('#<[^>]+>#', $text, $tags); 3655 $text = preg_replace('#<[^>]+>#', '<|>', $text); 3656 $text = preg_replace('#(\b".$match."\b)#i', '<span class="searchhighlight">\\1</span>', $text); 3657 foreach ($tags[0] as $tag) 3658 { 3659 $text = preg_replace('#<\|>#', $tag, $text, 1); 3660 } 3661 return $text; 3662 } 3663 3664 3665 3666 3667 /** 3668 * Convert Text to a suitable format for use in emails. eg. relative links will be replaced with full links etc. 3669 * @param string $text 3670 * @param boolean $posted - if the text has been posted. (uses stripslashes etc) 3671 * @param string $mods - flags for text transformation. 3672 */ 3673 public function toEmail($text, $posted = "", $mods = "parse_sc, no_make_clickable") 3674 { 3675 if ($posted === TRUE) 3676 { 3677 if (MAGIC_QUOTES_GPC) 3678 { 3679 $text = stripslashes($text); 3680 } 3681 $text = preg_replace('#\[(php)#i', '[\\1', $text); 3682 } 3683 3684 $text = (strtolower($mods) != "rawtext") ? $this->replaceConstants($text, "full") : $text; 3685 3686 if($this->isHtml($text)) 3687 { 3688 $text = str_replace(array("[html]","[/html]"), "", $text); 3689 $text = html_entity_decode( $text, ENT_COMPAT, 'UTF-8'); 3690 } 3691 else 3692 { 3693 3694 $text = $this->toHTML($text, true, $mods); 3695 } 3696 3697 return $text; 3698 } 3699 3700 3701 3702 /** 3703 * Given an email address, returns a link including with obfuscated text. 3704 * e-email css in e107.css inserts the user/domain data for display. 3705 * 3706 * @param string $email 3707 * @param string $words [optional] text to display 3708 * @param null $subject [optional] default subject for email. 3709 * @return string 3710 */ 3711 function emailObfuscate($email, $words = null, $subject =null) 3712 { 3713 if(strpos($email, '@') === false) 3714 { 3715 return ''; 3716 } 3717 3718 if ($subject) 3719 { 3720 $subject = '?subject='.$subject; 3721 } 3722 3723 list($name, $address) = explode('@', $email, 2); 3724 3725 if(empty($words)) 3726 { 3727 $words = "@"; 3728 $user = "data-user='".$this->obfuscate($name)."'"; 3729 $dom = "data-dom='".$this->obfuscate($address)."'"; 3730 } 3731 else 3732 { 3733 $user = ''; 3734 $dom = ''; 3735 } 3736 3737 $url = "mailto:".$email.$subject; 3738 3739 $safe = $this->obfuscate($url); 3740 3741 return "<a class='e-email' {$user} {$dom} rel='external' href='".$safe."'>".$words.'</a>'; 3742 } 3743 3744 3745 3746 /** 3747 * Obfuscate text from bots using Randomized encoding. 3748 * @param $text 3749 * @return string 3750 */ 3751 public function obfuscate($text) 3752 { 3753 $ret = ''; 3754 foreach (str_split($text) as $letter) 3755 { 3756 switch (rand(1, 3)) 3757 { 3758 // HTML entity code 3759 case 1: 3760 $ret .= '&#'.ord($letter).';'; 3761 break; 3762 3763 // Hex character code 3764 case 2: 3765 $ret .= '&#x'.dechex(ord($letter)).';'; 3766 break; 3767 3768 // Raw (no) encoding 3769 case 3: 3770 $ret .= $letter; 3771 } 3772 } 3773 3774 return $ret; 3775 } 3776 3777 3778 3779 3780 public function __get($name) 3781 { 3782 switch($name) 3783 { 3784 case 'e_sc': 3785 $ret = e107::getScParser(); 3786 break; 3787 3788 3789 default: 3790 trigger_error('$e107->$'.$name.' not defined', E_USER_WARNING); 3791 return NULL; 3792 break; 3793 } 3794 3795 3796 $this->$name = $ret; 3797 return $ret; 3798 } 3799} 3800 3801 3802 /** 3803 * New v2 Parser 3804 * Start Fresh and Build on it over time to become eventual replacement to e_parse. 3805 * Cameron's DOM-based parser. 3806 * 3807 * @method replaceConstants($text, $mode = '', $all = false) 3808 * @method toAttribute($title) 3809 * @method thumbUrl($icon) 3810 * @method thumbDimensions() 3811 */ 3812class e_parser 3813{ 3814 /** 3815 * @var DOMDocument 3816 */ 3817 public $domObj = null; 3818 public $isHtml = false; 3819 3820 3821 protected $bootstrap = null; 3822 protected $fontawesome = null; 3823 3824 protected $removedList = array(); 3825 protected $nodesToDelete = array(); 3826 protected $nodesToConvert = array(); 3827 protected $nodesToDisableSC = array(); 3828 protected $pathList = array(); 3829 protected $allowedAttributes = array( 3830 'default' => array('id', 'style', 'class', 'title', 'lang', 'accesskey'), 3831 'img' => array('src', 'alt', 'width', 'height'), 3832 'a' => array('href', 'target', 'rel'), 3833 'script' => array('type', 'src', 'language', 'async'), 3834 'iframe' => array('src', 'frameborder', 'width', 'height'), 3835 'input' => array('type','name','value'), 3836 'form' => array('action','method','target'), 3837 'audio' => array('src','controls', 'autoplay', 'loop', 'muted', 'preload' ), 3838 'video' => array('autoplay', 'controls', 'height', 'loop', 'muted', 'poster', 'preload', 'src', 'width'), 3839 'td' => array('colspan', 'rowspan'), 3840 'th' => array('colspan', 'rowspan'), 3841 'col' => array('span'), 3842 'embed' => array('src', 'wmode', 'type', 'width', 'height'), 3843 'x-bbcode' => array('alt'), 3844 'label' => array('for'), 3845 'source' => array('media', 'sizes', 'src', 'srcset', 'type'), 3846 3847 ); 3848 3849 protected $badAttrValues = array('javascript[\s]*?:','alert\(','vbscript[\s]*?:','data:text\/html', 'mhtml[\s]*?:', 'data:[\s]*?image'); 3850 3851 protected $replaceAttrValues = array( 3852 'default' => array() 3853 ); 3854 3855 protected $allowedTags = array('html', 'body','div','a','img','table','tr', 'td', 'th', 'tbody', 'thead', 'colgroup', 'b', 3856 'i', 'pre','code', 'strong', 'u', 'em','ul', 'ol', 'li','img','h1','h2','h3','h4','h5','h6','p', 3857 'div','pre','section','article', 'blockquote','hgroup','aside','figure','figcaption', 'abbr','span', 'audio', 'video', 'source', 'br', 3858 'small', 'caption', 'noscript', 'hr', 'section', 'iframe', 'sub', 'sup', 'cite', 'x-bbcode', 'label' 3859 ); 3860 protected $scriptTags = array('script','applet','form','input','button', 'embed', 'object', 'ins', 'select','textarea'); //allowed when $pref['post_script'] is enabled. 3861 3862 protected $scriptAttributes = array('onclick', 'onchange', 'onblur', 'onload', 'onfocus', 'onkeydown', 'onkeypress', 'onkeyup', 3863 'ondblclick', 'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 3864 'onwheel', 'oncopy', 'oncut', 'onpaste' 3865 ); 3866 3867 protected $blockTags = array('pre','div','h1','h2','h3','h4','h5','h6','blockquote'); // element includes its own line-break. 3868 3869 3870 private $scriptAccess = false; // nobody. 3871 3872 /** 3873 * e_parser constructor. 3874 */ 3875 public function __construct() 3876 { 3877 3878 $this->init(); 3879 $this->compileAttributeDefaults(); 3880 3881 /* 3882 $meths = get_class_methods('DomDocument'); 3883 sort($meths); 3884 print_a($meths); 3885 */ 3886 } 3887 3888 /** 3889 * Merge default 'global' attributes into assigned tags. 3890 */ 3891 private function compileAttributeDefaults() 3892 { 3893 foreach($this->allowedAttributes as $tag=>$array) 3894 { 3895 if($tag === 'default') 3896 { 3897 continue; 3898 } 3899 3900 foreach($this->allowedAttributes['default'] as $def) 3901 { 3902 $this->allowedAttributes[$tag][] = $def; 3903 } 3904 3905 } 3906 3907 } 3908 3909 /** 3910 * Used by e_parse to start 3911 */ 3912 function init() 3913 { 3914 $this->domObj = new DOMDocument('1.0', 'utf-8'); 3915 3916 if(defined('FONTAWESOME')) 3917 { 3918 $this->fontawesome = (int) FONTAWESOME; 3919 } 3920 3921 if(defined('BOOTSTRAP')) 3922 { 3923 $this->bootstrap = (int) BOOTSTRAP; 3924 3925 } 3926 3927 } 3928 3929 /** 3930 * Add Allowed Tags. 3931 * @param string 3932 */ 3933 public function addAllowedTag($tag) 3934 { 3935 $this->allowedTags[] = $tag; 3936 } 3937 3938 3939 /** 3940 * @param $tag - html tag. 3941 * @param $attArray - array of attributes. eg. array('style', 'id', 'class') etc. 3942 */ 3943 public function addAllowedAttribute($tag, $attArray) 3944 { 3945 $this->allowedAttributes[$tag] = (array) $attArray; 3946 } 3947 3948 3949 /** 3950 * Set Allowed Tags. 3951 * @param $array 3952 */ 3953 public function setAllowedTags($array=array()) 3954 { 3955 $this->allowedTags = $array; 3956 } 3957 3958 /** 3959 * Set Script Access 3960 * @param $val int e_UC_MEMBER, e_UC_NOBODY, e_UC_MAINADMIN or userclass number. 3961 */ 3962 public function setScriptAccess($val) 3963 { 3964 $this->scriptAccess = $val; 3965 } 3966 3967 public function getAllowedTags() 3968 { 3969 return $this->allowedTags; 3970 } 3971 3972 public function getAllowedAttributes() 3973 { 3974 return $this->allowedAttributes; 3975 } 3976 3977 3978 public function getScriptAccess() 3979 { 3980 return $this->scriptAccess; 3981 } 3982 3983 public function getRemoved() 3984 { 3985 return $this->removedList; 3986 } 3987 3988 /** 3989 * Set Allowed Attributes. 3990 * @param $array 3991 */ 3992 public function setAllowedAttributes($array=array()) 3993 { 3994 $this->allowedAttributes = $array; 3995 } 3996 3997 /** 3998 * Set Script Tags. 3999 * @param $array 4000 */ 4001 public function setScriptTags($array=array()) 4002 { 4003 $this->scriptTags = $array; 4004 } 4005 4006 4007 /** 4008 * @param int $version 4009 */ 4010 public function setFontAwesome($version) 4011 { 4012 $this->fontawesome = (int) $version; 4013 } 4014 4015 /** 4016 * @param int $version 4017 */ 4018 public function setBootstrap($version) 4019 { 4020 $this->bootstrap = (int) $version; 4021 } 4022 4023 4024 /** 4025 * Add leading zeros to a number. eg. 3 might become 000003 4026 * @param $num integer 4027 * @param $numDigits - total number of digits 4028 * @return string number with leading zeros. 4029 */ 4030 public function leadingZeros($num,$numDigits) 4031 { 4032 return (string) sprintf("%0".$numDigits."d",$num); 4033 } 4034 4035 /** 4036 * Generic variable translator for LAN definitions. 4037 * @param $lan - string LAN 4038 * @param string | array $vals - either a single value, which will replace '[x]' or an array with key=>value pairs. 4039 * @example $tp->lanVars("My name is [x] and I own a [y]", array('x'=>"John", 'y'=>"Cat")); 4040 * @example $tp->lanVars("My name is [x] and I own a [y]", array("John","Cat")); 4041 * @return string 4042 */ 4043 function lanVars($lan, $vals, $bold=false) 4044 { 4045 4046 $array = (!is_array($vals)) ? array('x'=>$vals) : $vals; 4047 4048 $search = array(); 4049 $replace = array(); 4050 4051 $defaults = array('x', 'y', 'z'); 4052 4053 foreach($array as $k=>$v) 4054 { 4055 if(is_numeric($k)) // convert array of numeric to x,y,z 4056 { 4057 $k = $defaults[$k]; 4058 } 4059 4060 $search[] = "[".$k."]"; 4061 $replace[] = ($bold===true) ? "<strong>".$v."</strong>" : $v; 4062 } 4063 4064 return str_replace($search, $replace, $lan); 4065 } 4066 4067 /** 4068 * Return an Array of all specific tags found in an HTML document and their attributes. 4069 * @param $html - raw html code 4070 * @param $taglist - comma separated list of tags to search or '*' for all. 4071 * @param $header - if the $html includes the html head or body tags - it should be set to true. 4072 */ 4073 public function getTags($html, $taglist='*', $header = false) 4074 { 4075 4076 if($header == false) 4077 { 4078 $html = "<html><body>".$html."</body></html>"; 4079 } 4080 4081 $doc = $this->domObj; 4082 4083 $doc->preserveWhiteSpace = true; 4084 libxml_use_internal_errors(true); 4085 $doc->loadHTML($html); 4086 4087 $tg = explode(",", $taglist); 4088 $ret = array(); 4089 4090 foreach($tg as $find) 4091 { 4092 $tmp = $doc->getElementsByTagName($find); 4093 4094 /** 4095 * @var $k 4096 * @var DOMDocument $node 4097 */ 4098 foreach($tmp as $k=>$node) 4099 { 4100 $tag = $node->nodeName; 4101 $inner = $node->C14N(); 4102 $inner = str_replace("
","",$inner); 4103 4104 foreach ($node->attributes as $attr) 4105 { 4106 $name = $attr->nodeName; 4107 $value = $attr->nodeValue; 4108 $ret[$tag][$k][$name] = $value; 4109 } 4110 4111 $ret[$tag][$k]['@value'] = $inner; 4112 4113 4114 } 4115 } 4116 4117 if($header == false) 4118 { 4119 unset($ret['html'],$ret['body']); 4120 } 4121 4122 4123 return $ret; 4124 } 4125 4126 4127 4128 /** 4129 * Parse xxxxx.glyph file to bootstrap glyph format. 4130 * @param string $text 4131 * @param array|string $options 4132 * @param bool $options['size'] 2x, 3x, 4x, or 5x 4133 * @param bool $options['fw'] Fixed-Width 4134 * @param bool $options['spin'] Spin 4135 * @param int $options['rotate'] Rotate in Degrees. 4136 * @example $tp->toGlyph('fa-spinner', 'spin=1'); 4137 * @example $tp->toGlyph('fa-spinner', array('spin'=>1)); 4138 * @example $tp->toGlyph('fa-shield', array('rotate'=>90, 'size'=>'2x')); 4139 */ 4140 public function toGlyph($text, $options=" ") 4141 { 4142 4143 if(empty($text)) 4144 { 4145 return false; 4146 } 4147 4148 if(is_array($options)) 4149 { 4150 $parm = $options; 4151 $options = varset($parm['space'],''); 4152 } 4153 elseif(strpos($options,'=')) 4154 { 4155 parse_str($options,$parm); 4156 $options = varset($parm['space'],''); 4157 } 4158 else 4159 { 4160 $parm = array(); 4161 } 4162 4163 if(substr($text,0,2) === 'e-') // e107 admin icon. 4164 { 4165 $size = (substr($text,-3) === '-32') ? 'S32' : 'S16'; 4166 4167 if(substr($text,-3) === '-24') 4168 { 4169 $size = 'S24'; 4170 } 4171 4172 return "<i class='".$size." ".$text."'></i>"; 4173 } 4174 4175 // Get Glyph names. 4176 // $bs3 = e107::getMedia()->getGlyphs('bs3',''); 4177 // $fa4 = e107::getMedia()->getGlyphs('fa4',''); 4178 4179 4180 4181 list($id) = explode('.glyph',$text,2); 4182 // list($type, $tmp2) = explode("-",$text,2); 4183 4184 // return $cls; 4185 4186 // $removePrefix = array('glyphicon-','icon-','fa-'); 4187 4188 // $id = str_replace($removePrefix, "", $cls); 4189 4190 4191 $spin = null; 4192 $rotate = null; 4193 $fixedW = null; 4194 $prefix = 'glyphicon glyphicon-'; // fallback 4195 $size = null; 4196 $tag = 'i'; 4197 4198 // return print_r($fa4,true); 4199/* 4200 if(deftrue('FONTAWESOME') && in_array($id ,$fa4)) // Contains FontAwesome 3 set also. 4201 { 4202 $prefix = 'fa fa-'; 4203 $size = (vartrue($parm['size'])) ? ' fa-'.$parm['size'] : ''; 4204 $tag = 'i'; 4205 $spin = !empty($parm['spin']) ? ' fa-spin' : ''; 4206 $rotate = !empty($parm['rotate']) ? ' fa-rotate-'.intval($parm['rotate']) : ''; 4207 $fixedW = !empty($parm['fw']) ? ' fa-fw' : ""; 4208 } 4209 elseif(deftrue("BOOTSTRAP")) 4210 { 4211 if(BOOTSTRAP === 3 && in_array($id ,$bs3)) 4212 { 4213 $prefix = 'glyphicon glyphicon-'; 4214 $tag = 'span'; 4215 } 4216 else 4217 { 4218 // $prefix = 'icon-'; 4219 $tag = 'i'; 4220 } 4221 4222 $size = ''; 4223 4224 } 4225 */ 4226 if(strpos($text, 'fa-') === 0) // Font-Awesome 4 & 5 4227 { 4228 $prefix = 'fa '; 4229 $size = (vartrue($parm['size'])) ? ' fa-'.$parm['size'] : ''; 4230 $tag = 'i'; 4231 $spin = !empty($parm['spin']) ? ' fa-spin' : ''; 4232 $rotate = !empty($parm['rotate']) ? ' fa-rotate-'.intval($parm['rotate']) : ''; 4233 $fixedW = !empty($parm['fw']) ? ' fa-fw' : ""; 4234 4235 if($this->fontawesome === 5) 4236 { 4237 $fab = e107::getMedia()->getGlyphs('fab'); 4238 $fas = e107::getMedia()->getGlyphs('fas');; 4239 4240 $code = substr($id,3); 4241 4242 if(in_array($code,$fab)) 4243 { 4244 $prefix = "fab "; 4245 } 4246 elseif(in_array($code,$fas)) 4247 { 4248 $prefix = "fas "; 4249 } 4250 4251 } 4252 4253 } 4254 elseif(strpos($text, 'glyphicon-') === 0) // Bootstrap 3 4255 { 4256 $prefix = 'glyphicon '; 4257 $tag = 'span'; 4258 4259 } 4260 elseif(strpos($text, 'icon-') === 0) // Bootstrap 2 4261 { 4262 if($this->bootstrap !== 2) // bootrap 2 icon but running bootstrap3. 4263 { 4264 $prefix = 'glyphicon '; 4265 $tag = 'span'; 4266 $id = str_replace("icon-", "glyphicon-", $id); 4267 } 4268 else 4269 { 4270 $prefix = ''; 4271 $tag = 'i'; 4272 } 4273 4274 } 4275 elseif($custom = e107::getThemeGlyphs()) // Custom Glyphs 4276 { 4277 foreach($custom as $glyphConfig) 4278 { 4279 if(strpos($text, $glyphConfig['prefix']) === 0) 4280 { 4281 $prefix = $glyphConfig['class'] . " "; 4282 $tag = $glyphConfig['tag']; 4283 continue; 4284 } 4285 } 4286 4287 } 4288 4289 4290 $idAtt = (!empty($parm['id'])) ? "id='".$parm['id']."' " : ''; 4291 $style = (!empty($parm['style'])) ? "style='".$parm['style']."' " : ''; 4292 $class = (!empty($parm['class'])) ? $parm['class']." " : ''; 4293 $placeholder = isset($parm['placeholder']) ? $parm['placeholder'] : "<!-- -->"; 4294 $title = (!empty($parm['title'])) ? " title='".$this->toAttribute($parm['title'])."' " : ''; 4295 4296 $text = "<".$tag." {$idAtt}class='".$class.$prefix.$id.$size.$spin.$rotate.$fixedW."' ".$style.$title.">".$placeholder."</".$tag.">" ; 4297 $text .= ($options !== false) ? $options : ""; 4298 4299 return $text; 4300 4301 4302 } 4303 4304 4305 /** 4306 * Return a Bootstrap Badge tag 4307 * @param $text 4308 * @return string 4309 */ 4310 public function toBadge($text, $parm=null) 4311 { 4312 $class = !empty($parm['class']) ? " ".$parm['class'] : ' badge-secondary'; 4313 4314 return "<span class='badge".$class."'>".$text."</span>"; 4315 } 4316 4317 4318 /** 4319 * Return a Bootstrap Label tag 4320 * @param $text 4321 * @return string 4322 */ 4323 public function toLabel($text, $type = null) 4324 { 4325 if($type === null) 4326 { 4327 $type = 'default'; 4328 } 4329 4330 $tmp = explode(",",$text); 4331 4332 $opt = array(); 4333 foreach($tmp as $v) 4334 { 4335 $opt[] = "<span class='label label-".$type."'>".$v."</span>"; 4336 } 4337 4338 return implode(" ",$opt); 4339 } 4340 4341 /** 4342 * Take a file-path and convert it to a download link. 4343 * @param $text 4344 * @return string 4345 */ 4346 public function toFile($text, $parm=array()) 4347 { 4348 $srch = array( 4349 '{e_MEDIA_FILE}' => 'e_MEDIA_FILE/', 4350 '{e_PLUGIN}' => 'e_PLUGIN/' 4351 ); 4352 4353 $link = e_HTTP."request.php?file=". str_replace(array_keys($srch), $srch,$text); 4354 4355 if(!empty($parm['raw'])) 4356 { 4357 return $link; 4358 } 4359 4360 return "<a href='".$link."'>-attachment-</a>"; //TODO Add pref for this. 4361 } 4362 4363 /** 4364 * Render an avatar based on supplied user data or current user when missing. 4365 * @param array $userData - user data from e107_user. ie. user_image, user_id etc. 4366 * @param array $options 4367 * @param int $options['w'] - image width in px 4368 * @param int $options['h'] - image height in px 4369 * @param int|bool $options['crop'] = enables cropping when true 4370 * @param string $options['shape'] - (optional) rounded|circle|thumbnail 4371 * @param string $options['id'] - 'id' attribute will be added to tag. 4372 * @param string $options['class'] - override default 'class' attribute in tag. 4373 * @param string $options['alt'] - override default 'alt' attribute in tag. 4374 * @param bool $options['base64'] - use embedded base64 for image src. 4375 * @param bool $options['hd'] - double the resolution of the image. Useful for retina displays. 4376 * @param string $options['type'] - when set to 'url' returns the URL value instead of the tag. 4377 * @param string $options['style'] - sets the style attribute. 4378 * @param string $options['mode'] - 'full' url mode. 4379 * @return string <img> tag of avatar. 4380 */ 4381 public function toAvatar($userData=null, $options=array()) 4382 { 4383 $tp = e107::getParser(); 4384 $width = !empty($options['w']) ? intval($options['w']) : $tp->thumbWidth; 4385 $height = ($tp->thumbHeight !== 0) ? $tp->thumbHeight : ""; 4386 $crop = !empty($options['crop']) ? $options['crop'] : $tp->thumbCrop; 4387 $linkStart = ''; 4388 $linkEnd = ''; 4389 $full = !empty($options['base64']) ? true : false; 4390 4391 if(!empty($options['mode']) && $options['mode'] === 'full') 4392 { 4393 $full = true; 4394 } 4395 4396 if(!empty($options['h'])) 4397 { 4398 $height = intval($options['h']); 4399 } 4400 4401 if(!empty($options['hd'])) // Fix resolution on Retina display. 4402 { 4403 $width = $width * 2; 4404 $height = $height * 2; 4405 } 4406 4407 4408 if($userData === null && USERID) 4409 { 4410 $userData = array(); 4411 $userData['user_id'] = USERID; 4412 $userData['user_image'] = USERIMAGE; 4413 $userData['user_name'] = USERNAME; 4414 $userData['user_currentvisit'] = USERCURRENTVISIT; 4415 } 4416 4417 4418 $image = (!empty($userData['user_image'])) ? varset($userData['user_image']) : null; 4419 4420 $genericFile = e_IMAGE."generic/blank_avatar.jpg"; 4421 $genericImg = $tp->thumbUrl($genericFile,"w=".$width."&h=".$height,true, $full); 4422 4423 if (!empty($image)) 4424 { 4425 4426 if(strpos($image,"://")!==false) // Remote Image 4427 { 4428 $url = $image; 4429 } 4430 elseif(substr($image,0,8) == "-upload-") 4431 { 4432 4433 $image = substr($image,8); // strip the -upload- from the beginning. 4434 if(file_exists(e_AVATAR_UPLOAD.$image)) 4435 { 4436 $file = e_AVATAR_UPLOAD.$image; 4437 $url = $tp->thumbUrl($file,"w=".$width."&h=".$height."&crop=".$crop, false, $full); 4438 } 4439 else 4440 { 4441 $file = $genericFile; 4442 $url = $genericImg; 4443 } 4444 } 4445 elseif(file_exists(e_AVATAR_DEFAULT.$image)) // User-Uplaoded Image 4446 { 4447 $file = e_AVATAR_DEFAULT.$image; 4448 $url = $tp->thumbUrl($file,"w=".$width."&h=".$height."&crop=".$crop, false, $full); 4449 } 4450 else // Image Missing. 4451 { 4452 $url = $genericImg; 4453 $file = $genericFile; 4454 } 4455 } 4456 else // No image provided - so send generic. 4457 { 4458 $url = $genericImg; 4459 $file = $genericFile; 4460 } 4461 4462 if(!empty($options['base64'])) // embed image data into URL. 4463 { 4464 $content = e107::getFile()->getRemoteContent($url); // returns false during unit tests, works otherwise. 4465 if(!empty($content)) 4466 { 4467 $ext = strtolower(pathinfo($file,PATHINFO_EXTENSION)); 4468 $url = 'data:image/'.$ext.';base64,'.base64_encode($content); 4469 } 4470 } 4471 4472 if(!empty($options['hd'])) // Fix resolution on Retina display. 4473 { 4474 $width = $width / 2; 4475 $height = ($height / 2); 4476 } 4477 4478 if(($url == $genericImg) && !empty($userData['user_id'] ) && (($userData['user_id'] == USERID)) && !empty($options['link'])) 4479 { 4480 $linkStart = "<a class='e-tip' title=\"".LAN_EDIT."\" href='".e107::getUrl()->create('user/myprofile/edit')."'>"; 4481 $linkEnd = "</a>"; 4482 } 4483 4484 $title = (ADMIN) ? $image : $tp->toAttribute($userData['user_name']); 4485 $shape = (!empty($options['shape'])) ? "img-".$options['shape'] : "img-rounded rounded"; 4486 4487 4488 if(!empty($options['type']) && $options['type'] === 'url') 4489 { 4490 return $url; 4491 } 4492 4493 if(!empty($options['alt'])) 4494 { 4495 $title = $tp->toAttribute($options['alt']); 4496 } 4497 4498 $heightInsert = empty($height) ? '' : "height='".$height."'"; 4499 $id = (!empty($options['id'])) ? "id='".$options['id']."' " : ""; 4500 4501 $classOnline = (!empty($userData['user_currentvisit']) && intval($userData['user_currentvisit']) > (time() - 300)) ? " user-avatar-online" : ''; 4502 4503 $class = !empty($options['class']) ? $options['class'] : $shape." user-avatar"; 4504 $style = !empty($options['style']) ? " style='".$options['style']."'" : ''; 4505 4506 $text = $linkStart; 4507 $text .= "<img ".$id."class='".$class.$classOnline."' alt=\"".$title."\" src='".$url."' width='".$width."' ".$heightInsert.$style." />"; 4508 $text .= $linkEnd; 4509 // return $url; 4510 return $text; 4511 4512 } 4513 4514 4515 4516 /** 4517 * Display an icon. 4518 * @param string $icon 4519 * @example $tp->toIcon("{e_IMAGES}icons/something.png"); 4520 */ 4521 public function toIcon($icon='',$parm = array()) 4522 { 4523 4524 if(empty($icon)) 4525 { 4526 return null; 4527 } 4528 4529 if(strpos($icon,'e_MEDIA_IMAGE')!==false) 4530 { 4531 // return "<div class='alert alert-danger'>Use \$tp->toImage() instead of toIcon() for ".$icon."</div>"; // debug info only. 4532 } 4533 4534 if(substr($icon,0,3) == '<i ') // if it's html (ie. css sprite) return the code. 4535 { 4536 return $icon; 4537 } 4538 4539 $ext = pathinfo($icon, PATHINFO_EXTENSION); 4540 $dimensions = null; 4541 4542 if(!$ext || $ext == 'glyph') // Bootstrap or Font-Awesome. 4543 { 4544 return $this->toGlyph($icon,$parm); 4545 } 4546 4547 if(strpos($icon,'e_MEDIA_IMAGE')!==false) 4548 { 4549 $path = $this->thumbUrl($icon); 4550 $dimensions = $this->thumbDimensions(); 4551 } 4552 elseif($icon[0] === '{') 4553 { 4554 $path = $this->replaceConstants($icon,'abs'); 4555 } 4556 elseif(!empty($parm['legacy'])) 4557 { 4558 $legacyList = (!is_array($parm['legacy'])) ? array($parm['legacy']) : $parm['legacy']; 4559 4560 foreach($legacyList as $legPath) 4561 { 4562 $legacyPath = $legPath.$icon; 4563 $filePath = $this->replaceConstants($legacyPath); 4564 4565 if(is_readable($filePath)) 4566 { 4567 $path = $this->replaceConstants($legacyPath,'full'); 4568 break; 4569 } 4570 4571 } 4572 4573 if(empty($path)) 4574 { 4575 $log = e107::getAdminLog(); 4576 $log->addDebug('Broken Icon Path: '.$icon."\n".print_r(debug_backtrace(null,2), true), false)->save('IMALAN_00'); 4577 e107::getDebug()->log('Broken Icon Path: '.$icon); 4578 return null; 4579 } 4580 4581 } 4582 else 4583 { 4584 $path = $icon; 4585 } 4586 4587 4588 $alt = (!empty($parm['alt'])) ? $this->toAttribute($parm['alt']) : basename($path); 4589 $class = (!empty($parm['class'])) ? $parm['class'] : 'icon'; 4590 4591 return "<img class='".$class."' src='".$path."' alt='".$alt."' ".$dimensions." />"; 4592 } 4593 4594 4595 /** 4596 * Render an img tag. 4597 * @param string $file 4598 * @param array $parm keys: legacy|w|h|alt|class|id|crop|loading 4599 * @param array $parm['legacy'] Usually a legacy path like {e_FILE} 4600 * @return string 4601 * @example $tp->toImage('welcome.png', array('legacy'=>{e_IMAGE}newspost_images/','w'=>200)); 4602 */ 4603 public function toImage($file, $parm=array()) 4604 { 4605 4606 if(strpos($file,'e_AVATAR')!==false) 4607 { 4608 return "<div class='alert alert-danger'>Use \$tp->toAvatar() instead of toImage() for ".$file."</div>"; // debug info only. 4609 4610 } 4611 4612 if(empty($file) && empty($parm['placeholder'])) 4613 { 4614 return null; 4615 } 4616 4617 if(!empty($file)) 4618 { 4619 $srcset = null; 4620 $path = null; 4621 $file = trim($file); 4622 $ext = pathinfo($file, PATHINFO_EXTENSION); 4623 $accepted = array('jpg','gif','png','jpeg', 'svg'); 4624 4625 4626 if(!in_array($ext,$accepted)) 4627 { 4628 return null; 4629 } 4630 } 4631 4632 /** @var e_parse $tp */ 4633 $tp = $this; 4634 4635 // e107::getDebug()->log($file); 4636 // e107::getDebug()->log($parm); 4637 4638 if(strpos($file,'http')===0) 4639 { 4640 $path = $file; 4641 } 4642 elseif(strpos($file,'e_MEDIA')!==false || strpos($file,'e_THEME')!==false || strpos($file,'e_PLUGIN')!==false || strpos($file,'{e_IMAGE}')!==false) //v2.x path. 4643 { 4644 4645 if(!isset($parm['w']) && !isset($parm['h'])) 4646 { 4647 $parm['w'] = $tp->thumbWidth(); 4648 $parm['h'] = $tp->thumbHeight(); 4649 $parm['crop'] = $tp->thumbCrop(); 4650 $parm['x'] = $tp->thumbEncode(); 4651 } 4652 4653 unset($parm['src']); 4654 $path = $tp->thumbUrl($file,$parm); 4655 4656 4657 if(empty($parm['w']) && empty($parm['h'])) 4658 { 4659 $parm['srcset'] = false; 4660 } 4661 elseif(!isset($parm['srcset'])) 4662 { 4663 $srcSetParm = $parm; 4664 $srcSetParm['size'] = ($parm['w'] < 100) ? '4x' : '2x'; 4665 $parm['srcset'] = $tp->thumbSrcSet($file, $srcSetParm); 4666 } 4667 4668 } 4669 elseif($file[0] === '{') // Legacy v1.x path. Example: {e_PLUGIN}myplugin/images/fixedimage.png 4670 { 4671 $path = $tp->replaceConstants($file,'abs'); 4672 } 4673 elseif(!empty($parm['legacy'])) // Search legacy path for image in a specific folder. No path, only file name provided. 4674 { 4675 4676 $legacyPath = rtrim($parm['legacy'],'/').'/'.$file; 4677 $filePath = $tp->replaceConstants($legacyPath); 4678 4679 if(is_readable($filePath)) 4680 { 4681 $path = $tp->replaceConstants($legacyPath,'abs'); 4682 } 4683 else 4684 { 4685 $log = e107::getAdminLog(); 4686 $log->addDebug('Broken Image Path: '.$legacyPath."\n".print_r(debug_backtrace(null,2), true), false)->save('IMALAN_00'); 4687 e107::getDebug()->log("Broken Image Path: ".$legacyPath); 4688 } 4689 4690 } 4691 else // usually http://.... 4692 { 4693 $path = $file; 4694 } 4695 4696 if(empty($path) && !empty($parm['placeholder'])) 4697 { 4698 $path = $tp->thumbUrl($file,$parm); 4699 } 4700 4701 $id = (!empty($parm['id'])) ? "id=\"".$parm['id']."\" " : "" ; 4702 $class = (!empty($parm['class'])) ? $parm['class'] : "img-responsive img-fluid"; 4703 $alt = (!empty($parm['alt'])) ? $tp->toAttribute($parm['alt']) : basename($file); 4704 $style = (!empty($parm['style'])) ? "style=\"".$parm['style']."\" " : "" ; 4705 $srcset = (!empty($parm['srcset'])) ? "srcset=\"".$parm['srcset']."\" " : ""; 4706 $width = (!empty($parm['w'])) ? "width=\"".intval($parm['w'])."\" " : ""; 4707 $height = (!empty($parm['h'])) ? "height=\"".intval($parm['h'])."\" " : ""; 4708 $loading = !empty($parm['loading']) ? "loading=\"".$parm['loading']."\" " : ""; // eg. lazy, eager, auto 4709 4710 return "<img {$id}class='{$class}' src='".$path."' alt=\"".$alt."\" ".$srcset.$width.$height.$style.$loading." />"; 4711 4712 } 4713 4714 4715 /** 4716 * Check if a string contains bbcode. 4717 * @param $text 4718 * @return bool 4719 */ 4720 function isBBcode($text) 4721 { 4722 if(preg_match('#(?<=<)\w+(?=[^<]*?>)#', $text)) 4723 { 4724 return false; 4725 } 4726 4727 $bbsearch = array('[/img]','[/h]', '[/b]', '[/link]', '[/right]', '[/center]', '[/flash]', '[/code]', '[/table]'); 4728 4729 foreach($bbsearch as $v) 4730 { 4731 if(strpos($text,$v)!==false) 4732 { 4733 return true; 4734 } 4735 4736 } 4737 4738 return false; 4739 4740 4741 } 4742 4743 4744 /** 4745 * Check if a string is HTML 4746 * @param $text 4747 * @return bool 4748 */ 4749 function isHtml($text) 4750 { 4751 4752 if(strpos($text,'[html]') !==false) 4753 { 4754 return true; 4755 } 4756 4757 if($this->isBBcode($text)) 4758 { 4759 return false; 4760 } 4761 4762 if(preg_match('#(?<=<)\w+(?=[^<]*?>)#', $text)) 4763 { 4764 return true; 4765 } 4766 4767 return false; 4768 4769 4770 } 4771 4772 4773 /** 4774 * Check if string is json and parse or return false. 4775 * @param $text 4776 * @return bool|mixed return false if not json, and json values if true. 4777 */ 4778 public function isJSON($text) 4779 { 4780 if(!is_string($text)) 4781 { 4782 return false; 4783 } 4784 4785 if(substr($text,0,1) === '{' || substr($text,0,1) === '[') // json 4786 { 4787 $dat = json_decode($text, true); 4788 4789 if(json_last_error() != JSON_ERROR_NONE) 4790 { 4791 // e107::getDebug()->log("Json data found"); 4792 return false; 4793 } 4794 4795 return $dat; 4796 } 4797 4798 return false; 4799 4800 } 4801 4802 4803 4804 /** 4805 * Checks if string is valid UTF-8. 4806 * 4807 * Try to detect UTF-8 using mb_detect_encoding(). If mb string extension is 4808 * not installed, we try to use a simple UTF-8-ness checker using a regular 4809 * expression originally created by the W3C. But W3C's function scans the 4810 * entire strings and checks that it conforms to UTF-8. 4811 * 4812 * @see http://w3.org/International/questions/qa-forms-utf-8.html 4813 * 4814 * So this function is faster and less specific. It only looks for non-ascii 4815 * multibyte sequences in the UTF-8 range and also to stop once it finds at 4816 * least one multibytes string. This is quite a lot faster. 4817 * 4818 * @param $string string string being checked. 4819 * @return bool Returns true if $string is valid UTF-8 and false otherwise. 4820 */ 4821 public function isUTF8($string) 4822 { 4823 if (function_exists('mb_check_encoding')) 4824 { 4825 return (mb_check_encoding($string, 'UTF-8')); 4826 } 4827 4828 return (bool) preg_match('%(?: 4829 [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte 4830 |\xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs 4831 |[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte 4832 |\xED[\x80-\x9F][\x80-\xBF] # excluding surrogates 4833 |\xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 4834 |[\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 4835 |\xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 4836 )+%xs', $string); 4837 4838 } 4839 4840 4841 4842 4843 4844 4845 /** 4846 * Check if a file is an video or not. 4847 * @param $file string 4848 * @return boolean 4849 */ 4850 function isVideo($file) 4851 { 4852 $ext = pathinfo($file,PATHINFO_EXTENSION); 4853 4854 return ($ext === 'youtube' || $ext === 'youtubepl') ? true : false; 4855 4856 } 4857 4858 /** 4859 * Check if a file is an image or not. 4860 * @param $file string 4861 * @return boolean 4862 */ 4863 function isImage($file) 4864 { 4865 if(substr($file,0,3)=="{e_") 4866 { 4867 $file = e107::getParser()->replaceConstants($file); 4868 } 4869 4870 4871 $ext = pathinfo($file,PATHINFO_EXTENSION); 4872 4873 return ($ext === 'jpg' || $ext === 'png' || $ext === 'gif' || $ext === 'jpeg') ? true : false; 4874 } 4875 4876 4877 /** 4878 * @param $file 4879 * @param array $parm 4880 * @return string 4881 */ 4882 public function toAudio($file, $parm=array()) 4883 { 4884 4885 $file = $this->replaceConstants($file, 'abs'); 4886 4887 $mime = varset($parm['mime'], 'audio/mpeg'); 4888 4889 $autoplay = !empty($parm['autoplay']) ? "autoplay " : ""; 4890 $controls = !empty($parm['controls']) ? "controls" : ""; 4891 4892 $text = '<audio controls style="max-width:100%" '.$autoplay.$controls.'> 4893 <source src="'.$file.'" type="'.$mime .'"> 4894 Your browser does not support the audio tag. 4895 </audio>'; 4896 4897 return $text; 4898 4899 } 4900 4901 4902 4903 /** 4904 * Display a Video file. 4905 * @param string $file - format: id.type eg. x123dkax.youtube 4906 * @param boolean $thumbnail - set to 'tag' to return an image thumbnail and 'src' to return the src url or 'video' for a small video thumbnail. 4907 */ 4908 function toVideo($file, $parm=array()) 4909 { 4910 if(empty($file)) 4911 { 4912 return false; 4913 } 4914 4915 $type = pathinfo($file, PATHINFO_EXTENSION); 4916 4917 $id = str_replace(".".$type, "", $file); 4918 4919 $thumb = vartrue($parm['thumb']); 4920 $mode = varset($parm['mode'],false); // tag, url 4921 4922 4923 4924 $pref = e107::getPref(); 4925 $ytpref = array(); 4926 foreach($pref as $k=>$v) // Find all Youtube Prefs. 4927 { 4928 if(substr($k,0,8) === 'youtube_') 4929 { 4930 $key = substr($k,8); 4931 $ytpref[$key] = $v; 4932 } 4933 } 4934 4935 unset($ytpref['bbcode_responsive']); // do not include in embed code. 4936 4937 if(!empty($ytpref['cc_load_policy'])) 4938 { 4939 $ytpref['cc_lang_pref'] = e_LAN; // switch captions with chosen user language. 4940 } 4941 4942 $ytqry = http_build_query($ytpref, null, '&'); 4943 4944 $defClass = !empty($this->bootstrap) ? "embed-responsive embed-responsive-16by9" : "video-responsive"; // levacy backup. 4945 4946 4947 if($type === 'youtube') 4948 { 4949 4950 // $thumbSrc = "https://i1.ytimg.com/vi/".$id."/0.jpg"; 4951 $thumbSrc = "https://i1.ytimg.com/vi/".$id."/mqdefault.jpg"; 4952 $video = '<iframe class="embed-responsive-item" width="560" height="315" src="//www.youtube.com/embed/'.$id.'?'.$ytqry.'" style="background-size: 100%;background-image: url('.$thumbSrc.');border:0px" allowfullscreen></iframe>'; 4953 $url = 'http://youtu.be/'.$id; 4954 4955 4956 if($mode === 'url') 4957 { 4958 return $url; 4959 } 4960 4961 4962 if($thumb === 'tag') 4963 { 4964 return "<img class='img-responsive img-fluid' src='".$thumbSrc."' alt='Youtube Video' style='width:".vartrue($parm['w'],'80')."px'/>"; 4965 } 4966 4967 if($thumb === 'email') 4968 { 4969 $thumbSrc = "http://i1.ytimg.com/vi/".$id."/maxresdefault.jpg"; // 640 x 480 4970 $filename = 'temp/yt-thumb-'.md5($id).".jpg"; 4971 $filepath = e_MEDIA.$filename; 4972 4973 4974 if(!file_exists($filepath)) 4975 { 4976 e107::getFile()->getRemoteFile($thumbSrc, $filename,'media'); 4977 } 4978 4979 return "<a href='".$url."'><img class='video-responsive video-thumbnail' src='{e_MEDIA}".$filename."' alt='".LAN_YOUTUBE_VIDEO."' title='".LAN_CLICK_TO_VIEW."' /> 4980 <div class='video-thumbnail-caption'><small>".LAN_CLICK_TO_VIEW."</small></div></a>"; 4981 } 4982 4983 if($thumb === 'src') 4984 { 4985 return $thumbSrc; 4986 } 4987 4988 4989 4990 if($thumb === 'video') 4991 { 4992 return '<div class="'.$defClass.' video-thumbnail thumbnail">'.$video.'</div>'; 4993 } 4994 4995 return '<div class="'.$defClass.' '.vartrue($parm['class']).'">'.$video.'</div>'; 4996 } 4997 4998 4999 if($type === 'youtubepl') 5000 { 5001 5002 if($thumb === 'tag') 5003 { 5004 $thumbSrc = e107::getMedia()->getThumb($id); 5005 5006 if(empty($thumbSrc)) 5007 { 5008 $thumbSrc = e_IMAGE_ABS."generic/playlist_120.png"; 5009 } 5010 return "<img class='img-responsive img-fluid' src='".$thumbSrc."' alt='".LAN_YOUTUBE_PLAYLIST."' style='width:".vartrue($parm['w'],'80')."px'/>"; 5011 5012 } 5013 5014 if($thumb === 'src') 5015 { 5016 $thumb = e107::getMedia()->getThumb($id); 5017 if(!empty($thumb)) 5018 { 5019 return $thumb; 5020 } 5021 else 5022 { 5023 // return "https://cdn0.iconfinder.com/data/icons/internet-2-2/64/youtube_playlist_videos_vid_web_online_internet-256.png"; 5024 return e_IMAGE_ABS."generic/playlist_120.png"; 5025 } 5026 } 5027 5028 $video = '<iframe width="560" height="315" src="https://www.youtube.com/embed/videoseries?list='.$id.'" style="border:0" allowfullscreen></iframe>'; 5029 return '<div class="'.$defClass.' '.vartrue($parm['class']).'">'.$video.'</div>'; 5030 } 5031 5032 if($type === 'mp4') 5033 { 5034 $file = $this->replaceConstants($file, 'abs'); 5035 5036 if($mode === 'url') 5037 { 5038 return $file; 5039 } 5040 5041 5042 $width = varset($parm['w'], 320); 5043 $height = varset($parm['h'], 240); 5044 $mime = varset($parm['mime'], 'video/mp4'); 5045 5046 return ' 5047 <div class="video-responsive"> 5048 <video width="'.$width.'" height="'.$height.'" controls> 5049 <source src="'.$file.'" type="'.$mime.'"> 5050 5051 Your browser does not support the video tag. 5052 </video> 5053 </div>'; 5054 } 5055 5056 5057 5058 return false; 5059 } 5060 5061 5062 5063 /** 5064 * Display a Date in the browser. 5065 * Includes support for 'livestamp' (http://mattbradley.github.io/livestampjs/) 5066 * @param integer $datestamp - unix timestamp 5067 * @param string $format - short | long | relative 5068 * @return string converted date (html) 5069 */ 5070 public function toDate($datestamp = null, $format='short') 5071 { 5072 if(!is_numeric($datestamp)){ return null; } 5073 5074 $value = e107::getDate()->convert_date($datestamp, $format); 5075 5076 $inc = ($format === 'relative') ? ' data-livestamp="'.$datestamp.'"' : ''; 5077 5078 return '<span'.$inc.'>'.$value.'</span>'; 5079 } 5080 5081 5082 5083 5084 5085 5086 /** 5087 * Parse new <x-bbcode> tags into bbcode output. 5088 * @param bool $retainTags : when you want to replace html and retain the <bbcode> tags wrapping it. 5089 * @return string html 5090 */ 5091 function parseBBTags($text,$retainTags = false) 5092 { 5093 $stext = str_replace(""", '"', $text); 5094 5095 $bbcodes = $this->getTags($stext, 'x-bbcode'); 5096 5097 foreach($bbcodes as $v) 5098 { 5099 foreach($v as $val) 5100 { 5101 $tag = base64_decode($val['alt']); 5102 $repl = ($retainTags == true) ? '$1'.$tag.'$2' : $tag; 5103 // $text = preg_replace('/(<x-bbcode[^>]*>).*(<\/x-bbcode>)/i',$repl, $text); 5104 $text = preg_replace('/(<x-bbcode alt=(?:"|")'.$val['alt'].'(?:"|")>).*(<\/x-bbcode>)/i',$repl, $text); 5105 5106 } 5107 } 5108 5109 return $text; 5110 } 5111 5112 5113 5114 /** 5115 * Perform and render XSS Test Comparison 5116 */ 5117 public function test($text='',$advanced = false) 5118 { 5119 // $tp = e107::getParser(); 5120 $sql = e107::getDb(); 5121 $tp = e107::getParser(); 5122 5123 if(empty($text)) 5124 { 5125 $text = <<<TMPL 5126[html]<p><strong>bold print</strong></p> 5127<pre class="prettyprint linenums"><a href='#'>Something</a></pre> 5128<p>Some text's and things.</p> 5129<p> </p> 5130<p><a href="/test.php?w=9&h=12">link</a></p> 5131<p>日本語 简体中文</p> 5132<p> </p> 5133[/html] 5134TMPL; 5135 } 5136 5137 // $text .= '[code=inline]<b class="something">Something</b>[/code]日本語 '; 5138 5139 // -------------------- Encoding ---------------- 5140 5141 $acc = $this->getScriptAccess(); 5142 $accName = e107::getUserClass()->uc_get_classname($acc); 5143 5144 echo "<h2>e107 Parser Test <small>with script access by <span class='label label-warning'>".$accName."</span></small></h2>"; 5145 echo"<h3>User-input <small>(eg. from \$_POST)</small></h3>"; 5146 5147 print_a($text); 5148 5149 $dbText = $tp->toDB($text,true); 5150 5151 echo "<h3>User-input ≫ toDB() "; 5152 5153 if($this->isHtml == true) 5154 { 5155 echo "<small>detected as <span class='label label-warning'>HTML</span></small>"; 5156 } 5157 else 5158 { 5159 echo "<small>detected as <span class='label label-info'>Plain text</span></small>"; 5160 } 5161 5162 echo "</h3>"; 5163 5164 print_a($dbText); 5165 5166 5167 if(!empty($advanced)) 5168 { 5169 echo "<div class='alert alert-warning'>"; 5170 $dbText2 = $tp->toDB($text, true, false, 'no_html'); 5171 echo "<h3>User-input ≫ toDb(\$text, true, false, 'no_html')</h3>"; 5172 print_a($dbText2); 5173 5174 echo "<div class='alert alert-warning'>"; 5175 $dbText3 = $tp->toDB($text, false, false, 'pReFs'); 5176 echo "<h3>User-input ≫ toDb(\$text, false, false, 'pReFs')</h3>"; 5177 print_a($dbText3); 5178 5179 // toClean 5180 $filter3 = $tp->filter($text, 'wds'); 5181 echo "<h3>User-input ≫ filter(\$text, 'wds')</h3>"; 5182 print_a( $filter3); 5183 5184 // Filter by String. 5185 $filter1 = $tp->filter($text,'str'); 5186 echo "<h3>User-input ≫ filter(\$text, 'str')</h3>"; 5187 print_a($filter1); 5188 5189 // Filter by Encoded. 5190 $filter2 = $tp->filter($text,'enc'); 5191 echo "<h3>User-input ≫ filter(\$text, 'enc')</h3>"; 5192 print_a($filter2); 5193 5194 5195 // toAttribute 5196 $toAtt = $tp->toAttribute($text); 5197 echo "<h3>User-input ≫ toAttribute(\$text)</h3>"; 5198 print_a($toAtt); 5199 5200 // toEmail 5201 $toEmail = $tp->toEmail($dbText); 5202 echo "<h3>User-input ≫ toEmail(\$text) <small>from DB</small></h3>"; 5203 print_a($toEmail); 5204 5205 // toEmail 5206 $toRss = $tp->toRss($text); 5207 echo "<h3>User-input ≫ toRss(\$text)</h3>"; 5208 print_a($toRss); 5209 5210 echo "</div>"; 5211 5212 5213 5214 } 5215 5216 echo "<h3>toDB() ≫ toHTML()</h3>"; 5217 $html = $tp->toHTML($dbText,true); 5218 print_a($html); 5219 5220 echo "<h3>toDB ≫ toHTML() <small>(rendered)</small></h3>"; 5221 echo $html; 5222 5223 echo "<h3>toDB ≫ toForm()</h3>"; 5224 $toForm = $tp->toForm($dbText); 5225 $toFormRender = e107::getForm()->open('test'); 5226 $toFormRender .= "<textarea cols='100' style='width:100%;height:300px' >".$toForm."</textarea>"; 5227 $toFormRender .= e107::getForm()->close(); 5228 5229 echo $toFormRender; 5230 5231 5232 echo "<h3>toDB ≫ bbarea</h3>"; 5233 echo e107::getForm()->bbarea('name',$toForm); 5234 5235 if(!empty($advanced)) 5236 { 5237 5238 echo "<h3>Allowed Tags</h3>"; 5239 print_a($this->allowedTags); 5240 5241 5242 echo "<h3>Converted Paths</h3>"; 5243 print_a($this->pathList); 5244 5245 echo "<h3>Removed Tags and Attributes</h3>"; 5246 print_a($this->removedList); 5247 5248 echo "<h3>Nodes to Convert</h3>"; 5249 print_a($this->nodesToConvert); 5250 5251 echo "<h3>Nodes to Disable SC</h3>"; 5252 print_a($this->nodesToDisableSC); 5253 } 5254 5255 similar_text($text, html_entity_decode( $toForm, ENT_COMPAT, 'UTF-8'),$perc); 5256 $scoreStyle = ($perc > 98) ? 'label-success' : 'label-danger'; 5257 echo "<h3><span class='label ".$scoreStyle."'>Similarity: ".number_format($perc)."%</span></h3>"; 5258 5259 echo "<table class='table table-bordered'> 5260 5261 5262 <tr> 5263 <th style='width:50%'>User-input</th> 5264 <th style='width:50%'>toForm() output</th> 5265 </tr> 5266 <tr> 5267 <td>".print_a($text,true)."</td> 5268 <td>". $toFormRender."</td> 5269 </tr> 5270 5271 </table>"; 5272 /* <tr> 5273 <td>".print_a(json_encode($text),true)."</td> 5274 <td>". print_a(json_encode(html_entity_decode( $toForm, ENT_COMPAT, 'UTF-8')),true)."</td> 5275 </tr>*/ 5276 5277 // print_a($text); 5278 5279return; 5280 5281//return; 5282 // --------------------------------- 5283 5284 5285 $html = $text; 5286 5287 $sql = e107::getDb(); 5288 $tp = e107::getParser(); 5289 $dbg = e107::getDebug(); 5290 5291 // $html = $this->getXss(); 5292 5293 echo "<h2>Unprocessed XSS</h2>"; 5294 // echo $html; // Remove Comment for a real mess! 5295 print_a($html); 5296 5297 echo "<h2>Standard v2 Parser</h2>"; 5298 echo "<h3>\$tp->dataFilter()</h3>"; 5299 // echo $tp->dataFilter($html); // Remove Comment for a real mess! 5300 $dbg->logTime('------ Start Parser Test -------'); 5301 print_a($tp->dataFilter($html)); 5302 $dbg->logTime('tp->dataFilter'); 5303 5304 echo "<h3>\$tp->toHTML()</h3>"; 5305 // echo $tp->dataFilter($html); // Remove Comment for a real mess! 5306 print_a($tp->toHTML($html)); 5307 $dbg->logTime('tp->toHtml'); 5308 5309 echo "<h3>\$tp->toDB()</h3>"; 5310 // echo $tp->dataFilter($html); // Remove Comment for a real mess! 5311 $todb = $tp->toDB($html); 5312 print_a( $todb); 5313 $dbg->logTime('tp->toDB'); 5314 5315 echo "<h3>\$tp->toForm() with toDB input.</h3>"; 5316 print_a( $tp->toForm($todb)); 5317 5318 echo "<h2>New Parser</h2>"; 5319 echo "<h3>Processed</h3>"; 5320 $cleaned = $this->cleanHtml($html, true); // false = don't check html pref. 5321 print_a($cleaned); 5322 $dbg->logTime('new Parser'); 5323 // $dbg->logTime('------ End Parser Test -------'); 5324 echo "<h3>Processed & Rendered</h3>"; 5325 echo $cleaned; 5326 5327 echo "<h2>New Parser - Data</h2>"; 5328 echo "<h3>Converted Paths</h3>"; 5329 print_a($this->pathList); 5330 5331 echo "<h3>Removed Tags and Attributes</h3>"; 5332 print_a($this->removedList); 5333 5334 // print_a($p); 5335 } 5336 5337 5338 5339 /** 5340 * Filters/Validates using the PHP5 filter_var() method. 5341 * @param $text 5342 * @param $type string str|int|email|url|w|wds|file 5343 * @return string | boolean | array 5344 */ 5345 function filter($text, $type='str',$validate=false) 5346 { 5347 if(empty($text)) 5348 { 5349 return $text; 5350 } 5351 5352 if($type === 'w') // words only. 5353 { 5354 return preg_replace('/[^\w]/',"",$text); 5355 } 5356 5357 if($type === 'd') // digits only. 5358 { 5359 return preg_replace('/[^\d]/',"",$text); 5360 } 5361 5362 if($type === 'wd') // words and digits only. 5363 { 5364 return preg_replace('/[^\w\d]/',"",$text); 5365 } 5366 5367 if($type === 'wds') // words, digits and spaces only. 5368 { 5369 return preg_replace('/[^\w\d ]/',"",$text); 5370 } 5371 5372 if($type === 'file') 5373 { 5374 return preg_replace('/[^\w\d_\.-]/',"-",$text); 5375 } 5376 5377 if($type === 'version') 5378 { 5379 return preg_replace('/[^\d_\.]/',"",$text); 5380 } 5381 5382 if($validate == false) 5383 { 5384 $filterTypes = array( 5385 'int' => FILTER_SANITIZE_NUMBER_INT, 5386 'str' => FILTER_SANITIZE_STRING, // no html. 5387 'email' => FILTER_SANITIZE_EMAIL, 5388 'url' => FILTER_SANITIZE_URL, 5389 'enc' => FILTER_SANITIZE_ENCODED 5390 ); 5391 } 5392 else 5393 { 5394 $filterTypes = array( 5395 'int' => FILTER_VALIDATE_INT, 5396 'email' => FILTER_VALIDATE_EMAIL, 5397 'ip' => FILTER_VALIDATE_IP, 5398 'url' => FILTER_VALIDATE_URL, 5399 5400 ); 5401 } 5402 5403 if(is_array($text)) 5404 { 5405 return filter_var_array($text, $filterTypes[$type]); 5406 } 5407 5408 5409 return filter_var($text, $filterTypes[$type]); 5410 5411 } 5412 5413 5414 private function grantScriptAccess() 5415 { 5416 $this->allowedTags = array_merge($this->allowedTags, $this->scriptTags); 5417 5418 foreach($this->allowedAttributes as $tag => $att) 5419 { 5420 foreach($this->scriptAttributes as $new) 5421 { 5422 $this->allowedAttributes[$tag][] = $new; 5423 } 5424 } 5425 5426 5427 return null; 5428 } 5429 5430 5431 5432 /** 5433 * Process and clean HTML from user input. 5434 * TODO Html5 tag support. 5435 * @param string $html raw HTML 5436 * @param boolean $checkPref 5437 * @return string 5438 */ 5439 public function cleanHtml($html='', $checkPref = true) 5440 { 5441 if(empty($html)){ return ''; } 5442 5443 if($this->isHtml($html) === false) 5444 { 5445 $html = str_replace('<','<',$html); 5446 $html = str_replace('>','>',$html); 5447 } 5448 5449 $html = str_replace(' ', '__E_PARSER_CLEAN_HTML_NON_BREAKING_SPACE__', $html); // prevent replacement of with spaces. 5450 // Workaround for https://bugs.php.net/bug.php?id=76285 5451 // Part 1 of 2 5452 $html = str_replace("\r", "", $html); // clean out windows line-breaks. 5453 $html = str_replace("\n", "__E_PARSER_CLEAN_HTML_LINE_BREAK__", $html); 5454 $html = str_replace("{", "__E_PARSER_CLEAN_HTML_CURLY_OPEN__", $html); 5455 $html = str_replace("}", "__E_PARSER_CLEAN_HTML_CURLY_CLOSED__", $html); 5456 5457 5458 if(strpos($html, "<body")===false) // HTML Fragment 5459 { 5460 $html = '<body>'.$html.'</body>'; 5461 } 5462 else // Full HTML page. 5463 { 5464 // $this->allowedTags[] = 'head'; 5465 // $this->allowedTags[] = 'body'; 5466 // $this->allowedTags[] = 'title'; 5467 //$this->allowedTags[] = 'meta'; 5468 } 5469 5470 if(!is_object($this->domObj)) 5471 { 5472 $this->init(); 5473 } 5474 5475 5476 if($this->scriptAccess === false) 5477 { 5478 $this->scriptAccess = e107::getConfig()->get('post_script', e_UC_NOBODY); // Pref to Allow <script> tags11; 5479 } 5480 5481 if(check_class($this->scriptAccess)) 5482 { 5483 $this->grantScriptAccess(); 5484 } 5485 5486 5487 // Set it up for processing. 5488 $doc = $this->domObj; 5489 libxml_use_internal_errors(true); 5490 if(function_exists('mb_convert_encoding')) 5491 { 5492 $html = mb_convert_encoding($html, 'HTML-ENTITIES', "UTF-8"); 5493 } 5494 5495 @$doc->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); 5496 5497 $this->nodesToConvert = array(); // required. 5498 $this->nodesToDelete = array(); // required. 5499 $this->removedList = array(); 5500 5501 $tmp = $doc->getElementsByTagName('*'); 5502 5503 /** @var DOMElement $node */ 5504 foreach($tmp as $node) 5505 { 5506 $path = $node->getNodePath(); 5507 5508 // echo "<br />Path = ".$path; 5509 // $tag = strval(basename($path)); 5510 5511 5512 if(strpos($path,'/code') !== false || strpos($path,'/pre') !== false) // treat as html. 5513 { 5514 $this->pathList[] = $path; 5515 // $this->nodesToConvert[] = $node->parentNode; // $node; 5516 $this->nodesToDisableSC[] = $node; 5517 continue; 5518 } 5519 5520 5521 $tag = preg_replace('/([a-z0-9\[\]\/]*)?\/([\w\-]*)(\[(\d)*\])?$/i', "$2", $path); 5522 if(!in_array($tag, $this->allowedTags)) 5523 { 5524 5525 $this->removedList['tags'][] = $tag; 5526 $this->nodesToDelete[] = $node; 5527 continue; 5528 } 5529 5530 foreach ($node->attributes as $attr) 5531 { 5532 $name = $attr->nodeName; 5533 $value = $attr->nodeValue; 5534 5535 $allow = isset($this->allowedAttributes[$tag]) ? $this->allowedAttributes[$tag] : $this->allowedAttributes['default']; 5536 5537 $removeAttributes = array(); 5538 5539 if(!in_array($name, $allow)) 5540 { 5541 5542 if(strpos($name,'data-') === 0 && $this->scriptAccess == true) 5543 { 5544 continue; 5545 } 5546 5547 $removeAttributes[] = $name; 5548 //$node->removeAttribute($name); 5549 $this->removedList['attributes'][] = $name. " from <".$tag.">"; 5550 continue; 5551 } 5552 5553 if($this->invalidAttributeValue($value)) // Check value against blacklisted values. 5554 { 5555 //$node->removeAttribute($name); 5556 $node->setAttribute($name, '#---sanitized---#'); 5557 $this->removedList['sanitized'][] = $tag.'['.$name.']'; 5558 } 5559 else 5560 { 5561 $_value = $this->secureAttributeValue($name, $value); 5562 5563 $node->setAttribute($name, $_value); 5564 if($_value !== $value) 5565 { 5566 $this->removedList['sanitized'][] = $tag.'['.$name.'] converted "'.$value.'" -> "'.$_value.'"'; 5567 } 5568 } 5569 } 5570 5571 // required - removing attributes in a loop breaks the loop 5572 if(!empty($removeAttributes)) 5573 { 5574 foreach ($removeAttributes as $name) 5575 { 5576 $node->removeAttribute($name); 5577 } 5578 } 5579 5580 5581 } 5582 5583 // Remove some stuff. 5584 foreach($this->nodesToDelete as $node) 5585 { 5586 $node->parentNode->removeChild($node); 5587 } 5588 5589 // Disable Shortcodes in pre/code 5590 5591 foreach($this->nodesToDisableSC as $key => $node) 5592 { 5593 $value = $node->C14N(); 5594 5595 if(empty($value)) 5596 { 5597 continue; 5598 } 5599 5600 $value = str_replace("
", "\r", $value); 5601 5602 if($node->nodeName === 'pre') 5603 { 5604 $value = preg_replace('/^<pre[^>]*>/', '', $value); 5605 $value = str_replace("</pre>", "", $value); 5606 $value = str_replace('<br></br>', "__E_PARSER_CLEAN_HTML_LINE_BREAK__", $value); 5607 } 5608 elseif($node->nodeName === 'code') 5609 { 5610 $value = preg_replace('/^<code[^>]*>/', '', $value); 5611 $value = str_replace("</code>", "", $value); 5612 $value = str_replace("<br></br>", "__E_PARSER_CLEAN_HTML_LINE_BREAK__", $value); 5613 } 5614 5615 $value = str_replace('__E_PARSER_CLEAN_HTML_CURLY_OPEN__', '{{{', $value); // temporarily change {e_XXX} to {{{e_XXX}}} 5616 $value = str_replace('__E_PARSER_CLEAN_HTML_CURLY_CLOSED__', '}}}', $value); // temporarily change {e_XXX} to {{{e_XXX}}} 5617 5618 5619 $newNode = $doc->createElement($node->nodeName); 5620 $newNode->nodeValue = $value; 5621 5622 if($class = $node->getAttribute('class')) 5623 { 5624 $newNode->setAttribute('class',$class); 5625 } 5626 5627 if($style = $node->getAttribute('style')) 5628 { 5629 $newNode->setAttribute('style',$style); 5630 } 5631 5632 $node->parentNode->replaceChild($newNode, $node); 5633 } 5634 5635 5636 5637 // Convert <code> and <pre> Tags to Htmlentities. 5638 /* TODO XXX Still necessary? Perhaps using bbcodes only? 5639 foreach($this->nodesToConvert as $node) 5640 { 5641 $value = $node->C14N(); 5642 5643 $value = str_replace("
","",$value); 5644 5645 // print_a("WOWOWO"); 5646 5647 if($node->nodeName == 'pre') 5648 { 5649 $value = substr($value,5); 5650 $end = strrpos($value,"</pre>"); 5651 $value = substr($value,0,$end); 5652 } 5653 5654 if($node->nodeName == 'code') 5655 { 5656 $value = substr($value,6); 5657 $end = strrpos($value,"</code>"); 5658 $value = substr($value,0,$end); 5659 } 5660 5661 $value = htmlentities(htmlentities($value)); // Needed 5662 $node->nodeValue = $value; 5663 } 5664 */ 5665 5666 $cleaned = $doc->saveHTML($doc->documentElement); // $doc->documentElement fixes utf-8/entities issue. @see http://stackoverflow.com/questions/8218230/php-domdocument-loadhtml-not-encoding-utf-8-correctly 5667 // Workaround for https://bugs.php.net/bug.php?id=76285 5668 // Part 2 of 2 5669 $cleaned = str_replace("\n", "", $cleaned); 5670 $cleaned = str_replace("__E_PARSER_CLEAN_HTML_LINE_BREAK__", "\n", $cleaned); 5671 5672 $cleaned = str_replace('__E_PARSER_CLEAN_HTML_NON_BREAKING_SPACE__', ' ', $cleaned); // prevent replacement of with spaces. - convert back. 5673 5674 $cleaned = str_replace('{{{','{', $cleaned); // convert shortcode temporary triple-curly braces back to entities. 5675 $cleaned = str_replace('}}}','}', $cleaned); // convert shortcode temporary triple-curly braces back to entities. 5676 5677 $cleaned = str_replace("__E_PARSER_CLEAN_HTML_CURLY_OPEN__","{", $cleaned); 5678 $cleaned = str_replace("__E_PARSER_CLEAN_HTML_CURLY_CLOSED__","}", $cleaned); 5679 5680 $cleaned = str_replace(array('<body>','</body>'),'', $cleaned); // filter out tags. 5681 5682 return trim($cleaned); 5683 } 5684 5685 public function secureAttributeValue($attribute, $value) 5686 { 5687 $search = isset($this->replaceAttrValues[$attribute]) ? $this->replaceAttrValues[$attribute] : $this->replaceAttrValues['default']; 5688 if(!empty($search)) 5689 { 5690 $value = str_replace($search, '', $value); 5691 } 5692 return $value; 5693 } 5694 5695 5696 /** 5697 * Check for Invalid Attribute Values 5698 * @param $value string 5699 * @return true/false 5700 */ 5701 function invalidAttributeValue($value) 5702 { 5703 5704 5705 foreach($this->badAttrValues as $v) // global list because a bad value is bad regardless of the attribute it's in. ;-) 5706 { 5707 if(preg_match('/'.$v.'/i',$value)==true) 5708 { 5709 $this->removedList['blacklist'][] = "Match found for '{$v}' in '{$value}'"; 5710 5711 return true; 5712 } 5713 5714 } 5715 5716 return false; 5717 } 5718 5719 5720 5721 /** 5722 * XSS HTML code to test against 5723 */ 5724 public function getXss() 5725 { 5726 5727$html = <<<EOF 5728Internationalization Test: 5729ภาษาไทย <br /> 5730日本語 <br /> 5731简体中文 <br /> 5732<a href='somewhere.html' src='invalidatrribute' >Test</a> 5733A GOOD LINK: <a href='http://mylink.php'>Some Link</a> 5734<a href='javascript: something' src='invalidatrribute' >Test regex</a> 5735<img href='invalidattribute' src='myimage.jpg' /> 5736<frameset onload=alert(1) data-something=where> 5737<table background="javascript:alert(1)"><tr><td><a href="something.php" onclick="alert(1)">Hi there</a></td></tr></table> 5738<div> 5739<!--<img src="--><img src=x onerror=alert(1)//"> 5740<comment><img src="</comment><img src=x onerror=alert(1)//"> 5741<ul> 5742<li style=list-style:url() onerror=alert(1)></li> <div style=content:url(data:image/svg+xml,%3Csvg/%3E);visibility:hidden onload=alert(1)></div> 5743</ul> 5744</div> 5745</frameset> 5746<head><base href="javascript://"/></head><body><a href="/. /,alert(1)//#">XXX</a></body> 5747<SCRIPT FOR=document EVENT=onreadystatechange>alert(1)</SCRIPT> 5748<OBJECT CLASSID="clsid:333C7BC4-460F-11D0-BC04-0080C7055A83"><PARAM NAME="DataURL" VALUE="javascript:alert(1)"></OBJECT> 5749<b <script>alert(1)//</script>0</script></b> 5750<div id="div1"><input value="``onmouseover=alert(1)"></div> <div id="div2"></div>< 5751script>document.getElementById("div2").innerHTML = document.getElementById("div1").innerHTML;</script> 5752Some example text<br /> 5753<b>This is bold</b><br /> 5754<i>This is italic</i><br /> 5755<small>Some small text</small> 5756<pre>This is pre-formatted 5757 <script>alert('something')</script> 5758 <b>Bold Stuff</b> 5759 <pre>something</pre> 5760 <code>code</code> 5761 <b>BOLD</b> 5762 function myfunction() 5763 { 5764 5765 } 5766 </pre> 5767<code> 5768 function myfunction() 5769 { 5770 5771 } 5772 5773<script>alert('something')</script> 5774</code> 5775<svg><![CDATA[><image xlink:href="]]><img src=xx:x onerror=alert(2)//"></svg> 5776<style><img src="</style><img src=x onerror=alert(1)//"> 5777<x '="foo"><x foo='><img src=x onerror=alert(1)//'> <!-- IE 6-9 --> <! '="foo"><x foo='><img src=x onerror=alert(2)//'> <? '="foo"><x foo='><img src=x onerror=alert(3)//'> 5778<embed src="javascript:alert(1)"></embed> // O10.10↓, OM10.0↓, GC6↓, FF <img src="javascript:alert(2)"> <image src="javascript:alert(2)"> // IE6, O10.10↓, OM10.0↓ <script src="javascript:alert(3)"></script> // IE6, O11.01↓, OM10.1↓ 5779<div style=width:1px;filter:glow onfilterchange=alert(1)>x</div> 5780<object allowscriptaccess="always" data="test.swf"></object> 5781[A] <? foo="><script>alert(1)</script>"> <! foo="><script>alert(1)</script>"> </ foo="><script>alert(1)</script>"> [B] <? foo="><x foo='?><script>alert(1)</script>'>"> [C] <! foo="[[[x]]"><x foo="]foo><script>alert(1)</script>"> [D] <% foo><x foo="%><script>alert(1)</script>"> 5782<iframe src=mhtml:http://html5sec.org/test.html!xss.html></iframe> <iframe src=mhtml:http://html5sec.org/test.gif!xss.html></iframe> 5783<html> <body> <b>some content without two new line \n\n</b> Content-Type: multipart/related; boundary="******"<b>some content without two new line</b> --****** Content-Location: xss.html Content-Transfer-Encoding: base64 PGlmcmFtZSBuYW1lPWxvIHN0eWxlPWRpc3BsYXk6bm9uZT48L2lmcmFtZT4NCjxzY3JpcHQ+DQp1 cmw9bG9jYXRpb24uaHJlZjtkb2N1bWVudC5nZXRFbGVtZW50c0J5TmFtZSgnbG8nKVswXS5zcmM9 dXJsLnN1YnN0cmluZyg2LHVybC5pbmRleE9mKCcvJywxNSkpO3NldFRpbWVvdXQoImFsZXJ0KGZy YW1lc1snbG8nXS5kb2N1bWVudC5jb29raWUpIiwyMDAwKTsNCjwvc2NyaXB0PiAgICAg --******-- </body> </html> 5784<!-- IE 5-9 --> <div id=d><x xmlns="><iframe onload=alert(1)"></div> <script>d.innerHTML+='';</script> <!-- IE 10 in IE5-9 Standards mode --> <div id=d><x xmlns='"><iframe onload=alert(2)//'></div> <script>d.innerHTML+='';</script> 5785<img[a][b]src=x[d]onerror[c]=[e]"alert(1)"> 5786<a href="[a]java[b]script[c]:alert(1)">XXX</a> 5787<img src="x` `<script>alert(1)</script>"` `> 5788<img src onerror /" '"= alt=alert(1)//"> 5789<title onpropertychange=alert(1)></title><title title=></title> 5790<!-- IE 5-8 standards mode --> <a href=http://foo.bar/#x=`y></a><img alt="`><img src=xx:x onerror=alert(1)></a>"> <!-- IE 5-9 standards mode --> <!a foo=x=`y><img alt="`><img src=xx:x onerror=alert(2)//"> <?a foo=x=`y><img alt="`><img src=xx:x onerror=alert(3)//"> 5791<!--[if]><script>alert(1)</script --> <!--[if<img src=x onerror=alert(2)//]> --> 5792<script> Blabla </script> 5793<script src="/\example.com\foo.js"></script> // Safari 5.0, Chrome 9, 10 <script src="\\example.com\foo.js"></script> // Safari 5.0 5794<object id="x" classid="clsid:CB927D12-4FF7-4a9e-A169-56E4B8A75598"></object> <object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" onqt_error="alert(1)" style="behavior:url(#x);"><param name=postdomevents /></object> 5795<!-- `<img/src=xx:xx onerror=alert(1)//--!> 5796<xmp> <% </xmp> <img alt='%></xmp><img src=xx:x onerror=alert(1)//'> <script> x='<%' </script> %>/ alert(2) </script> XXX <style> *['<!--']{} </style> -->{} *{color:red}</style> 5797<a style="-o-link:'javascript:alert(1)';-o-link-source:current">X</a> 5798<style>p[foo=bar{}*{-o-link:'javascript:alert(1)'}{}*{-o-link-source:current}*{background:red}]{background:green};</style> 5799<div style="font-family:'foo[a];color:red;';">XXX</div> 5800<form id="test"></form><button form="test" formaction="javascript:alert(1)">X</button> 5801<input onfocus=write(1) autofocus> 5802<video poster=javascript:alert(1)//></video> 5803<video>somemovei.mp4</video> 5804<body onscroll=alert(1)><br><br><br><br><br><br>...<br><br><br><br><input autofocus> 5805 5806<article id="something">Some text goes here</article> 5807 5808 5809EOF; 5810 5811return $html; 5812 5813 } 5814 5815 5816 5817 5818} 5819 5820 5821 5822class e_emotefilter 5823{ 5824 private $search = array(); 5825 private $replace = array(); 5826 public $emotes; 5827 private $singleSearch = array(); 5828 private $singleReplace = array(); 5829 5830 function __construct() 5831 { 5832 $pref = e107::getPref(); 5833 5834 if(empty($pref['emotepack'])) 5835 { 5836 $pref['emotepack'] = "default"; 5837 e107::getConfig('emote')->clearPrefCache('emote'); 5838 e107::getConfig('core')->set('emotepack','default')->save(false,true,false); 5839 } 5840 5841 $this->emotes = e107::getConfig("emote")->getPref(); 5842 5843 if(empty($this->emotes)) 5844 { 5845 return; 5846 } 5847 5848 $base = defined('e_HTTP_STATIC') && is_string(e_HTTP_STATIC) ? e_HTTP_STATIC : SITEURLBASE; 5849 5850 foreach($this->emotes as $key => $value) 5851 { 5852 5853 $value = trim($value); 5854 5855 if ($value) 5856 { // Only 'activate' emote if there's a substitution string set 5857 5858 5859 $key = preg_replace("#!(\w{3,}?)$#si", ".\\1", $key); 5860 // Next two probably to sort out legacy issues - may not be required any more 5861 // $key = preg_replace("#_(\w{3})$#", ".\\1", $key); 5862 5863 $key = str_replace("!", "_", $key); 5864 5865 $filename = e_IMAGE."emotes/" . $pref['emotepack'] . "/" . $key; 5866 5867 5868 5869 $fileloc = $base.e_IMAGE_ABS."emotes/" . $pref['emotepack'] . "/" . $key; 5870 5871 $alt = str_replace(array('.png','.gif', '.jpg'),'', $key); 5872 5873 if(file_exists($filename)) 5874 { 5875 $tmp = explode(" ", $value); 5876 foreach($tmp as $code) 5877 { 5878 $img = "<img class='e-emoticon' src='".$fileloc."' alt=\"".$alt."\" />"; 5879 5880 $this->search[] = "\n".$code; 5881 $this->replace[] = "\n".$img; 5882 5883 $this->search[] = " ".$code; 5884 $this->replace[] = " ".$img; 5885 5886 $this->search[] = ">".$code; // Fix for emote within html. 5887 $this->replace[] = ">".$img; 5888 5889 $this->singleSearch[] = $code; 5890 $this->singleReplace[] = $img; 5891 5892 } 5893 5894 5895 /* 5896 if(strstr($value, " ")) 5897 { 5898 $tmp = explode(" ", $value); 5899 foreach($tmp as $code) 5900 { 5901 $this->search[] = " ".$code; 5902 $this->search[] = "\n".$code; 5903 5904 $this->replace[] = " <img class='e-emoticon' src='".$fileloc."' alt=\"".$alt."\" /> "; 5905 $this->replace[] = "\n <img class='e-emoticon' src='".$fileloc."'alt=\"".$alt."\" /> "; 5906 } 5907 unset($tmp); 5908 } 5909 else 5910 { 5911 if($value) 5912 { 5913 $this->search[] = " ".$value; 5914 $this->search[] = "\n".$value; 5915 5916 $this->replace[] = " <img class='e-emoticon' src='".$fileloc."' alt=\"".$alt."\" /> "; 5917 $this->replace[] = "\n <img class='e-emoticon' src='".$fileloc."' alt=\"".$alt."\" /> "; 5918 } 5919 }*/ 5920 } 5921 } 5922 else 5923 { 5924 unset($this->emotes[$key]); 5925 } 5926 5927 5928 } 5929 5930 // print_a($this->regSearch); 5931 // print_a($this->regReplace); 5932 5933 } 5934 5935 5936 function filterEmotes($text) 5937 { 5938 5939 if(empty($text)) 5940 { 5941 return ''; 5942 } 5943 5944 if(!empty($this->singleSearch) && (strlen($text) < 12) && in_array($text, $this->singleSearch)) // just one emoticon with no space, line-break or html tags around it. 5945 { 5946 return str_replace($this->singleSearch,$this->singleReplace,$text); 5947 } 5948 5949 return str_replace($this->search, $this->replace, $text); 5950 5951 } 5952 5953 5954 function filterEmotesRev($text) 5955 { 5956 return str_replace($this->replace, $this->search, $text); 5957 } 5958} 5959 5960 5961class e_profanityFilter 5962{ 5963 var $profanityList; 5964 5965 function __construct() 5966 { 5967 global $pref; 5968 5969 $words = explode(",", $pref['profanity_words']); 5970 $word_array = array(); 5971 foreach($words as $word) 5972 { 5973 $word = trim($word); 5974 if($word != "") 5975 { 5976 $word_array[] = $word; 5977 if (strpos($word, '$') !== FALSE) 5978 { 5979 $word_array[] = str_replace('$', '\$', $word); // Special case - '$' may be 'in clear' or as entity 5980 } 5981 } 5982 } 5983 if(count($word_array)) 5984 { 5985 $this->profanityList = str_replace('#','\#',implode("\b|\b", $word_array)); // We can get entities in the string - confuse the regex delimiters 5986 } 5987 unset($words); 5988 return TRUE; 5989 } 5990 5991 function filterProfanities($text) 5992 { 5993 global $pref; 5994 if (!$this->profanityList) 5995 { 5996 return $text; 5997 } 5998 if ($pref['profanity_replace']) 5999 { 6000 return preg_replace("#\b".$this->profanityList."\b#is", $pref['profanity_replace'], $text); 6001 } 6002 else 6003 { 6004 return preg_replace_callback("#\b".$this->profanityList."\b#is", array($this, 'replaceProfanities'), $text); 6005 } 6006 } 6007 6008 function replaceProfanities($matches) 6009 { 6010 /*! 6011 @function replaceProfanities callback 6012 @abstract replaces vowels in profanity words with stars 6013 @param text string - text string to be filtered 6014 @result filtered text 6015 */ 6016 6017 return preg_replace("#a|e|i|o|u#i", "*" , $matches[0]); 6018 } 6019} 6020 6021 6022/** 6023 * Backwards Compatibility Class textparse 6024 */ 6025class textparse { 6026 6027 function editparse($text, $mode = "off") 6028 { 6029 if(E107_DBG_DEPRECATED) 6030 { 6031 e107::getDebug()->logDeprecated(); 6032 } 6033 6034 return e107::getParser()->toForm($text); 6035 } 6036 6037 function tpa($text, $mode = '', $referrer = '', $highlight_search = false, $poster_id = '') 6038 { 6039 if(E107_DBG_DEPRECATED) 6040 { 6041 e107::getDebug()->logDeprecated(); 6042 } 6043 6044 return e107::getParser()->toHTML($text, true, $mode, $poster_id); 6045 } 6046 6047 function tpj($text) 6048 { 6049 6050 if(E107_DBG_DEPRECATED) 6051 { 6052 e107::getDebug()->logDeprecated(); 6053 } 6054 6055 return $text; 6056 } 6057 6058 function formtpa($text, $mode = '') 6059 { 6060 6061 if(E107_DBG_DEPRECATED) 6062 { 6063 e107::getDebug()->logDeprecated(); 6064 } 6065 6066 return e107::getParser()->toDB($text); 6067 } 6068 6069 function formtparev($text) 6070 { 6071 6072 if(E107_DBG_DEPRECATED) 6073 { 6074 e107::getDebug()->logDeprecated(); 6075 } 6076 6077 return e107::getParser()->toForm($text); 6078 } 6079 6080}