Textpattern | PHP Cross Reference | Content Management Systems |
Description: Collection of miscellaneous tools.
1 <?php 2 3 /* 4 * Textpattern Content Management System 5 * http://textpattern.com 6 * 7 * Copyright (C) 2016 The Textpattern Development Team 8 * 9 * This file is part of Textpattern. 10 * 11 * Textpattern is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU General Public License 13 * as published by the Free Software Foundation, version 2. 14 * 15 * Textpattern is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with Textpattern. If not, see <http://www.gnu.org/licenses/>. 22 */ 23 24 /** 25 * Collection of miscellaneous tools. 26 * 27 * @package Misc 28 */ 29 30 /** 31 * Strips NULL bytes. 32 * 33 * @param string|array $in The input value 34 * @return mixed 35 */ 36 37 function deNull($in) 38 { 39 return is_array($in) ? doArray($in, 'deNull') : strtr($in, array("\0" => '')); 40 } 41 42 /** 43 * Strips carriage returns and linefeeds. 44 * 45 * @param string|array $in The input value 46 * @return mixed 47 */ 48 49 function deCRLF($in) 50 { 51 return is_array($in) ? doArray($in, 'deCRLF') : strtr($in, array("\n" => '', "\r" => '')); 52 } 53 54 /** 55 * Applies a callback to a given string or an array. 56 * 57 * @param string|array $in An array or a string to run through the callback function 58 * @param callback $function The callback function 59 * @return mixed 60 * @example 61 * echo doArray(array('value1', 'value2'), 'intval'); 62 */ 63 64 function doArray($in, $function) 65 { 66 if (is_array($in)) { 67 return array_map($function, $in); 68 } 69 70 if (is_array($function)) { 71 return call_user_func($function, $in); 72 } 73 74 return $function($in); 75 } 76 77 /** 78 * Un-quotes a quoted string or an array of values. 79 * 80 * @param string|array $in The input value 81 * @return mixed 82 */ 83 84 function doStrip($in) 85 { 86 return is_array($in) ? doArray($in, 'doStrip') : doArray($in, 'stripslashes'); 87 } 88 89 /** 90 * Strips HTML and PHP tags from a string or an array. 91 * 92 * @param string|array $in The input value 93 * @return mixed 94 * @example 95 * echo doStripTags('<p>Hello world!</p>'); 96 */ 97 98 function doStripTags($in) 99 { 100 return is_array($in) ? doArray($in, 'doStripTags') : doArray($in, 'strip_tags'); 101 } 102 103 /** 104 * Converts entity escaped brackets back to characters. 105 * 106 * @param string|array $in The input value 107 * @return mixed 108 */ 109 110 function doDeEnt($in) 111 { 112 return doArray($in, 'deEntBrackets'); 113 } 114 115 /** 116 * Converts entity escaped brackets back to characters. 117 * 118 * @param string $in The input value 119 * @return string 120 */ 121 122 function deEntBrackets($in) 123 { 124 $array = array( 125 '<' => '<', 126 '<' => '<', 127 '<' => '<', 128 '>' => '>', 129 '>' => '>', 130 '>' => '>', 131 ); 132 133 foreach ($array as $k => $v) { 134 $in = preg_replace("/".preg_quote($k)."/i", $v, $in); 135 } 136 137 return $in; 138 } 139 140 /** 141 * Escapes special characters for use in an SQL statement. 142 * 143 * Always use this function when dealing with user-defined values in SQL 144 * statements. If this function is not used to escape user-defined data in a 145 * statement, the query is vulnerable to SQL injection attacks. 146 * 147 * @param string|array $in The input value 148 * @return mixed An array of escaped values or a string depending on $in 149 * @package DB 150 * @example 151 * echo safe_field('column', 'table', "color = '" . doSlash(gps('color')) . "'"); 152 */ 153 154 function doSlash($in) 155 { 156 return doArray($in, 'safe_escape'); 157 } 158 159 /** 160 * Escape SQL LIKE pattern's wildcards for use in an SQL statement. 161 * 162 * @param string|array $in The input value 163 * @return mixed An array of escaped values or a string depending on $in 164 * @since 4.6.0 165 * @package DB 166 * @example 167 * echo safe_field('column', 'table', "color LIKE '" . doLike(gps('color')) . "'"); 168 */ 169 170 function doLike($in) 171 { 172 return doArray($in, 'safe_escape_like'); 173 } 174 175 /** 176 * A shell for htmlspecialchars() with $flags defaulting to ENT_QUOTES. 177 * 178 * @param string $string The string being converted 179 * @param int $flags A bitmask of one or more flags. The default is ENT_QUOTES 180 * @param string $encoding Defines encoding used in conversion. The default is UTF-8 181 * @param bool $double_encode When double_encode is turned off PHP will not encode existing HTML entities, the default is to convert everything 182 * @return string 183 * @see https://secure.php.net/manual/en/function.htmlspecialchars.php 184 * @since 4.5.0 185 * @package Filter 186 */ 187 188 function txpspecialchars($string, $flags = ENT_QUOTES, $encoding = 'UTF-8', $double_encode = true) 189 { 190 // Ignore ENT_HTML5 and ENT_XHTML for now. 191 // ENT_HTML5 and ENT_XHTML are defined in PHP 5.4+ but we consistently encode single quotes as ' in any doctype. 192 // global $prefs; 193 // static $h5 = null; 194 // 195 // if (defined(ENT_HTML5)) { 196 // if ($h5 === null) { 197 // $h5 = ($prefs['doctype'] == 'html5' && txpinterface == 'public'); 198 // } 199 // 200 // if ($h5) { 201 // $flags = ($flags | ENT_HTML5) & ~ENT_HTML401; 202 // } 203 // } 204 // 205 return htmlspecialchars($string, $flags, $encoding, $double_encode); 206 } 207 208 /** 209 * Converts special characters to HTML entities. 210 * 211 * @param array|string $in The input value 212 * @return mixed The array or string with HTML syntax characters escaped 213 * @package Filter 214 */ 215 216 function doSpecial($in) 217 { 218 return doArray($in, 'txpspecialchars'); 219 } 220 221 /** 222 * Converts the given value to NULL. 223 * 224 * @param mixed $a The input value 225 * @return null 226 * @package Filter 227 * @access private 228 */ 229 230 function _null($a) 231 { 232 return null; 233 } 234 235 /** 236 * Converts an array of values to NULL. 237 * 238 * @param array $in The array 239 * @return array 240 * @package Filter 241 */ 242 243 function array_null($in) 244 { 245 return array_map('_null', $in); 246 } 247 248 /** 249 * Escapes a page title. Converts <, >, ', " characters to HTML entities. 250 * 251 * @param string $title The input string 252 * @return string The string escaped 253 * @package Filter 254 */ 255 256 function escape_title($title) 257 { 258 return strtr($title, array( 259 '<' => '<', 260 '>' => '>', 261 "'" => ''', 262 '"' => '"', 263 )); 264 } 265 266 /** 267 * Sanitises a string for use in a JavaScript string. 268 * 269 * Escapes \, \n, \r, " and ' characters. It removes 'PARAGRAPH SEPARATOR' 270 * (U+2029) and 'LINE SEPARATOR' (U+2028). When you need to pass a string 271 * from PHP to JavaScript, use this function to sanitise the value to avoid 272 * XSS attempts. 273 * 274 * @param string $js JavaScript input 275 * @return string Escaped JavaScript 276 * @since 4.4.0 277 * @package Filter 278 */ 279 280 function escape_js($js) 281 { 282 $js = preg_replace('/[\x{2028}\x{2029}]/u', '', $js); 283 284 return addcslashes($js, "\\\'\"\n\r"); 285 } 286 287 /** 288 * A shell for htmlspecialchars() with $flags defaulting to ENT_QUOTES. 289 * 290 * @param string $str The input string 291 * @return string 292 * @deprecated in 4.2.0 293 * @see txpspecialchars() 294 * @package Filter 295 */ 296 297 function escape_output($str) 298 { 299 trigger_error(gTxt('deprecated_function_with', array('{name}' => __FUNCTION__, '{with}' => 'txpspecialchars')), E_USER_NOTICE); 300 301 return txpspecialchars($str); 302 } 303 304 /** 305 * Replaces < and > characters with entities. 306 * 307 * @param string $str The input string 308 * @return string 309 * @deprecated in 4.2.0 310 * @see txpspecialchars() 311 * @package Filter 312 */ 313 314 function escape_tags($str) 315 { 316 trigger_error(gTxt('deprecated_function', array('{name}' => __FUNCTION__)), E_USER_NOTICE); 317 318 return strtr($str, array( 319 '<' => '<', 320 '>' => '>', 321 )); 322 } 323 324 /** 325 * Escapes CDATA section for an XML document. 326 * 327 * @param string $str The string 328 * @return string XML representation wrapped in CDATA tags 329 * @package XML 330 */ 331 332 function escape_cdata($str) 333 { 334 return '<![CDATA['.str_replace(']]>', ']]]><![CDATA[]>', $str).']]>'; 335 } 336 337 /** 338 * Returns a localisation string. 339 * 340 * @param string $var String name 341 * @param array $atts Replacement pairs 342 * @param string $escape Convert special characters to HTML entities. Either "html" or "" 343 * @return string A localisation string 344 * @package L10n 345 */ 346 347 function gTxt($var, $atts = array(), $escape = 'html') 348 { 349 global $textarray; 350 351 if (!is_array($atts)) { 352 $atts = array(); 353 } 354 355 if ($escape == 'html') { 356 foreach ($atts as $key => $value) { 357 $atts[$key] = txpspecialchars($value); 358 } 359 } 360 361 $v = strtolower($var); 362 363 if (isset($textarray[$v])) { 364 $out = $textarray[$v]; 365 366 if ($out !== '') { 367 return strtr($out, $atts); 368 } 369 } 370 371 if ($atts) { 372 return $var.': '.join(', ', $atts); 373 } 374 375 return $var; 376 } 377 378 /** 379 * Loads client-side localisation scripts. 380 * 381 * Passes localisation strings from the database to JavaScript. 382 * 383 * Only works on the admin-side pages. 384 * 385 * @param string|array $var Scalar or array of string keys 386 * @param array $atts Array or array of arrays of variable substitution pairs 387 * @param array $route Optional events/steps upon which to add the strings 388 * @since 4.5.0 389 * @package L10n 390 * @example 391 * gTxtScript(array('string1', 'string2', 'string3')); 392 */ 393 394 function gTxtScript($var, $atts = array(), $route = array()) 395 { 396 global $textarray_script, $event, $step; 397 398 $targetEvent = empty($route[0]) ? null : (array)$route[0]; 399 $targetStep = empty($route[1]) ? null : (array)$route[1]; 400 401 if (($targetEvent === null || in_array($event, $targetEvent)) && ($targetStep === null || in_array($step, $targetStep))) { 402 if (!is_array($textarray_script)) { 403 $textarray_script = array(); 404 } 405 406 $data = is_array($var) ? array_map('gTxt', $var, $atts) : (array) gTxt($var, $atts); 407 $textarray_script = $textarray_script + array_combine((array) $var, $data); 408 } 409 } 410 411 /** 412 * Returns given timestamp in a format of 01 Jan 2001 15:19:16. 413 * 414 * @param int $timestamp The UNIX timestamp 415 * @return string A formatted date 416 * @access private 417 * @see safe_stftime() 418 * @package DateTime 419 * @example 420 * echo gTime(); 421 */ 422 423 function gTime($timestamp = 0) 424 { 425 return safe_strftime('%d %b %Y %X', $timestamp); 426 } 427 428 /** 429 * Creates a dumpfile from a backtrace and outputs given parameters. 430 * 431 * @package Debug 432 */ 433 434 function dmp() 435 { 436 static $f = false; 437 438 if (defined('txpdmpfile')) { 439 global $prefs; 440 441 if (!$f) { 442 $f = fopen($prefs['tempdir'].'/'.txpdmpfile, 'a'); 443 } 444 445 $stack = get_caller(); 446 fwrite($f, "\n[".$stack[0].t.safe_strftime('iso8601')."]\n"); 447 } 448 449 $a = func_get_args(); 450 451 if (!$f) { 452 echo "<pre dir=\"auto\">".n; 453 } 454 455 foreach ($a as $thing) { 456 $out = is_scalar($thing) ? strval($thing) : var_export($thing, true); 457 458 if ($f) { 459 fwrite($f, $out.n); 460 } else { 461 echo txpspecialchars($out).n; 462 } 463 } 464 465 if (!$f) { 466 echo "</pre>".n; 467 } 468 } 469 470 /** 471 * Gets the given language's strings from the database. 472 * 473 * Fetches the given language from the database and returns the strings 474 * as an array. 475 * 476 * If no $events is specified, only appropriate strings for the current context 477 * are returned. If 'txpinterface' constant equals 'admin' all strings are 478 * returned. Otherwise, only strings from events 'common' and 'public'. 479 * 480 * If $events is FALSE, returns all strings. 481 * 482 * @param string $lang The language code 483 * @param array|string|bool $events An array of loaded events 484 * @return array 485 * @package L10n 486 * @see load_lang_event() 487 * @example 488 * print_r( 489 * load_lang('en-gb', false) 490 * ); 491 */ 492 493 function load_lang($lang, $events = null) 494 { 495 if ($events === null && txpinterface != 'admin') { 496 $events = array('public', 'common'); 497 } 498 499 $where = " AND name != ''"; 500 501 if ($events) { 502 $where .= " AND event IN (".join(',', quote_list((array) $events)).")"; 503 } 504 505 $out = array(); 506 507 foreach (array($lang, 'en-gb') as $lang_code) { 508 $rs = safe_rows_start("name, data", 'txp_lang', "lang = '".doSlash($lang_code)."'".$where); 509 510 if (!empty($rs)) { 511 while ($a = nextRow($rs)) { 512 $out[$a['name']] = $a['data']; 513 } 514 515 return $out; 516 } 517 } 518 519 return $out; 520 } 521 522 /** 523 * Loads date definitions from a localisation file. 524 * 525 * @param string $lang The language 526 * @package L10n 527 * @deprecated in 4.6.0 528 */ 529 530 function load_lang_dates($lang) 531 { 532 $filename = is_file(txpath.'/lang/'.$lang.'_dates.txt') ? 533 txpath.'/lang/'.$lang.'_dates.txt' : 534 txpath.'/lang/en-gb_dates.txt'; 535 $file = @file(txpath.'/lang/'.$lang.'_dates.txt', 'r'); 536 537 if (is_array($file)) { 538 foreach ($file as $line) { 539 if ($line[0] == '#' || strlen($line) < 2) { 540 continue; 541 } 542 543 list($name, $val) = explode('=>', $line, 2); 544 $out[trim($name)] = trim($val); 545 } 546 547 return $out; 548 } 549 550 return false; 551 } 552 553 /** 554 * Gets language strings for the given event. 555 * 556 * If no $lang is specified, the strings are loaded from the currently 557 * active language. 558 * 559 * @param string $event The event to get, e.g. "common", "admin", "public" 560 * @param string $lang The language code 561 * @return array|string Array of string on success, or an empty string when no strings were found 562 * @package L10n 563 * @see load_lang() 564 * @example 565 * print_r( 566 * load_lang_event('common') 567 * ); 568 */ 569 570 function load_lang_event($event, $lang = LANG) 571 { 572 $installed = (false !== safe_field("name", 'txp_lang', "lang = '".doSlash($lang)."' LIMIT 1")); 573 574 $lang_code = ($installed) ? $lang : 'en-gb'; 575 576 $rs = safe_rows_start("name, data", 'txp_lang', "lang = '".doSlash($lang_code)."' AND event = '".doSlash($event)."'"); 577 578 $out = array(); 579 580 if ($rs && !empty($rs)) { 581 while ($a = nextRow($rs)) { 582 $out[$a['name']] = $a['data']; 583 } 584 } 585 586 return ($out) ? $out : ''; 587 } 588 589 /** 590 * Requires privileges from a user. 591 * 592 * @deprecated in 4.3.0 593 * @see require_privs() 594 * @package User 595 */ 596 597 function check_privs() 598 { 599 trigger_error(gTxt('deprecated_function_with', array('{name}' => __FUNCTION__, '{with}' => 'require_privs')), E_USER_NOTICE); 600 global $txp_user; 601 $privs = safe_field("privs", 'txp_users', "name = '".doSlash($txp_user)."'"); 602 $args = func_get_args(); 603 604 if (!in_array($privs, $args)) { 605 exit(pageTop('Restricted').'<p style="margin-top:3em;text-align:center">'. 606 gTxt('restricted_area').'</p>'); 607 } 608 } 609 610 /** 611 * Grants privileges to user-groups. 612 * 613 * Will not let you override existing privs. 614 * 615 * @param string $res The resource 616 * @param string $perm List of user-groups, e.g. '1,2,3' 617 * @package User 618 * @example 619 * add_privs('my_admin_side_panel_event', '1,2,3,4,5'); 620 */ 621 622 function add_privs($res, $perm = '1') 623 { 624 global $txp_permissions; 625 626 if (!isset($txp_permissions[$res])) { 627 $perm = join(',', do_list_unique($perm)); 628 $txp_permissions[$res] = $perm; 629 } 630 } 631 632 /** 633 * Checks if a user has privileges to the given resource. 634 * 635 * @param string $res The resource 636 * @param string $user The user. If no user name is supplied, assume the current logged in user 637 * @return bool 638 * @package User 639 * @example 640 * add_privs('my_privilege_resource', '1,2,3'); 641 * if (has_privs('my_privilege_resource', 'username')) 642 * { 643 * echo "'username' has privileges to 'my_privilege_resource'."; 644 * } 645 */ 646 647 function has_privs($res, $user = '') 648 { 649 global $txp_user, $txp_permissions; 650 static $privs; 651 652 $user = (string) $user; 653 654 if ($user === '') { 655 $user = (string) $txp_user; 656 } 657 658 if ($user !== '') { 659 if (!isset($privs[$user])) { 660 $privs[$user] = safe_field("privs", 'txp_users', "name = '".doSlash($user)."'"); 661 } 662 663 if (isset($txp_permissions[$res]) && $privs[$user] && $txp_permissions[$res]) { 664 return in_list($privs[$user], $txp_permissions[$res]); 665 } 666 } 667 668 return false; 669 } 670 671 /** 672 * Require privileges from a user to the given resource. 673 * 674 * Terminates the script if user doesn't have required privileges. 675 * 676 * @param string|null $res The resource, or NULL 677 * @param string $user The user. If no user name is supplied, assume the current logged in user 678 * @package User 679 * @example 680 * require_privs('article.edit'); 681 */ 682 683 function require_privs($res = null, $user = '') 684 { 685 if ($res === null || !has_privs($res, $user)) { 686 pagetop(gTxt('restricted_area')); 687 echo graf(gTxt('restricted_area'), array('class' => 'restricted-area')); 688 end_page(); 689 exit; 690 } 691 } 692 693 /** 694 * Gets a list of users having access to a resource. 695 * 696 * @param string $res The resource, e.g. 'article.edit.published' 697 * @return array A list of usernames 698 * @since 4.5.0 699 * @package User 700 */ 701 702 function the_privileged($res) 703 { 704 global $txp_permissions; 705 706 if (isset($txp_permissions[$res])) { 707 return safe_column("name", 'txp_users', "FIND_IN_SET(privs, '".$txp_permissions[$res]."') ORDER BY name ASC"); 708 } else { 709 return array(); 710 } 711 } 712 713 /** 714 * Gets a list of user groups. 715 * 716 * @return array 717 * @package User 718 * @example 719 * print_r( 720 * get_groups() 721 * ); 722 */ 723 724 function get_groups() 725 { 726 global $txp_groups; 727 728 return doArray($txp_groups, 'gTxt'); 729 } 730 731 /** 732 * Gets the dimensions of an image for a HTML <img> tag. 733 * 734 * @param string $name The filename 735 * @return string|bool height="100" width="40", or FALSE on failure 736 * @package Image 737 * @example 738 * if ($size = sizeImage('/path/to/image.png')) 739 * { 740 * echo "<img src='image.png' {$size} />"; 741 * } 742 */ 743 744 function sizeImage($name) 745 { 746 $size = @getimagesize($name); 747 748 return is_array($size) ? $size[3] : false; 749 } 750 751 /** 752 * Lists image types that can be safely uploaded. 753 * 754 * Returns different results based on the logged in user's privileges. 755 * 756 * @param int $type If set, validates the given value 757 * @return mixed 758 * @package Image 759 * @since 4.6.0 760 * @example 761 * list($width, $height, $extension) = getimagesize('image'); 762 * if ($type = get_safe_image_types($extension)) 763 * { 764 * echo "Valid image of {$type}."; 765 * } 766 */ 767 768 function get_safe_image_types($type = null) 769 { 770 if (!has_privs('image.create.trusted')) { 771 $extensions = array(0, '.gif', '.jpg', '.png'); 772 } else { 773 $extensions = array(0, '.gif', '.jpg', '.png', '.swf', 0, 0, 0, 0, 0, 0, 0, 0, '.swf'); 774 } 775 776 if (func_num_args() > 0) { 777 return !empty($extensions[$type]) ? $extensions[$type] : false; 778 } 779 780 return $extensions; 781 } 782 783 /** 784 * Checks if GD supports the given image type. 785 * 786 * @param string $image_type Either '.gif', '.png', '.jpg' 787 * @return bool TRUE if the type is supported 788 * @package Image 789 */ 790 791 function check_gd($image_type) 792 { 793 if (!function_exists('gd_info')) { 794 return false; 795 } 796 797 $gd_info = gd_info(); 798 799 switch ($image_type) { 800 case '.gif': 801 return ($gd_info['GIF Create Support'] == true); 802 break; 803 case '.png': 804 return ($gd_info['PNG Support'] == true); 805 break; 806 case '.jpg': 807 return (!empty($gd_info['JPEG Support']) || !empty($gd_info['JPG Support'])); 808 break; 809 } 810 811 return false; 812 } 813 814 /** 815 * Uploads an image. 816 * 817 * Can be used to upload a new image or replace an existing one. 818 * If $id is specified, the image will be replaced. If $uploaded is set FALSE, 819 * $file can take a local file instead of HTTP file upload variable. 820 * 821 * All uploaded files will included on the Images panel. 822 * 823 * @param array $file HTTP file upload variables 824 * @param array $meta Image meta data, allowed keys 'caption', 'alt', 'category' 825 * @param int $id Existing image's ID 826 * @param bool $uploaded If FALSE, $file takes a filename instead of upload vars 827 * @return array|string An array of array(message, id) on success, localized error string on error 828 * @package Image 829 * @example 830 * print_r(image_data( 831 * $_FILES['myfile'], 832 * array( 833 * 'caption' => '', 834 * 'alt' => '', 835 * 'category' => '', 836 * ) 837 * )); 838 */ 839 840 function image_data($file, $meta = array(), $id = 0, $uploaded = true) 841 { 842 global $txp_user, $event; 843 844 $name = $file['name']; 845 $error = $file['error']; 846 $file = $file['tmp_name']; 847 848 if ($uploaded) { 849 if ($error !== UPLOAD_ERR_OK) { 850 return upload_get_errormsg($error); 851 } 852 853 $file = get_uploaded_file($file); 854 855 if (get_pref('file_max_upload_size') < filesize($file)) { 856 unlink($file); 857 858 return upload_get_errormsg(UPLOAD_ERR_FORM_SIZE); 859 } 860 } 861 862 if (empty($file)) { 863 return upload_get_errormsg(UPLOAD_ERR_NO_FILE); 864 } 865 866 list($w, $h, $extension) = getimagesize($file); 867 $ext = get_safe_image_types($extension); 868 869 if (!$ext) { 870 return gTxt('only_graphic_files_allowed'); 871 } 872 873 $name = substr($name, 0, strrpos($name, '.')).$ext; 874 $safename = doSlash($name); 875 $meta = lAtts(array( 876 'category' => '', 877 'caption' => '', 878 'alt' => '', 879 ), (array) $meta, false); 880 881 extract(doSlash($meta)); 882 883 $q = " 884 name = '$safename', 885 ext = '$ext', 886 w = $w, 887 h = $h, 888 alt = '$alt', 889 caption = '$caption', 890 category = '$category', 891 date = NOW(), 892 author = '".doSlash($txp_user)."' 893 "; 894 895 if (empty($id)) { 896 $rs = safe_insert('txp_image', $q); 897 898 if ($rs) { 899 $id = $GLOBALS['ID'] = $rs; 900 } else { 901 return gTxt('image_save_error'); 902 } 903 } else { 904 $id = assert_int($id); 905 } 906 907 $newpath = IMPATH.$id.$ext; 908 909 if (shift_uploaded_file($file, $newpath) == false) { 910 if (!empty($rs)) { 911 safe_delete('txp_image', "id = $id"); 912 unset($GLOBALS['ID']); 913 } 914 915 return $newpath.sp.gTxt('upload_dir_perms'); 916 } elseif (empty($rs)) { 917 $rs = safe_update('txp_image', $q, "id = $id"); 918 919 if (!$rs) { 920 return gTxt('image_save_error'); 921 } 922 } 923 924 @chmod($newpath, 0644); 925 926 // GD is supported 927 if (check_gd($ext)) { 928 // Auto-generate a thumbnail using the last settings 929 if (get_pref('thumb_w') > 0 || get_pref('thumb_h') > 0) { 930 $t = new txp_thumb($id); 931 $t->crop = (bool) get_pref('thumb_crop'); 932 $t->hint = '0'; 933 $t->width = (int) get_pref('thumb_w'); 934 $t->height = (int) get_pref('thumb_h'); 935 $t->write(); 936 } 937 } 938 939 $message = gTxt('image_uploaded', array('{name}' => $name)); 940 update_lastmod('image_uploaded', compact('id', 'name', 'ext', 'w', 'h', 'alt', 'caption', 'category', 'txpuser')); 941 942 // call post-upload plugins with new image's $id 943 callback_event('image_uploaded', $event, false, $id); 944 945 return array($message, $id); 946 } 947 948 /** 949 * Gets an image as an array. 950 * 951 * @param string $where SQL where clause 952 * @return array|bool An image data, or FALSE on failure 953 * @package Image 954 * @example 955 * if ($image = fileDownloadFetchInfo('id = 1')) 956 * { 957 * print_r($image); 958 * } 959 */ 960 961 function imageFetchInfo($where) 962 { 963 $rs = safe_row("*", 'txp_image', $where); 964 965 if ($rs) { 966 return image_format_info($rs); 967 } 968 969 return false; 970 } 971 972 /** 973 * Formats image info. 974 * 975 * Takes an image data array generated by imageFetchInfo() and formats the contents. 976 * 977 * @param array $image The image 978 * @return array 979 * @see imageFetchInfo() 980 * @access private 981 * @package Image 982 */ 983 984 function image_format_info($image) 985 { 986 if (($unix_ts = @strtotime($image['date'])) > 0) { 987 $image['date'] = $unix_ts; 988 } 989 990 return $image; 991 } 992 993 /** 994 * Formats link info. 995 * 996 * @param array $link The link to format 997 * @return array Formatted link data 998 * @access private 999 * @package Link 1000 */ 1001 1002 function link_format_info($link) 1003 { 1004 if (($unix_ts = @strtotime($link['date'])) > 0) { 1005 $link['date'] = $unix_ts; 1006 } 1007 1008 return $link; 1009 } 1010 1011 /** 1012 * Gets a HTTP GET or POST parameter. 1013 * 1014 * Internally handles and normalises MAGIC_QUOTES_GPC, 1015 * strips CRLF from GET parameters and removes NULL bytes. 1016 * 1017 * @param string $thing The parameter to get 1018 * @return string|array The value of $thing, or an empty string 1019 * @package Network 1020 * @example 1021 * if (gps('sky') == 'blue' && gps('roses') == 'red') 1022 * { 1023 * echo 'Roses are red, sky is blue.'; 1024 * } 1025 */ 1026 1027 function gps($thing) 1028 { 1029 $out = ''; 1030 1031 if (isset($_GET[$thing])) { 1032 if (MAGIC_QUOTES_GPC) { 1033 $out = doStrip($_GET[$thing]); 1034 } else { 1035 $out = $_GET[$thing]; 1036 } 1037 1038 $out = doArray($out, 'deCRLF'); 1039 } elseif (isset($_POST[$thing])) { 1040 if (MAGIC_QUOTES_GPC) { 1041 $out = doStrip($_POST[$thing]); 1042 } else { 1043 $out = $_POST[$thing]; 1044 } 1045 } 1046 1047 $out = doArray($out, 'deNull'); 1048 1049 return $out; 1050 } 1051 1052 /** 1053 * Gets an array of HTTP GET or POST parameters. 1054 * 1055 * @param array $array The parameters to extract 1056 * @return array 1057 * @package Network 1058 * @example 1059 * extract(gpsa(array('sky', 'roses')); 1060 * if ($sky == 'blue' && $roses == 'red') 1061 * { 1062 * echo 'Roses are red, sky is blue.'; 1063 * } 1064 */ 1065 1066 function gpsa($array) 1067 { 1068 if (is_array($array)) { 1069 $out = array(); 1070 1071 foreach ($array as $a) { 1072 $out[$a] = gps($a); 1073 } 1074 1075 return $out; 1076 } 1077 1078 return false; 1079 } 1080 1081 /** 1082 * Gets a HTTP POST parameter. 1083 * 1084 * Internally handles and normalises MAGIC_QUOTES_GPC, 1085 * and removes NULL bytes. 1086 * 1087 * @param string $thing The parameter to get 1088 * @return string|array The value of $thing, or an empty string 1089 * @package Network 1090 * @example 1091 * if (ps('sky') == 'blue' && ps('roses') == 'red') 1092 * { 1093 * echo 'Roses are red, sky is blue.'; 1094 * } 1095 */ 1096 1097 function ps($thing) 1098 { 1099 $out = ''; 1100 if (isset($_POST[$thing])) { 1101 if (MAGIC_QUOTES_GPC) { 1102 $out = doStrip($_POST[$thing]); 1103 } else { 1104 $out = $_POST[$thing]; 1105 } 1106 } 1107 1108 $out = doArray($out, 'deNull'); 1109 1110 return $out; 1111 } 1112 1113 /** 1114 * Gets an array of HTTP POST parameters. 1115 * 1116 * @param array $array The parameters to extract 1117 * @return array 1118 * @package Network 1119 * @example 1120 * extract(psa(array('sky', 'roses')); 1121 * if ($sky == 'blue' && $roses == 'red') 1122 * { 1123 * echo 'Roses are red, sky is blue.'; 1124 * } 1125 */ 1126 1127 function psa($array) 1128 { 1129 foreach ($array as $a) { 1130 $out[$a] = ps($a); 1131 } 1132 1133 return $out; 1134 } 1135 1136 /** 1137 * Gets an array of HTTP POST parameters and strips HTML and PHP tags 1138 * from values. 1139 * 1140 * @param array $array The parameters to extract 1141 * @return array 1142 * @package Network 1143 */ 1144 1145 function psas($array) 1146 { 1147 foreach ($array as $a) { 1148 $out[$a] = doStripTags(ps($a)); 1149 } 1150 1151 return $out; 1152 } 1153 1154 /** 1155 * Gets all received HTTP POST parameters. 1156 * 1157 * @return array 1158 * @package Network 1159 */ 1160 1161 function stripPost() 1162 { 1163 if (isset($_POST)) { 1164 if (MAGIC_QUOTES_GPC) { 1165 return doStrip($_POST); 1166 } else { 1167 return $_POST; 1168 } 1169 } 1170 1171 return ''; 1172 } 1173 1174 /** 1175 * Gets a variable from $_SERVER global array. 1176 * 1177 * @param mixed $thing The variable 1178 * @return mixed The variable, or an empty string on error 1179 * @package System 1180 * @example 1181 * echo serverSet('HTTP_USER_AGENT'); 1182 */ 1183 1184 function serverSet($thing) 1185 { 1186 return (isset($_SERVER[$thing])) ? $_SERVER[$thing] : ''; 1187 } 1188 1189 /** 1190 * Gets the client's IP address. 1191 * 1192 * Supports proxies and uses 'X_FORWARDED_FOR' HTTP header if deemed necessary. 1193 * 1194 * @return string 1195 * @package Network 1196 * @example 1197 * if ($ip = remote_addr()) 1198 * { 1199 * echo "Your IP address is: {$ip}."; 1200 * } 1201 */ 1202 1203 function remote_addr() 1204 { 1205 $ip = serverSet('REMOTE_ADDR'); 1206 1207 if (($ip == '127.0.0.1' || $ip == '::1' || $ip == '::ffff:127.0.0.1' || $ip == serverSet('SERVER_ADDR')) && serverSet('HTTP_X_FORWARDED_FOR')) { 1208 $ips = explode(', ', serverSet('HTTP_X_FORWARDED_FOR')); 1209 $ip = $ips[0]; 1210 } 1211 1212 return $ip; 1213 } 1214 1215 /** 1216 * Gets a variable from HTTP POST or a prefixed cookie. 1217 * 1218 * Fetches either a HTTP cookie of the given name prefixed with 1219 * 'txp_', or a HTTP POST parameter without a prefix. 1220 * 1221 * @param string $thing The variable 1222 * @return array|string The variable or an empty string 1223 * @package Network 1224 * @example 1225 * if ($cs = psc('myVariable')) 1226 * { 1227 * echo "'txp_myVariable' cookie or 'myVariable' POST parameter contained: '{$cs}'."; 1228 * } 1229 */ 1230 1231 function pcs($thing) 1232 { 1233 if (isset($_COOKIE["txp_".$thing])) { 1234 if (MAGIC_QUOTES_GPC) { 1235 return doStrip($_COOKIE["txp_".$thing]); 1236 } else { 1237 return $_COOKIE["txp_".$thing]; 1238 } 1239 } elseif (isset($_POST[$thing])) { 1240 if (MAGIC_QUOTES_GPC) { 1241 return doStrip($_POST[$thing]); 1242 } else { 1243 return $_POST[$thing]; 1244 } 1245 } 1246 1247 return ''; 1248 } 1249 1250 /** 1251 * Gets a HTTP cookie. 1252 * 1253 * Internally normalises MAGIC_QUOTES_GPC. 1254 * 1255 * @param string $thing The cookie 1256 * @return string The cookie or an empty string 1257 * @package Network 1258 * @example 1259 * if ($cs = cs('myVariable')) 1260 * { 1261 * echo "'myVariable' cookie contained: '{$cs}'."; 1262 * } 1263 */ 1264 1265 function cs($thing) 1266 { 1267 if (isset($_COOKIE[$thing])) { 1268 if (MAGIC_QUOTES_GPC) { 1269 return doStrip($_COOKIE[$thing]); 1270 } else { 1271 return $_COOKIE[$thing]; 1272 } 1273 } 1274 1275 return ''; 1276 } 1277 1278 /** 1279 * Converts a boolean to a localised "Yes" or "No" string. 1280 * 1281 * @param bool $status The boolean. Ignores type and as such can also take a string or an integer 1282 * @return string No if FALSE, Yes otherwise 1283 * @package L10n 1284 * @example 1285 * echo yes_no(3 * 3 === 2); 1286 */ 1287 1288 function yes_no($status) 1289 { 1290 return ($status) ? gTxt('yes') : gTxt('no'); 1291 } 1292 1293 /** 1294 * Gets UNIX timestamp with microseconds. 1295 * 1296 * @return float 1297 * @package DateTime 1298 * @example 1299 * echo getmicrotime(); 1300 */ 1301 1302 function getmicrotime() 1303 { 1304 list($usec, $sec) = explode(" ", microtime()); 1305 1306 return ((float) $usec + (float) $sec); 1307 } 1308 1309 /** 1310 * Loads the given plugin or checks if it was loaded. 1311 * 1312 * @param string $name The plugin 1313 * @param bool $force If TRUE loads the plugin even if it's disabled 1314 * @return bool TRUE if the plugin is loaded 1315 * @example 1316 * if (load_plugin('abc_plugin')) 1317 * { 1318 * echo "'abc_plugin' is active."; 1319 * } 1320 */ 1321 1322 function load_plugin($name, $force = false) 1323 { 1324 global $plugins, $plugins_ver, $prefs, $txp_current_plugin; 1325 1326 if (is_array($plugins) and in_array($name, $plugins)) { 1327 return true; 1328 } 1329 1330 if (!empty($prefs['plugin_cache_dir'])) { 1331 $dir = rtrim($prefs['plugin_cache_dir'], '/').'/'; 1332 1333 // In case it's a relative path. 1334 if (!is_dir($dir)) { 1335 $dir = rtrim(realpath(txpath.'/'.$dir), '/').'/'; 1336 } 1337 1338 if (is_file($dir.$name.'.php')) { 1339 $plugins[] = $name; 1340 set_error_handler("pluginErrorHandler"); 1341 1342 if (isset($txp_current_plugin)) { 1343 $txp_parent_plugin = $txp_current_plugin; 1344 } 1345 1346 $txp_current_plugin = $name; 1347 include $dir.$name.'.php'; 1348 $txp_current_plugin = isset($txp_parent_plugin) ? $txp_parent_plugin : null; 1349 $plugins_ver[$name] = @$plugin['version']; 1350 restore_error_handler(); 1351 1352 return true; 1353 } 1354 } 1355 1356 $rs = safe_row("name, code, version", 'txp_plugin', ($force ? '' : "status = 1 AND ")."name = '".doSlash($name)."'"); 1357 1358 if ($rs) { 1359 $plugins[] = $rs['name']; 1360 $plugins_ver[$rs['name']] = $rs['version']; 1361 set_error_handler("pluginErrorHandler"); 1362 1363 if (isset($txp_current_plugin)) { 1364 $txp_parent_plugin = $txp_current_plugin; 1365 } 1366 1367 $txp_current_plugin = $rs['name']; 1368 eval($rs['code']); 1369 $txp_current_plugin = isset($txp_parent_plugin) ? $txp_parent_plugin : null; 1370 restore_error_handler(); 1371 1372 return true; 1373 } 1374 1375 return false; 1376 } 1377 1378 /** 1379 * Loads a plugin. 1380 * 1381 * Identical to load_plugin() except upon failure it issues an E_USER_ERROR. 1382 * 1383 * @param string $name The plugin 1384 * @return bool 1385 * @see load_plugin() 1386 */ 1387 1388 function require_plugin($name) 1389 { 1390 if (!load_plugin($name)) { 1391 trigger_error("Unable to include required plugin \"{$name}\"", E_USER_ERROR); 1392 1393 return false; 1394 } 1395 1396 return true; 1397 } 1398 1399 /** 1400 * Loads a plugin. 1401 * 1402 * Identical to load_plugin() except upon failure it issues an E_USER_WARNING. 1403 * 1404 * @param string $name The plugin 1405 * @return bool 1406 * @see load_plugin() 1407 */ 1408 1409 function include_plugin($name) 1410 { 1411 if (!load_plugin($name)) { 1412 trigger_error("Unable to include plugin \"{$name}\"", E_USER_WARNING); 1413 1414 return false; 1415 } 1416 1417 return true; 1418 } 1419 1420 /** 1421 * Error handler for plugins. 1422 * 1423 * @param int $errno 1424 * @param string $errstr 1425 * @param string $errfile 1426 * @param int $errline 1427 * @access private 1428 * @package Debug 1429 */ 1430 1431 function pluginErrorHandler($errno, $errstr, $errfile, $errline) 1432 { 1433 global $production_status, $txp_current_plugin; 1434 1435 $error = array(); 1436 1437 if ($production_status == 'testing') { 1438 $error = array( 1439 E_WARNING => 'Warning', 1440 E_RECOVERABLE_ERROR => 'Catchable fatal error', 1441 E_USER_ERROR => 'User_Error', 1442 E_USER_WARNING => 'User_Warning', 1443 ); 1444 } elseif ($production_status == 'debug') { 1445 $error = array( 1446 E_WARNING => 'Warning', 1447 E_NOTICE => 'Notice', 1448 E_RECOVERABLE_ERROR => 'Catchable fatal error', 1449 E_USER_ERROR => 'User_Error', 1450 E_USER_WARNING => 'User_Warning', 1451 E_USER_NOTICE => 'User_Notice', 1452 ); 1453 1454 if (!isset($error[$errno])) { 1455 $error[$errno] = $errno; 1456 } 1457 } 1458 1459 if (!isset($error[$errno]) || !error_reporting()) { 1460 return; 1461 } 1462 1463 printf('<pre dir="auto">'.gTxt('plugin_load_error').' <b>%s</b> -> <b>%s: %s on line %s</b></pre>', 1464 $txp_current_plugin, $error[$errno], $errstr, $errline); 1465 1466 if ($production_status == 'debug') { 1467 print "\n<pre class=\"backtrace\" dir=\"ltr\"><code>".txpspecialchars(join("\n", get_caller(10)))."</code></pre>"; 1468 } 1469 } 1470 1471 /** 1472 * Error handler for page templates. 1473 * 1474 * @param int $errno 1475 * @param string $errstr 1476 * @param string $errfile 1477 * @param int $errline 1478 * @access private 1479 * @package Debug 1480 */ 1481 1482 function tagErrorHandler($errno, $errstr, $errfile, $errline) 1483 { 1484 global $production_status, $txp_current_tag, $txp_current_form, $pretext, $trace; 1485 1486 $error = array(); 1487 1488 if ($production_status == 'testing') { 1489 $error = array( 1490 E_WARNING => 'Warning', 1491 E_RECOVERABLE_ERROR => 'Textpattern Catchable fatal error', 1492 E_USER_ERROR => 'Textpattern Error', 1493 E_USER_WARNING => 'Textpattern Warning', 1494 ); 1495 } elseif ($production_status == 'debug') { 1496 $error = array( 1497 E_WARNING => 'Warning', 1498 E_NOTICE => 'Notice', 1499 E_RECOVERABLE_ERROR => 'Textpattern Catchable fatal error', 1500 E_USER_ERROR => 'Textpattern Error', 1501 E_USER_WARNING => 'Textpattern Warning', 1502 E_USER_NOTICE => 'Textpattern Notice', 1503 ); 1504 1505 if (!isset($error[$errno])) { 1506 $error[$errno] = $errno; 1507 } 1508 } 1509 1510 if (!isset($error[$errno]) || !error_reporting()) { 1511 return; 1512 } 1513 1514 if (empty($pretext['page'])) { 1515 $page = gTxt('none'); 1516 } else { 1517 $page = $pretext['page']; 1518 } 1519 1520 if (!isset($txp_current_form)) { 1521 $txp_current_form = gTxt('none'); 1522 } 1523 1524 $locus = gTxt('while_parsing_page_form', array( 1525 '{page}' => $page, 1526 '{form}' => $txp_current_form, 1527 )); 1528 1529 printf("<pre dir=\"auto\">".gTxt('tag_error').' <b>%s</b> -> <b> %s: %s %s</b></pre>', 1530 txpspecialchars($txp_current_tag), $error[$errno], $errstr, $locus); 1531 1532 if ($production_status == 'debug') { 1533 print "\n<pre class=\"backtrace\" dir=\"ltr\"><code>".txpspecialchars(join("\n", get_caller(10)))."</code></pre>"; 1534 1535 $trace->log(gTxt('tag_error').' '.$txp_current_tag.' -> '.$error[$errno].': '.$errstr.' '.$locus); 1536 } 1537 } 1538 1539 /** 1540 * Error handler for XML feeds. 1541 * 1542 * @param int $errno 1543 * @param string $errstr 1544 * @param string $errfile 1545 * @param int $errline 1546 * @access private 1547 * @package Debug 1548 */ 1549 1550 function feedErrorHandler($errno, $errstr, $errfile, $errline) 1551 { 1552 global $production_status; 1553 1554 if ($production_status != 'debug') { 1555 return; 1556 } 1557 1558 return tagErrorHandler($errno, $errstr, $errfile, $errline); 1559 } 1560 1561 /** 1562 * Error handler for admin-side pages. 1563 * 1564 * @param int $errno 1565 * @param string $errstr 1566 * @param string $errfile 1567 * @param int $errline 1568 * @access private 1569 * @package Debug 1570 */ 1571 1572 function adminErrorHandler($errno, $errstr, $errfile, $errline) 1573 { 1574 global $production_status, $theme, $event, $step; 1575 1576 $error = array(); 1577 1578 if ($production_status == 'testing') { 1579 $error = array( 1580 E_WARNING => 'Warning', 1581 E_RECOVERABLE_ERROR => 'Catchable fatal error', 1582 E_USER_ERROR => 'User_Error', 1583 E_USER_WARNING => 'User_Warning', 1584 ); 1585 } elseif ($production_status == 'debug') { 1586 $error = array( 1587 E_WARNING => 'Warning', 1588 E_NOTICE => 'Notice', 1589 E_RECOVERABLE_ERROR => 'Catchable fatal error', 1590 E_USER_ERROR => 'User_Error', 1591 E_USER_WARNING => 'User_Warning', 1592 E_USER_NOTICE => 'User_Notice', 1593 ); 1594 1595 if (!isset($error[$errno])) { 1596 $error[$errno] = $errno; 1597 } 1598 } 1599 1600 if (!isset($error[$errno]) || !error_reporting()) { 1601 return; 1602 } 1603 1604 // When even a minimum environment is missing. 1605 if (!isset($production_status)) { 1606 echo '<pre dir="auto">'.gTxt('internal_error').' "'.$errstr.'"'.n."in $errfile at line $errline".'</pre>'; 1607 1608 return; 1609 } 1610 1611 $backtrace = ''; 1612 1613 if (has_privs('debug.verbose')) { 1614 $msg = $error[$errno].' "'.$errstr.'"'; 1615 } else { 1616 $msg = gTxt('internal_error'); 1617 } 1618 1619 if ($production_status == 'debug' && has_privs('debug.backtrace')) { 1620 $msg .= n."in $errfile at line $errline"; 1621 $backtrace = join(n, get_caller(10, 1)); 1622 } 1623 1624 if ($errno == E_ERROR || $errno == E_USER_ERROR) { 1625 $httpstatus = 500; 1626 } else { 1627 $httpstatus = 200; 1628 } 1629 1630 $out = "$msg.\n$backtrace"; 1631 1632 if (http_accept_format('html')) { 1633 if ($backtrace) { 1634 echo "<pre dir=\"auto\">$msg.</pre>". 1635 n.'<pre class="backtrace" dir="ltr"><code>'. 1636 txpspecialchars($backtrace).'</code></pre>'; 1637 } elseif (is_object($theme)) { 1638 echo $theme->announce(array($out, E_ERROR), true); 1639 } else { 1640 echo "<pre dir=\"auto\">$out</pre>"; 1641 } 1642 } elseif (http_accept_format('js')) { 1643 if (is_object($theme)) { 1644 send_script_response($theme->announce_async(array($out, E_ERROR), true)); 1645 } else { 1646 send_script_response('/* '.$out.'*/'); 1647 } 1648 } elseif (http_accept_format('xml')) { 1649 send_xml_response(array( 1650 'http-status' => $httpstatus, 1651 'internal_error' => "$out", 1652 )); 1653 } else { 1654 txp_die($msg, 500); 1655 } 1656 } 1657 1658 /** 1659 * Error handler for update scripts. 1660 * 1661 * @param int $errno 1662 * @param string $errstr 1663 * @param string $errfile 1664 * @param int $errline 1665 * @access private 1666 * @package Debug 1667 */ 1668 1669 function updateErrorHandler($errno, $errstr, $errfile, $errline) 1670 { 1671 global $production_status; 1672 1673 $old = $production_status; 1674 $production_status = 'debug'; 1675 1676 adminErrorHandler($errno, $errstr, $errfile, $errline); 1677 1678 $production_status = $old; 1679 1680 throw new Exception('update failed'); 1681 } 1682 1683 /** 1684 * Error handler for public-side. 1685 * 1686 * @param int $errno 1687 * @param string $errstr 1688 * @param string $errfile 1689 * @param int $errline 1690 * @access private 1691 * @package Debug 1692 */ 1693 1694 function publicErrorHandler($errno, $errstr, $errfile, $errline) 1695 { 1696 global $production_status; 1697 1698 $error = array(); 1699 1700 if ($production_status == 'testing') { 1701 $error = array( 1702 E_WARNING => 'Warning', 1703 E_USER_ERROR => 'Textpattern Error', 1704 E_USER_WARNING => 'Textpattern Warning', 1705 ); 1706 } elseif ($production_status == 'debug') { 1707 $error = array( 1708 E_WARNING => 'Warning', 1709 E_NOTICE => 'Notice', 1710 E_USER_ERROR => 'Textpattern Error', 1711 E_USER_WARNING => 'Textpattern Warning', 1712 E_USER_NOTICE => 'Textpattern Notice', 1713 ); 1714 1715 if (!isset($error[$errno])) { 1716 $error[$errno] = $errno; 1717 } 1718 } 1719 1720 if (!isset($error[$errno]) || !error_reporting()) { 1721 return; 1722 } 1723 1724 printf("<pre dir=\"auto\">".gTxt('general_error').' <b>%s: %s on line %s</b></pre>', 1725 $error[$errno], $errstr, $errline); 1726 1727 if ($production_status == 'debug') { 1728 print "\n<pre class=\"backtrace\" dir=\"ltr\"><code>".txpspecialchars(join("\n", get_caller(10)))."</code></pre>"; 1729 } 1730 } 1731 1732 /** 1733 * Loads plugins. 1734 * 1735 * @param bool $type If TRUE loads admin-side plugins, otherwise public 1736 */ 1737 1738 function load_plugins($type = false) 1739 { 1740 global $prefs, $plugins, $plugins_ver, $app_mode, $trace; 1741 1742 if (!is_array($plugins)) { 1743 $plugins = array(); 1744 } 1745 $trace->start('[Loading plugins]'); 1746 1747 if (!empty($prefs['plugin_cache_dir'])) { 1748 $dir = rtrim($prefs['plugin_cache_dir'], '/').'/'; 1749 1750 // In case it's a relative path. 1751 if (!is_dir($dir)) { 1752 $dir = rtrim(realpath(txpath.'/'.$dir), '/').'/'; 1753 } 1754 1755 $files = glob($dir.'*.php'); 1756 1757 if ($files) { 1758 natsort($files); 1759 1760 foreach ($files as $f) { 1761 $trace->start("[Loading plugin from cache dir: '$f']"); 1762 load_plugin(basename($f, '.php')); 1763 $trace->stop(); 1764 } 1765 } 1766 } 1767 1768 $admin = ($app_mode == 'async' ? '4,5' : '1,3,4,5'); 1769 $where = "status = 1 AND type IN (".($type ? $admin : "0,1,5").")"; 1770 1771 $rs = safe_rows("name, code, version", 'txp_plugin', $where." ORDER BY load_order ASC, name ASC"); 1772 1773 if ($rs) { 1774 $old_error_handler = set_error_handler("pluginErrorHandler"); 1775 1776 foreach ($rs as $a) { 1777 if (!in_array($a['name'], $plugins)) { 1778 $plugins[] = $a['name']; 1779 $plugins_ver[$a['name']] = $a['version']; 1780 $GLOBALS['txp_current_plugin'] = $a['name']; 1781 $trace->start("[Loading plugin: '{$a['name']}' version '{$a['version']}']"); 1782 $eval_ok = eval($a['code']); 1783 $trace->stop(); 1784 1785 if ($eval_ok === false) { 1786 echo gTxt('plugin_load_error_above').strong($a['name']).n.br; 1787 } 1788 1789 unset($GLOBALS['txp_current_plugin']); 1790 } 1791 } 1792 restore_error_handler(); 1793 } 1794 $trace->stop(); 1795 } 1796 1797 /** 1798 * Attachs a handler to a callback event. 1799 * 1800 * @param callback $func The callback function 1801 * @param string $event The callback event 1802 * @param string $step The callback step 1803 * @param bool $pre Before or after. Works only with selected callback events 1804 * @package Callback 1805 * @example 1806 * register_callback('my_callback_function', 'article.updated'); 1807 * function my_callback_function($event) 1808 * { 1809 * return "'$event' fired."; 1810 * } 1811 */ 1812 1813 function register_callback($func, $event, $step = '', $pre = 0) 1814 { 1815 global $plugin_callback; 1816 $plugin_callback[] = array( 1817 'function' => $func, 1818 'event' => $event, 1819 'step' => $step, 1820 'pre' => $pre, 1821 ); 1822 } 1823 1824 /** 1825 * Registers an admin-side extension page. 1826 * 1827 * For now this just does the same as register_callback(). 1828 * 1829 * @param callback $func The callback function 1830 * @param string $event The callback event 1831 * @param string $step The callback step 1832 * @param bool $top The top or the bottom of the page 1833 * @access private 1834 * @see register_callback() 1835 * @package Callback 1836 */ 1837 1838 function register_page_extension($func, $event, $step = '', $top = 0) 1839 { 1840 register_callback($func, $event, $step, $top); 1841 } 1842 1843 /** 1844 * Call an event's callback. 1845 * 1846 * Executes all callback handlers attached to the matched event and step. 1847 * 1848 * When called, any event handlers attached with register_callback() to the 1849 * matching event, step and pre will be called. The handlers, callback 1850 * functions, will be executed in the same order they were registered. 1851 * 1852 * Any extra arguments will be passed to the callback handlers in the same 1853 * argument position. This allows passing any type of data to the attached 1854 * handlers. Callback handlers will also receive the event and the step. 1855 * 1856 * Returns a combined value of all values returned by the callback handlers. 1857 * 1858 * @param string $event The callback event 1859 * @param string $step Additional callback step 1860 * @param bool|int|array $pre Allows two callbacks, a prepending and an appending, with same event and step. Array allows return values chaining 1861 * @return mixed The value returned by the attached callback functions, or an empty string 1862 * @package Callback 1863 * @see register_callback() 1864 * @example 1865 * register_callback('my_callback_function', 'my_custom_event'); 1866 * function my_callback_function($event, $step, $extra) 1867 * { 1868 * return "Passed '$extra' on '$event'."; 1869 * } 1870 * echo callback_event('my_custom_event', '', 0, 'myExtraValue'); 1871 */ 1872 1873 function callback_event($event, $step = '', $pre = 0) 1874 { 1875 global $plugin_callback, $production_status, $trace; 1876 1877 if (!is_array($plugin_callback)) { 1878 return ''; 1879 } 1880 1881 list($pre, $renew) = (array)$pre + array(0, null); 1882 $trace->start("[Callback_event: '$event', step='$step', pre='$pre']"); 1883 1884 // Any payload parameters? 1885 $argv = func_get_args(); 1886 $argv = (count($argv) > 3) ? array_slice($argv, 3) : array(); 1887 1888 foreach ($plugin_callback as $c) { 1889 if ($c['event'] == $event && (empty($c['step']) || $c['step'] == $step) && $c['pre'] == $pre) { 1890 if (is_callable($c['function'])) { 1891 if ($production_status !== 'live') { 1892 $trace->start("\t[Call function: '".callback_tostring($c['function'])."'".(empty($argv) ? '' : ", argv='".serialize($argv)."'")."]"); 1893 } 1894 1895 $return_value = call_user_func_array($c['function'], array('event' => $event, 'step' => $step) + $argv); 1896 if (isset($renew)) { 1897 $argv[$renew] = $return_value; 1898 } 1899 1900 if (isset($out) && !isset($renew)) { 1901 if (is_array($return_value) && is_array($out)) { 1902 $out = array_merge($out, $return_value); 1903 } elseif (is_bool($return_value) && is_bool($out)) { 1904 $out = $return_value && $out; 1905 } else { 1906 $out .= $return_value; 1907 } 1908 } else { 1909 $out = $return_value; 1910 } 1911 1912 if ($production_status !== 'live') { 1913 $trace->stop(); 1914 } 1915 } elseif ($production_status === 'debug') { 1916 trigger_error(gTxt('unknown_callback_function', array('{function}' => callback_tostring($c['function']))), E_USER_WARNING); 1917 } 1918 } 1919 } 1920 1921 $trace->stop(); 1922 1923 if (isset($out)) { 1924 return $out; 1925 } 1926 1927 return ''; 1928 } 1929 1930 /** 1931 * Call an event's callback with two optional byref parameters. 1932 * 1933 * @param string $event The callback event 1934 * @param string $step Optional callback step 1935 * @param bool $pre Allows two callbacks, a prepending and an appending, with same event and step 1936 * @param mixed $data Optional arguments for event handlers 1937 * @param mixed $options Optional arguments for event handlers 1938 * @return array Collection of return values from event handlers 1939 * @since 4.5.0 1940 * @package Callback 1941 */ 1942 1943 function callback_event_ref($event, $step = '', $pre = 0, &$data = null, &$options = null) 1944 { 1945 global $plugin_callback, $production_status; 1946 1947 if (!is_array($plugin_callback)) { 1948 return array(); 1949 } 1950 1951 $return_value = array(); 1952 1953 foreach ($plugin_callback as $c) { 1954 if ($c['event'] == $event and (empty($c['step']) or $c['step'] == $step) and $c['pre'] == $pre) { 1955 if (is_callable($c['function'])) { 1956 // Cannot call event handler via call_user_func() as this would 1957 // dereference all arguments. Side effect: callback handler 1958 // *must* be ordinary function, *must not* be class method in 1959 // PHP <5.4. See https://bugs.php.net/bug.php?id=47160. 1960 $return_value[] = $c['function']($event, $step, $data, $options); 1961 } elseif ($production_status == 'debug') { 1962 trigger_error(gTxt('unknown_callback_function', array('{function}' => callback_tostring($c['function']))), E_USER_WARNING); 1963 } 1964 } 1965 } 1966 1967 return $return_value; 1968 } 1969 1970 /** 1971 * Converts a callable to a string presentation. 1972 * 1973 * <code> 1974 * echo callback_tostring(array('class', 'method')); 1975 * </code> 1976 * 1977 * @param callback $callback The callback 1978 * @return string The $callback as a human-readable string 1979 * @since 4.5.0 1980 * @package Callback 1981 * @deprecated in 4.6.0 1982 * @see \Textpattern\Type\Callable::toString() 1983 */ 1984 1985 function callback_tostring($callback) 1986 { 1987 return Txp::get('\Textpattern\Type\TypeCallable', $callback)->toString(); 1988 } 1989 1990 /** 1991 * Checks if a callback event has active handlers. 1992 * 1993 * @param string $event The callback event 1994 * @param string $step The callback step 1995 * @param bool $pre The position 1996 * @return bool TRUE if the event is active, FALSE otherwise 1997 * @since 4.6.0 1998 * @package Callback 1999 * @example 2000 * if (has_handler('article_saved')) 2001 * { 2002 * echo "There are active handlers for 'article_saved' event."; 2003 * } 2004 */ 2005 2006 function has_handler($event, $step = '', $pre = 0) 2007 { 2008 return (bool) callback_handlers($event, $step, $pre, false); 2009 } 2010 2011 /** 2012 * Lists handlers attached to an event. 2013 * 2014 * @param string $event The callback event 2015 * @param string $step The callback step 2016 * @param bool $pre The position 2017 * @param bool $as_string Return callables in string representation 2018 * @return array|bool An array of handlers, or FALSE 2019 * @since 4.6.0 2020 * @package Callback 2021 * @example 2022 * if ($handlers = callback_handlers('article_saved')) 2023 * { 2024 * print_r($handlers); 2025 * } 2026 */ 2027 2028 function callback_handlers($event, $step = '', $pre = 0, $as_string = true) 2029 { 2030 global $plugin_callback; 2031 2032 $out = array(); 2033 2034 foreach ((array) $plugin_callback as $c) { 2035 if ($c['event'] == $event && (!$c['step'] || $c['step'] == $step) && $c['pre'] == $pre) { 2036 if ($as_string) { 2037 $out[] = callback_tostring($c['function']); 2038 } else { 2039 $out[] = $c['function']; 2040 } 2041 } 2042 } 2043 2044 if ($out) { 2045 return $out; 2046 } 2047 2048 return false; 2049 } 2050 2051 /** 2052 * Registers a new admin-side panel and adds a navigation link to the menu. 2053 * 2054 * @param string $area The menu the panel appears in, e.g. "home", "content", "presentation", "admin", "extensions" 2055 * @param string $panel The panel's event 2056 * @param string $title The menu item's label 2057 * @package Callback 2058 * @example 2059 * add_privs('abc_admin_event', '1,2'); 2060 * register_tab('extensions', 'abc_admin_event', 'My Panel'); 2061 * register_callback('abc_admin_function', 'abc_admin_event'); 2062 */ 2063 2064 function register_tab($area, $panel, $title) 2065 { 2066 global $plugin_areas, $event; 2067 2068 if ($event !== 'plugin') { 2069 $plugin_areas[$area][$title] = $panel; 2070 } 2071 } 2072 2073 /** 2074 * Call an event's pluggable UI function. 2075 * 2076 * @param string $event The event 2077 * @param string $element The element selector 2078 * @param string $default The default interface markup 2079 * @return mixed Returned value from a callback handler, or $default if no custom UI was provided 2080 * @package Callback 2081 */ 2082 2083 function pluggable_ui($event, $element, $default = '') 2084 { 2085 $argv = func_get_args(); 2086 $argv = array_slice($argv, 2); 2087 // Custom user interface, anyone? 2088 // Signature for called functions: 2089 // string my_called_func(string $event, string $step, string $default_markup[, mixed $context_data...]) 2090 $ui = call_user_func_array('callback_event', array('event' => $event, 'step' => $element, 'pre' => array(0, 0)) + $argv); 2091 2092 // Either plugins provided a user interface, or we render our own. 2093 return ($ui === '') ? $default : $ui; 2094 } 2095 2096 /** 2097 * Gets an attribute from the $theatts global. 2098 * 2099 * @param string $name 2100 * @param string $default 2101 * @return string 2102 * @deprecated in 4.2.0 2103 * @see lAtts() 2104 * @package TagParser 2105 */ 2106 2107 function getAtt($name, $default = null) 2108 { 2109 trigger_error(gTxt('deprecated_function_with', array('{name}' => __FUNCTION__, '{with}' => 'lAtts')), E_USER_NOTICE); 2110 global $theseatts; 2111 2112 return isset($theseatts[$name]) ? $theseatts[$name] : $default; 2113 } 2114 2115 /** 2116 * Gets an attribute from the given array. 2117 * 2118 * @param array $atts 2119 * @param string $name 2120 * @param string $default 2121 * @return string 2122 * @deprecated in 4.2.0 2123 * @see lAtts() 2124 * @package TagParser 2125 */ 2126 2127 function gAtt(&$atts, $name, $default = null) 2128 { 2129 trigger_error(gTxt('deprecated_function_with', array('{name}' => __FUNCTION__, '{with}' => 'lAtts')), E_USER_NOTICE); 2130 2131 return isset($atts[$name]) ? $atts[$name] : $default; 2132 } 2133 2134 /** 2135 * Merge the second array into the first array. 2136 * 2137 * @param array $pairs The first array 2138 * @param array $atts The second array 2139 * @param bool $warn If TRUE triggers errors if second array contains values that are not in the first 2140 * @return array The two arrays merged 2141 * @package TagParser 2142 */ 2143 2144 function lAtts($pairs, $atts, $warn = true) 2145 { 2146 global $production_status; 2147 2148 foreach ($atts as $name => $value) { 2149 if (array_key_exists($name, $pairs)) { 2150 $pairs[$name] = $value; 2151 } elseif ($warn and $production_status != 'live') { 2152 trigger_error(gTxt('unknown_attribute', array('{att}' => $name))); 2153 } 2154 } 2155 2156 return ($pairs) ? $pairs : false; 2157 } 2158 2159 /** 2160 * Generates All, None and Range selection buttons. 2161 * 2162 * @return string HTML 2163 * @deprecated in 4.5.0 2164 * @see multi_edit() 2165 * @package Form 2166 */ 2167 2168 function select_buttons() 2169 { 2170 return 2171 gTxt('select'). 2172 n.fInput('button', 'selall', gTxt('all'), '', 'select all', 'selectall();'). 2173 n.fInput('button', 'selnone', gTxt('none'), '', 'select none', 'deselectall();'). 2174 n.fInput('button', 'selrange', gTxt('range'), '', 'select range', 'selectrange();'); 2175 } 2176 2177 /** 2178 * Sanitises a string for use in an article's URL title. 2179 * 2180 * @param string $text The title or an URL 2181 * @param bool $force Force sanitisation 2182 * @return string|null 2183 * @package URL 2184 */ 2185 2186 function stripSpace($text, $force = false) 2187 { 2188 if ($force || get_pref('attach_titles_to_permalinks')) { 2189 $text = trim(sanitizeForUrl($text), '-'); 2190 2191 if (get_pref('permlink_format')) { 2192 return (function_exists('mb_strtolower') ? mb_strtolower($text, 'UTF-8') : strtolower($text)); 2193 } else { 2194 return str_replace('-', '', $text); 2195 } 2196 } 2197 } 2198 2199 /** 2200 * Sanitises a string for use in a URL. 2201 * 2202 * Be aware that you still have to urlencode the string when appropriate. 2203 * This function just makes the string look prettier and excludes some 2204 * unwanted characters, but leaves UTF-8 letters and digits intact. 2205 * 2206 * @param string $text The string 2207 * @return string 2208 * @package URL 2209 */ 2210 2211 function sanitizeForUrl($text) 2212 { 2213 $out = callback_event('sanitize_for_url', '', 0, $text); 2214 2215 if ($out !== '') { 2216 return $out; 2217 } 2218 2219 $in = $text; 2220 // Remove names entities and tags. 2221 $text = preg_replace("/(^|&\S+;)|(<[^>]*>)/U", "", dumbDown($text)); 2222 // Remove all characters except letter, number, dash, space and backslash 2223 $text = preg_replace('/[^\p{L}\p{N}\-_\s\/\\\\]/u', '', $text); 2224 // Collapse spaces, minuses, (back-)slashes. 2225 $text = trim(preg_replace('/[\s\-\/\\\\]+/', '-', $text), '-'); 2226 2227 return $text; 2228 } 2229 2230 /** 2231 * Sanitises a string for use in a filename. 2232 * 2233 * @param string $text The string 2234 * @return string 2235 * @package File 2236 */ 2237 2238 function sanitizeForFile($text) 2239 { 2240 $out = callback_event('sanitize_for_file', '', 0, $text); 2241 2242 if ($out !== '') { 2243 return $out; 2244 } 2245 2246 // Remove control characters and " * \ : < > ? / | 2247 $text = preg_replace('/[\x00-\x1f\x22\x2a\x2f\x3a\x3c\x3e\x3f\x5c\x7c\x7f]+/', '', $text); 2248 // Remove duplicate dots and any leading or trailing dots/spaces. 2249 $text = preg_replace('/[.]{2,}/', '.', trim($text, '. ')); 2250 2251 return $text; 2252 } 2253 2254 /** 2255 * Sanitises a string for use in a page template's name. 2256 * 2257 * @param string $text The string 2258 * @return string 2259 * @package Filter 2260 * @access private 2261 */ 2262 2263 function sanitizeForPage($text) 2264 { 2265 $out = callback_event('sanitize_for_page', '', 0, $text); 2266 2267 if ($out !== '') { 2268 return $out; 2269 } 2270 2271 return trim(preg_replace('/[<>&"\']/', '', $text)); 2272 } 2273 2274 /** 2275 * Transliterates a string to ASCII. 2276 * 2277 * Used to generate RFC 3986 compliant and pretty ASCII-only URLs. 2278 * 2279 * @param string $str The string to convert 2280 * @param string $lang The language which translation table is used 2281 * @see sanitizeForUrl() 2282 * @package L10n 2283 */ 2284 2285 function dumbDown($str, $lang = LANG) 2286 { 2287 static $array; 2288 2289 if (empty($array[$lang])) { 2290 $array[$lang] = array( // Nasty, huh? 2291 'À' => 'A','À' => 'A','Á' => 'A','Á' => 'A','Â' => 'A','Â' => 'A', 2292 'Ã' => 'A','Ã' => 'A','Ä' => 'Ae','Ä' => 'A','Å' => 'A','Å' => 'A', 2293 'Æ' => 'Ae','Æ' => 'AE', 2294 'Ā' => 'A','Ą' => 'A','Ă' => 'A', 2295 'Ç' => 'C','Ç' => 'C','Ć' => 'C','Č' => 'C','Ĉ' => 'C','Ċ' => 'C', 2296 'Ď' => 'D','Đ' => 'D','Ð' => 'D','Ð' => 'D', 2297 'È' => 'E','È' => 'E','É' => 'E','É' => 'E','Ê' => 'E','Ê' => 'E','Ë' => 'E','Ë' => 'E', 2298 'Ē' => 'E','Ę' => 'E','Ě' => 'E','Ĕ' => 'E','Ė' => 'E', 2299 'Ĝ' => 'G','Ğ' => 'G','Ġ' => 'G','Ģ' => 'G', 2300 'Ĥ' => 'H','Ħ' => 'H', 2301 'Ì' => 'I','Ì' => 'I','Í' => 'I','Í' => 'I','Î' => 'I','Î' => 'I','Ï' => 'I','Ï' => 'I', 2302 'Ī' => 'I','Ĩ' => 'I','Ĭ' => 'I','Į' => 'I','İ' => 'I', 2303 'IJ' => 'IJ', 2304 'Ĵ' => 'J', 2305 'Ķ' => 'K', 2306 'Ł' => 'K','Ľ' => 'K','Ĺ' => 'K','Ļ' => 'K','Ŀ' => 'K', 2307 'Ñ' => 'N','Ñ' => 'N','Ń' => 'N','Ň' => 'N','Ņ' => 'N','Ŋ' => 'N', 2308 'Ò' => 'O','Ò' => 'O','Ó' => 'O','Ó' => 'O','Ô' => 'O','Ô' => 'O','Õ' => 'O','Õ' => 'O', 2309 'Ö' => 'Oe','Ö' => 'Oe', 2310 'Ø' => 'O','Ø' => 'O','Ō' => 'O','Ő' => 'O','Ŏ' => 'O', 2311 'Œ' => 'OE', 2312 'Ŕ' => 'R','Ř' => 'R','Ŗ' => 'R', 2313 'Ś' => 'S','Š' => 'S','Ş' => 'S','Ŝ' => 'S','Ș' => 'S', 2314 'Ť' => 'T','Ţ' => 'T','Ŧ' => 'T','Ț' => 'T', 2315 'Ù' => 'U','Ù' => 'U','Ú' => 'U','Ú' => 'U','Û' => 'U','Û' => 'U', 2316 'Ü' => 'Ue','Ū' => 'U','Ü' => 'Ue', 2317 'Ů' => 'U','Ű' => 'U','Ŭ' => 'U','Ũ' => 'U','Ų' => 'U', 2318 'Ŵ' => 'W', 2319 'Ý' => 'Y','Ý' => 'Y','Ŷ' => 'Y','Ÿ' => 'Y', 2320 'Ź' => 'Z','Ž' => 'Z','Ż' => 'Z', 2321 'Þ' => 'T','Þ' => 'T', 2322 'à' => 'a','á' => 'a','â' => 'a','ã' => 'a','ä' => 'ae', 2323 'ä' => 'ae', 2324 'å' => 'a','ā' => 'a','ą' => 'a','ă' => 'a','å' => 'a', 2325 'æ' => 'ae', 2326 'ç' => 'c','ć' => 'c','č' => 'c','ĉ' => 'c','ċ' => 'c', 2327 'ď' => 'd','đ' => 'd','ð' => 'd', 2328 'è' => 'e','é' => 'e','ê' => 'e','ë' => 'e','ē' => 'e', 2329 'ę' => 'e','ě' => 'e','ĕ' => 'e','ė' => 'e', 2330 'ƒ' => 'f', 2331 'ĝ' => 'g','ğ' => 'g','ġ' => 'g','ģ' => 'g', 2332 'ĥ' => 'h','ħ' => 'h', 2333 'ì' => 'i','í' => 'i','î' => 'i','ï' => 'i','ī' => 'i', 2334 'ĩ' => 'i','ĭ' => 'i','į' => 'i','ı' => 'i', 2335 'ij' => 'ij', 2336 'ĵ' => 'j', 2337 'ķ' => 'k','ĸ' => 'k', 2338 'ł' => 'l','ľ' => 'l','ĺ' => 'l','ļ' => 'l','ŀ' => 'l', 2339 'ñ' => 'n','ń' => 'n','ň' => 'n','ņ' => 'n','ʼn' => 'n', 2340 'ŋ' => 'n', 2341 'ò' => 'o','ó' => 'o','ô' => 'o','õ' => 'o','ö' => 'oe', 2342 'ö' => 'oe', 2343 'ø' => 'o','ō' => 'o','ő' => 'o','ŏ' => 'o', 2344 'œ' => 'oe', 2345 'ŕ' => 'r','ř' => 'r','ŗ' => 'r', 2346 'š' => 's', 2347 'ù' => 'u','ú' => 'u','û' => 'u','ü' => 'ue','ū' => 'u', 2348 'ü' => 'ue', 2349 'ů' => 'u','ű' => 'u','ŭ' => 'u','ũ' => 'u','ų' => 'u', 2350 'ŵ' => 'w', 2351 'ý' => 'y','ÿ' => 'y','ŷ' => 'y', 2352 'ž' => 'z','ż' => 'z','ź' => 'z', 2353 'þ' => 't', 2354 'ß' => 'ss', 2355 'ſ' => 'ss', 2356 'à' => 'a','á' => 'a','â' => 'a','ã' => 'a','ä' => 'ae', 2357 'å' => 'a','æ' => 'ae','ç' => 'c','ð' => 'd', 2358 'è' => 'e','é' => 'e','ê' => 'e','ë' => 'e', 2359 'ì' => 'i','í' => 'i','î' => 'i','ï' => 'i', 2360 'ñ' => 'n', 2361 'ò' => 'o','ó' => 'o','ô' => 'o','õ' => 'o','ö' => 'oe', 2362 'ø' => 'o', 2363 'ù' => 'u','ú' => 'u','û' => 'u','ü' => 'ue', 2364 'ý' => 'y','ÿ' => 'y', 2365 'þ' => 't', 2366 'ß' => 'ss', 2367 ); 2368 2369 if (is_file(txpath.'/lib/i18n-ascii.txt')) { 2370 $i18n = parse_ini_file(txpath.'/lib/i18n-ascii.txt', true); 2371 2372 // Load the global map. 2373 if (isset($i18n['default']) && is_array($i18n['default'])) { 2374 $array[$lang] = array_merge($array[$lang], $i18n['default']); 2375 2376 // Base language overrides: 'de-AT' applies the 'de' section. 2377 if (preg_match('/([a-zA-Z]+)-.+/', $lang, $m)) { 2378 if (isset($i18n[$m[1]]) && is_array($i18n[$m[1]])) { 2379 $array[$lang] = array_merge($array[$lang], $i18n[$m[1]]); 2380 } 2381 } 2382 2383 // Regional language overrides: 'de-AT' applies the 'de-AT' section. 2384 if (isset($i18n[$lang]) && is_array($i18n[$lang])) { 2385 $array[$lang] = array_merge($array[$lang], $i18n[$lang]); 2386 } 2387 } 2388 2389 // Load an old file (no sections) just in case. 2390 else { 2391 $array[$lang] = array_merge($array[$lang], $i18n); 2392 } 2393 } 2394 } 2395 2396 return strtr($str, $array[$lang]); 2397 } 2398 2399 /** 2400 * Cleans a URL. 2401 * 2402 * @param string $url The URL 2403 * @return string 2404 * @access private 2405 * @package URL 2406 */ 2407 2408 function clean_url($url) 2409 { 2410 return preg_replace("/\"|'|(?:\s.*$)/", '', $url); 2411 } 2412 2413 /** 2414 * Replace the last space with a   non-breaking space. 2415 * 2416 * @param string $str The string 2417 * @return string 2418 */ 2419 2420 function noWidow($str) 2421 { 2422 if (REGEXP_UTF8 == 1) { 2423 return preg_replace('@[ ]+([[:punct:]]?[\p{L}\p{N}\p{Pc}]+[[:punct:]]?)$@u', ' $1', rtrim($str)); 2424 } 2425 2426 return preg_replace('@[ ]+([[:punct:]]?\w+[[:punct:]]?)$@', ' $1', rtrim($str)); 2427 } 2428 2429 /** 2430 * Checks if an IP is on a spam blacklist. 2431 * 2432 * @param string $ip The IP address 2433 * @param string|array $checks The checked lists. Defaults to 'spam_blacklists' preferences string 2434 * @return string|bool The lists the IP is on or FALSE 2435 * @package Comment 2436 * @example 2437 * if (is_blacklisted('127.0.0.1')) 2438 * { 2439 * echo "'127.0.0.1' is blacklisted."; 2440 * } 2441 */ 2442 2443 function is_blacklisted($ip, $checks = '') 2444 { 2445 if (!$checks) { 2446 $checks = do_list_unique(get_pref('spam_blacklists')); 2447 } 2448 2449 $rip = join('.', array_reverse(explode('.', $ip))); 2450 2451 foreach ((array) $checks as $a) { 2452 $parts = explode(':', $a, 2); 2453 $rbl = $parts[0]; 2454 2455 if (isset($parts[1])) { 2456 foreach (explode(':', $parts[1]) as $code) { 2457 $codes[] = strpos($code, '.') ? $code : '127.0.0.'.$code; 2458 } 2459 } 2460 2461 $hosts = $rbl ? @gethostbynamel($rip.'.'.trim($rbl, '. ').'.') : false; 2462 2463 if ($hosts and (!isset($codes) or array_intersect($hosts, $codes))) { 2464 $listed[] = $rbl; 2465 } 2466 } 2467 2468 return (!empty($listed)) ? join(', ', $listed) : false; 2469 } 2470 2471 /** 2472 * Checks if the user is authenticated on the public-side. 2473 * 2474 * @param string $user The checked username. If not provided, any user is accepted 2475 * @return array|bool An array containing details about the user; name, RealName, email, privs. FALSE when the user hasn't authenticated. 2476 * @package User 2477 * @example 2478 * if ($user = is_logged_in()) 2479 * { 2480 * echo "Logged in as {$user['RealName']}"; 2481 * } 2482 */ 2483 2484 function is_logged_in($user = '') 2485 { 2486 $name = substr(cs('txp_login_public'), 10); 2487 2488 if (!strlen($name) or strlen($user) and $user !== $name) { 2489 return false; 2490 } 2491 2492 $rs = safe_row("nonce, name, RealName, email, privs", 'txp_users', "name = '".doSlash($name)."'"); 2493 2494 if ($rs and substr(md5($rs['nonce']), -10) === substr(cs('txp_login_public'), 0, 10)) { 2495 unset($rs['nonce']); 2496 2497 return $rs; 2498 } else { 2499 return false; 2500 } 2501 } 2502 2503 /** 2504 * Updates the path to the site. 2505 * 2506 * @param string $here The path 2507 * @access private 2508 * @package Pref 2509 */ 2510 2511 function updateSitePath($here) 2512 { 2513 set_pref('path_to_site', $here, 'publish', PREF_HIDDEN); 2514 } 2515 2516 /** 2517 * Converts Textpattern tag's attribute list to an array. 2518 * 2519 * @param string $text The attribute list, e.g. foobar="1" barfoo="0" 2520 * @return array Array of attributes 2521 * @access private 2522 * @package TagParser 2523 */ 2524 2525 function splat($text) 2526 { 2527 static $stack, $parse; 2528 global $production_status, $trace; 2529 2530 if (strlen($text) < 3) { 2531 return array(); 2532 } 2533 2534 $sha = sha1($text); 2535 2536 if (!isset($stack[$sha])) { 2537 $stack[$sha] = array(); 2538 $parse[$sha] = array(); 2539 2540 if (preg_match_all('@(\w+)\s*=\s*(?:"((?:[^"]|"")*)"|\'((?:[^\']|\'\')*)\'|([^\s\'"/>]+))@s', $text, $match, PREG_SET_ORDER)) { 2541 foreach ($match as $m) { 2542 switch (count($m)) { 2543 case 3: 2544 $val = str_replace('""', '"', $m[2]); 2545 break; 2546 case 4: 2547 $val = str_replace("''", "'", $m[3]); 2548 2549 if (strpos($m[3], ':') !== false) { 2550 $parse[$sha][] = strtolower($m[1]); 2551 } 2552 2553 break; 2554 case 5: 2555 $val = $m[4]; 2556 trigger_error(gTxt('attribute_values_must_be_quoted'), E_USER_WARNING); 2557 break; 2558 } 2559 2560 $stack[$sha][strtolower($m[1])] = $val; 2561 } 2562 } 2563 } 2564 2565 if (empty($parse[$sha])) { 2566 return $stack[$sha]; 2567 } else { 2568 $atts = $stack[$sha]; 2569 2570 if ($production_status !== 'live') { 2571 foreach ($parse[$sha] as $p) { 2572 $trace->start("[attribute '".$p."']"); 2573 $atts[$p] = parse($atts[$p]); 2574 $trace->stop('[/attribute]'); 2575 } 2576 } else { 2577 foreach ($parse[$sha] as $p) { 2578 $atts[$p] = parse($atts[$p]); 2579 } 2580 } 2581 2582 return $atts; 2583 } 2584 } 2585 2586 /** 2587 * Replaces CR and LF with spaces, and drops NULL bytes. 2588 * 2589 * Used for sanitising email headers. 2590 * 2591 * @param string $str The string 2592 * @return string 2593 * @package Mail 2594 * @deprecated in 4.6.0 2595 * @see \Textpattern\Mail\Encode::escapeHeader() 2596 */ 2597 2598 function strip_rn($str) 2599 { 2600 return Txp::get('\Textpattern\Mail\Encode')->escapeHeader($str); 2601 } 2602 2603 /** 2604 * Validates a string as an email address. 2605 * 2606 * <code> 2607 * if (is_valid_email('john.doe@example.com')) 2608 * { 2609 * echo "'john.doe@example.com' validates."; 2610 * } 2611 * </code> 2612 * 2613 * @param string $address The email address 2614 * @return bool 2615 * @package Mail 2616 * @deprecated in 4.6.0 2617 * @see filter_var() 2618 */ 2619 2620 function is_valid_email($address) 2621 { 2622 return (bool) filter_var($address, FILTER_VALIDATE_EMAIL); 2623 } 2624 2625 /** 2626 * Sends an email message as the currently logged in user. 2627 * 2628 * <code> 2629 * if (txpMail('john.doe@example.com', 'Subject', 'Some message')) 2630 * { 2631 * echo "Email sent to 'john.doe@example.com'."; 2632 * } 2633 * </code> 2634 * 2635 * @param string $to_address The receiver 2636 * @param string $subject The subject 2637 * @param string $body The message 2638 * @param string $reply_to The reply to address 2639 * @return bool Returns FALSE when sending failed 2640 * @see \Textpattern\Mail\Compose 2641 * @package Mail 2642 */ 2643 2644 function txpMail($to_address, $subject, $body, $reply_to = null) 2645 { 2646 global $txp_user; 2647 2648 // Send the email as the currently logged in user. 2649 if ($txp_user) { 2650 $sender = safe_row( 2651 "RealName, email", 2652 'txp_users', 2653 "name = '".doSlash($txp_user)."'" 2654 ); 2655 2656 if ($sender && is_valid_email(get_pref('publisher_email'))) { 2657 $sender['email'] = get_pref('publisher_email'); 2658 } 2659 } 2660 // If not logged in, the receiver is the sender. 2661 else { 2662 $sender = safe_row( 2663 "RealName, email", 2664 'txp_users', 2665 "email = '".doSlash($to_address)."'" 2666 ); 2667 } 2668 2669 if ($sender) { 2670 extract($sender); 2671 2672 try { 2673 $message = Txp::get('Textpattern\Mail\Compose') 2674 ->from($email, $RealName) 2675 ->to($to_address) 2676 ->subject($subject) 2677 ->body($body); 2678 2679 if ($reply_to) { 2680 $message->replyTo($reply_to); 2681 } 2682 2683 $message->send(); 2684 } catch (\Textpattern\Mail\Exception $e) { 2685 return false; 2686 } 2687 2688 return true; 2689 } 2690 2691 return false; 2692 } 2693 2694 /** 2695 * Encodes a string for use in an email header. 2696 * 2697 * @param string $string The string 2698 * @param string $type The type of header, either "text" or "phrase" 2699 * @return string 2700 * @package Mail 2701 * @deprecated in 4.6.0 2702 * @see \Textpattern\Mail\Encode::header() 2703 */ 2704 2705 function encode_mailheader($string, $type) 2706 { 2707 try { 2708 return Txp::get('\Textpattern\Mail\Encode')->header($string, $type); 2709 } catch (\Textpattern\Mail\Exception $e) { 2710 trigger_error($e->getMessage(), E_USER_WARNING); 2711 } 2712 } 2713 2714 /** 2715 * Converts an email address into unicode entities. 2716 * 2717 * @param string $txt The email address 2718 * @return string Encoded email address 2719 * @package Mail 2720 * @deprecated in 4.6.0 2721 * @see \Textpattern\Mail\Encode::entityObfuscateAddress() 2722 */ 2723 2724 function eE($txt) 2725 { 2726 return Txp::get('\Textpattern\Mail\Encode')->entityObfuscateAddress($txt); 2727 } 2728 2729 /** 2730 * Strips PHP tags from a string. 2731 * 2732 * @param string $in The input 2733 * @return string 2734 */ 2735 2736 function stripPHP($in) 2737 { 2738 return preg_replace("/".chr(60)."\?(?:php)?|\?".chr(62)."/i", '', $in); 2739 } 2740 2741 /** 2742 * Gets a HTML select field containing all categories, or sub-categories. 2743 * 2744 * @param string $name Return specified parent category's sub-categories 2745 * @param string $cat The selected category option 2746 * @param string $id The HTML ID 2747 * @return string|bool HTML select field or FALSE on error 2748 * @package Form 2749 */ 2750 2751 function event_category_popup($name, $cat = '', $id = '') 2752 { 2753 $rs = getTree('root', $name); 2754 2755 if ($rs) { 2756 return treeSelectInput('category', $rs, $cat, $id); 2757 } 2758 2759 return false; 2760 } 2761 2762 /** 2763 * Creates a form template. 2764 * 2765 * On a successful run, will trigger a 'form.create > done' callback event. 2766 * 2767 * @param string $name The name 2768 * @param string $type The type 2769 * @param string $Form The template 2770 * @return bool FALSE on error 2771 * @since 4.6.0 2772 * @package Template 2773 */ 2774 2775 function create_form($name, $type, $Form) 2776 { 2777 $types = get_form_types(); 2778 2779 if (form_exists($name) || !is_valid_form($name) || !in_array($type, array_keys($types))) { 2780 return false; 2781 } 2782 2783 if ( 2784 safe_insert( 2785 'txp_form', 2786 "name = '".doSlash($name)."', 2787 type = '".doSlash($type)."', 2788 Form = '".doSlash($Form)."'" 2789 ) === false 2790 ) { 2791 return false; 2792 } 2793 2794 callback_event('form.create', 'done', 0, compact('name', 'type', 'Form')); 2795 2796 return true; 2797 } 2798 2799 /** 2800 * Checks if a form template exists. 2801 * 2802 * @param string $name The form 2803 * @return bool TRUE if the form exists 2804 * @since 4.6.0 2805 * @package Template 2806 */ 2807 2808 function form_exists($name) 2809 { 2810 return (bool) safe_row("name", 'txp_form', "name = '".doSlash($name)."'"); 2811 } 2812 2813 /** 2814 * Validates a string as a form template name. 2815 * 2816 * @param string $name The form name 2817 * @return bool TRUE if the string validates 2818 * @since 4.6.0 2819 * @package Template 2820 */ 2821 2822 function is_valid_form($name) 2823 { 2824 if (function_exists('mb_strlen')) { 2825 $length = mb_strlen($name, '8bit'); 2826 } else { 2827 $length = strlen($name); 2828 } 2829 2830 return $name && !preg_match('/^\s|[<>&"\']|\s$/u', $name) && $length <= 64; 2831 } 2832 2833 /** 2834 * Gets a list of form types. 2835 * 2836 * The list form types can be extended with a 'form.types > types' 2837 * callback event. Callback functions get passed three arguments: '$event', 2838 * '$step' and '$types'. The third parameter contains a reference to an 2839 * array of 'type => label' pairs. 2840 * 2841 * @return array An array of form types 2842 * @since 4.6.0 2843 * @package Template 2844 */ 2845 2846 function get_form_types() 2847 { 2848 static $types = null; 2849 2850 if ($types === null) { 2851 $types = array( 2852 'article' => gTxt('article'), 2853 'misc' => gTxt('misc'), 2854 'comment' => gTxt('comment'), 2855 'category' => gTxt('category'), 2856 'file' => gTxt('file'), 2857 'link' => gTxt('link'), 2858 'section' => gTxt('section'), 2859 ); 2860 2861 callback_event_ref('form.types', 'types', 0, $types); 2862 } 2863 2864 return $types; 2865 } 2866 2867 /** 2868 * Gets a list of essential form templates. 2869 * 2870 * These forms can not be deleted or renamed. 2871 * 2872 * The list forms can be extended with a 'form.essential > forms' 2873 * callback event. Callback functions get passed three arguments: '$event', 2874 * '$step' and '$essential'. The third parameter contains a reference to an 2875 * array of forms. 2876 * 2877 * @return array An array of form names 2878 * @since 4.6.0 2879 * @package Template 2880 */ 2881 2882 function get_essential_forms() 2883 { 2884 static $essential = null; 2885 2886 if ($essential === null) { 2887 $essential = array( 2888 'comments', 2889 'comments_display', 2890 'comment_form', 2891 'default', 2892 'plainlinks', 2893 'files', 2894 ); 2895 2896 callback_event_ref('form.essential', 'forms', 0, $essential); 2897 } 2898 2899 return $essential; 2900 } 2901 2902 /** 2903 * Updates a list's per page number. 2904 * 2905 * Gets the per page number from a "qty" HTTP POST/GET parameter and 2906 * creates a user-specific preference value "$name_list_pageby". 2907 * 2908 * @param string|null $name The name of the list 2909 */ 2910 2911 function event_change_pageby($name = null) 2912 { 2913 global $event, $prefs; 2914 2915 if ($name === null) { 2916 $name = $event; 2917 } 2918 2919 $qty = gps('qty'); 2920 assert_int($qty); 2921 $pageby = $name.'_list_pageby'; 2922 $GLOBALS[$pageby] = $prefs[$pageby] = $qty; 2923 2924 set_pref($pageby, $qty, $event, PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE); 2925 } 2926 2927 /** 2928 * Generates a multi-edit widget. 2929 * 2930 * @param string $name 2931 * @param array $methods 2932 * @param int $page 2933 * @param string $sort 2934 * @param string $dir 2935 * @param string $crit 2936 * @param string $search_method 2937 * @deprecated in 4.5.0 2938 * @see multi_edit() 2939 * @package Form 2940 */ 2941 2942 function event_multiedit_form($name, $methods = null, $page, $sort, $dir, $crit, $search_method) 2943 { 2944 $method = ps('edit_method'); 2945 2946 if ($methods === null) { 2947 $methods = array( 2948 'delete' => gTxt('delete'), 2949 ); 2950 } 2951 2952 return '<label for="withselected">'.gTxt('with_selected').'</label>'. 2953 n.selectInput('edit_method', $methods, $method, 1, ' id="withselected" onchange="poweredit(this); return false;"'). 2954 n.eInput($name). 2955 n.sInput($name.'_multi_edit'). 2956 n.hInput('page', $page). 2957 ($sort ? n.hInput('sort', $sort).n.hInput('dir', $dir) : ''). 2958 (($crit != '') ? n.hInput('crit', $crit).n.hInput('search_method', $search_method) : ''). 2959 n.fInput('submit', '', gTxt('go')); 2960 } 2961 2962 /** 2963 * Generic multi-edit form's edit handler shared across panels. 2964 * 2965 * Receives an action from a multi-edit form and runs it in the given 2966 * database table. 2967 * 2968 * @param string $table The database table 2969 * @param string $id_key The database column selected items match to. Column should be integer type 2970 * @return string Comma-separated list of affected items 2971 * @see multi_edit() 2972 */ 2973 2974 function event_multi_edit($table, $id_key) 2975 { 2976 $method = ps('edit_method'); 2977 $selected = ps('selected'); 2978 2979 if ($selected) { 2980 if ($method == 'delete') { 2981 foreach ($selected as $id) { 2982 $id = assert_int($id); 2983 2984 if (safe_delete($table, "$id_key = $id")) { 2985 $ids[] = $id; 2986 } 2987 } 2988 2989 return join(', ', $ids); 2990 } 2991 } 2992 2993 return ''; 2994 } 2995 2996 /** 2997 * Gets a "since days ago" date format from a given UNIX timestamp. 2998 * 2999 * @param int $stamp UNIX timestamp 3000 * @return string "n days ago" 3001 * @package DateTime 3002 */ 3003 3004 function since($stamp) 3005 { 3006 $diff = (time() - $stamp); 3007 3008 if ($diff <= 3600) { 3009 $mins = round($diff / 60); 3010 $since = ($mins <= 1) ? ($mins == 1) ? '1 '.gTxt('minute') : gTxt('a_few_seconds') : "$mins ".gTxt('minutes'); 3011 } elseif (($diff <= 86400) && ($diff > 3600)) { 3012 $hours = round($diff / 3600); 3013 $since = ($hours <= 1) ? '1 '.gTxt('hour') : "$hours ".gTxt('hours'); 3014 } elseif ($diff >= 86400) { 3015 $days = round($diff / 86400); 3016 $since = ($days <= 1) ? "1 ".gTxt('day') : "$days ".gTxt('days'); 3017 } 3018 3019 return $since.' '.gTxt('ago'); // sorry, this needs to be hacked until a truly multilingual version is done 3020 } 3021 3022 /** 3023 * Calculates a timezone offset. 3024 * 3025 * Calculates the offset between the server local time and the 3026 * user's selected timezone at a given point in time. 3027 * 3028 * @param int $timestamp The timestamp. Defaults to time() 3029 * @return int The offset in seconds 3030 * @package DateTime 3031 */ 3032 3033 function tz_offset($timestamp = null) 3034 { 3035 global $gmtoffset, $timezone_key; 3036 static $dtz = array(), $timezone_server = null; 3037 3038 if ($timezone_server === null) { 3039 $timezone_server = date_default_timezone_get(); 3040 } 3041 3042 if ($timezone_server === $timezone_key) { 3043 return 0; 3044 } 3045 3046 if ($timestamp === null) { 3047 $timestamp = time(); 3048 } 3049 3050 try { 3051 if (!isset($dtz[$timezone_server])) { 3052 $dtz[$timezone_server] = new \DateTimeZone($timezone_server); 3053 } 3054 3055 $transition = $dtz[$timezone_server]->getTransitions($timestamp, $timestamp); 3056 $serveroffset = $transition[0]['offset']; 3057 } catch (\Exception $e) { 3058 extract(getdate($timestamp)); 3059 $serveroffset = gmmktime($hours, $minutes, 0, $mon, $mday, $year) - mktime($hours, $minutes, 0, $mon, $mday, $year); 3060 } 3061 3062 try { 3063 if (!isset($dtz[$timezone_key])) { 3064 $dtz[$timezone_key] = new \DateTimeZone($timezone_key); 3065 } 3066 3067 $transition = $dtz[$timezone_key]->getTransitions($timestamp, $timestamp); 3068 $siteoffset = $transition[0]['offset']; 3069 } catch (\Exception $e) { 3070 $siteoffset = $gmtoffset; 3071 } 3072 3073 return $siteoffset - $serveroffset; 3074 } 3075 3076 /** 3077 * Formats a time. 3078 * 3079 * Respects the locale and local timezone, and makes sure the 3080 * output string is encoded in UTF-8. 3081 * 3082 * @param string $format The date format 3083 * @param int $time UNIX timestamp. Defaults to time() 3084 * @param bool $gmt Return GMT time 3085 * @param string $override_locale Override the locale 3086 * @return string Formatted date 3087 * @package DateTime 3088 * @example 3089 * echo safe_strftime('w3cdtf'); 3090 */ 3091 3092 function safe_strftime($format, $time = '', $gmt = false, $override_locale = '') 3093 { 3094 global $locale; 3095 3096 if (!$time) { 3097 $time = time(); 3098 } 3099 3100 // We could add some other formats here. 3101 if ($format == 'iso8601' or $format == 'w3cdtf') { 3102 $format = '%Y-%m-%dT%H:%M:%SZ'; 3103 $gmt = true; 3104 } elseif ($format == 'rfc822') { 3105 $format = '%a, %d %b %Y %H:%M:%S GMT'; 3106 $gmt = true; 3107 $override_locale = 'en-gb'; 3108 } 3109 3110 if ($override_locale) { 3111 $oldLocale = Txp::get('\Textpattern\L10n\Locale')->getLocale(LC_TIME); 3112 3113 try { 3114 Txp::get('\Textpattern\L10n\Locale')->setLocale(LC_TIME, $override_locale); 3115 } catch (\Exception $e) { 3116 // Revert to original locale on error and signal that the 3117 // later revert isn't necessary 3118 Txp::get('\Textpattern\L10n\Locale')->setLocale(LC_TIME, $oldLocale); 3119 $oldLocale = false; 3120 } 3121 } 3122 3123 if ($format == 'since') { 3124 $str = since($time); 3125 } elseif ($gmt) { 3126 $str = gmstrftime($format, $time); 3127 } else { 3128 $str = strftime($format, $time + tz_offset($time)); 3129 } 3130 3131 @list($lang, $charset) = explode('.', $locale); 3132 3133 if (empty($charset)) { 3134 $charset = 'ISO-8859-1'; 3135 } elseif (IS_WIN and is_numeric($charset)) { 3136 $charset = 'Windows-'.$charset; 3137 } 3138 3139 if ($charset != 'UTF-8' and $format != 'since') { 3140 $new = ''; 3141 if (is_callable('iconv')) { 3142 $new = @iconv($charset, 'UTF-8', $str); 3143 } 3144 3145 if ($new) { 3146 $str = $new; 3147 } elseif (is_callable('utf8_encode')) { 3148 $str = utf8_encode($str); 3149 } 3150 } 3151 3152 // Revert to the old locale. 3153 if ($override_locale && $oldLocale) { 3154 Txp::get('\Textpattern\L10n\Locale')->setLocale(LC_TIME, $oldLocale); 3155 } 3156 3157 return $str; 3158 } 3159 3160 /** 3161 * Converts a time string from the Textpattern timezone to GMT. 3162 * 3163 * @param string $time_str The time string 3164 * @return int UNIX timestamp 3165 * @package DateTime 3166 */ 3167 3168 function safe_strtotime($time_str) 3169 { 3170 $ts = strtotime($time_str); 3171 3172 // tz_offset calculations are expensive 3173 $tz_offset = tz_offset($ts); 3174 return strtotime($time_str, time() + $tz_offset) - $tz_offset; 3175 } 3176 3177 /** 3178 * Generic error handler. 3179 * 3180 * @param int $errno 3181 * @param string $errstr 3182 * @param string $errfile 3183 * @param int $errline 3184 * @access private 3185 * @package Debug 3186 */ 3187 3188 function myErrorHandler($errno, $errstr, $errfile, $errline) 3189 { 3190 if (!error_reporting()) { 3191 return; 3192 } 3193 3194 echo '<pre dir="auto">'.n.n."$errno: $errstr in $errfile at line $errline\n"; 3195 3196 if (is_callable('debug_backtrace')) { 3197 echo "Backtrace:\n"; 3198 $trace = debug_backtrace(); 3199 3200 foreach ($trace as $ent) { 3201 if (isset($ent['file'])) { 3202 echo $ent['file'].':'; 3203 } 3204 3205 if (isset($ent['function'])) { 3206 echo $ent['function'].'('; 3207 3208 if (isset($ent['args'])) { 3209 $args = ''; 3210 3211 foreach ($ent['args'] as $arg) { 3212 $args .= $arg.','; 3213 } 3214 3215 echo rtrim($args, ','); 3216 } 3217 3218 echo ') '; 3219 } 3220 3221 if (isset($ent['line'])) { 3222 echo 'at line '.$ent['line'].' '; 3223 } 3224 3225 if (isset($ent['file'])) { 3226 echo 'in '.$ent['file']; 3227 } 3228 3229 echo "\n"; 3230 } 3231 } 3232 3233 echo "</pre>"; 3234 } 3235 3236 /** 3237 * Verifies temporary directory. 3238 * 3239 * Verifies that the temporary directory is writeable. 3240 * 3241 * @param string $dir The directory to check 3242 * @return bool|null NULL on error, TRUE on success 3243 * @package Debug 3244 */ 3245 3246 function find_temp_dir() 3247 { 3248 global $path_to_site, $img_dir; 3249 3250 if (IS_WIN) { 3251 $guess = array( 3252 txpath.DS.'tmp', 3253 getenv('TMP'), 3254 getenv('TEMP'), 3255 getenv('SystemRoot').DS.'Temp', 3256 'C:'.DS.'Temp', 3257 $path_to_site.DS.$img_dir, 3258 ); 3259 3260 foreach ($guess as $k => $v) { 3261 if (empty($v)) { 3262 unset($guess[$k]); 3263 } 3264 } 3265 } else { 3266 $guess = array( 3267 txpath.DS.'tmp', 3268 '', 3269 DS.'tmp', 3270 $path_to_site.DS.$img_dir, 3271 ); 3272 } 3273 3274 foreach ($guess as $dir) { 3275 $tf = @tempnam($dir, 'txp_'); 3276 3277 if ($tf) { 3278 $tf = realpath($tf); 3279 } 3280 3281 if ($tf and file_exists($tf)) { 3282 unlink($tf); 3283 3284 return dirname($tf); 3285 } 3286 } 3287 3288 return false; 3289 } 3290 3291 /** 3292 * Moves an uploaded file and returns its new location. 3293 * 3294 * @param string $f The filename of the uploaded file 3295 * @param string $dest The destination of the moved file. If omitted, the file is moved to the temp directory 3296 * @return string|bool The new path or FALSE on error 3297 * @package File 3298 */ 3299 3300 function get_uploaded_file($f, $dest = '') 3301 { 3302 global $tempdir; 3303 3304 if (!is_uploaded_file($f)) { 3305 return false; 3306 } 3307 3308 if ($dest) { 3309 $newfile = $dest; 3310 } else { 3311 $newfile = tempnam($tempdir, 'txp_'); 3312 if (!$newfile) { 3313 return false; 3314 } 3315 } 3316 3317 // $newfile is created by tempnam(), but move_uploaded_file will overwrite it. 3318 if (move_uploaded_file($f, $newfile)) { 3319 return $newfile; 3320 } 3321 } 3322 3323 /** 3324 * Gets an array of files in the Files directory that weren't uploaded 3325 * from Textpattern. 3326 * 3327 * Used for importing existing files on the server to Textpattern's files panel. 3328 * 3329 * @return array An array of file paths 3330 * @package File 3331 */ 3332 3333 function get_filenames() 3334 { 3335 global $file_base_path; 3336 3337 $files = array(); 3338 3339 if (!is_dir($file_base_path) || !is_readable($file_base_path)) { 3340 return array(); 3341 } 3342 3343 $cwd = getcwd(); 3344 3345 if (chdir($file_base_path)) { 3346 $directory = glob('*', GLOB_NOSORT); 3347 3348 if ($directory) { 3349 foreach ($directory as $filename) { 3350 if (is_file($filename) && is_readable($filename)) { 3351 $files[$filename] = $filename; 3352 } 3353 } 3354 3355 unset($directory); 3356 } 3357 3358 if ($cwd) { 3359 chdir($cwd); 3360 } 3361 } 3362 3363 if (!$files) { 3364 return array(); 3365 } 3366 3367 $rs = safe_rows_start("filename", 'txp_file', "1 = 1"); 3368 3369 if ($rs && numRows($rs)) { 3370 while ($a = nextRow($rs)) { 3371 unset($files[$a['filename']]); 3372 } 3373 } 3374 3375 return $files; 3376 } 3377 3378 /** 3379 * Renders a download link. 3380 * 3381 * @param int $id The file ID 3382 * @param string $label The label 3383 * @param string $filename The filename 3384 * @return string HTML 3385 * @package File 3386 */ 3387 3388 function make_download_link($id, $label = '', $filename = '') 3389 { 3390 if ((string) $label === '') { 3391 $label = gTxt('download'); 3392 } 3393 3394 $url = filedownloadurl($id, $filename); 3395 3396 // Do not use the array() form of passing $atts to href(). 3397 // Doing so breaks download links on the admin side due to 3398 // double-encoding of the ampersands. 3399 return href($label, $url, ' title = "' . gTxt('download') . '"'); 3400 } 3401 3402 /** 3403 * Sets error reporting level. 3404 * 3405 * @param string $level The level. Either "debug", "live" or "testing" 3406 * @package Debug 3407 */ 3408 3409 function set_error_level($level) 3410 { 3411 if ($level == 'debug') { 3412 error_reporting(E_ALL | E_STRICT); 3413 } elseif ($level == 'live') { 3414 // Don't show errors on screen. 3415 $suppress = E_NOTICE | E_USER_NOTICE | E_WARNING | E_STRICT | (defined('E_DEPRECATED') ? E_DEPRECATED : 0); 3416 error_reporting(E_ALL ^ $suppress); 3417 @ini_set("display_errors", "1"); 3418 } else { 3419 // Default is 'testing': display everything except notices. 3420 error_reporting((E_ALL | E_STRICT) ^ (E_NOTICE | E_USER_NOTICE)); 3421 } 3422 } 3423 3424 /** 3425 * Moves a file. 3426 * 3427 * @param string $f The file to move 3428 * @param string $dest The destination 3429 * @return bool TRUE on success, or FALSE on error 3430 * @package File 3431 */ 3432 3433 function shift_uploaded_file($f, $dest) 3434 { 3435 if (@rename($f, $dest)) { 3436 return true; 3437 } 3438 3439 if (@copy($f, $dest)) { 3440 unlink($f); 3441 3442 return true; 3443 } 3444 3445 return false; 3446 } 3447 3448 /** 3449 * Translates upload error code to a localised error message. 3450 * 3451 * @param int $err_code The error code 3452 * @return string The $err_code as a message 3453 * @package File 3454 */ 3455 3456 function upload_get_errormsg($err_code) 3457 { 3458 $msg = ''; 3459 3460 switch ($err_code) { 3461 // Value: 0; There is no error, the file uploaded with success. 3462 case UPLOAD_ERR_OK: 3463 $msg = ''; 3464 break; 3465 // Value: 1; The uploaded file exceeds the upload_max_filesize directive in php.ini. 3466 case UPLOAD_ERR_INI_SIZE: 3467 $msg = gTxt('upload_err_ini_size'); 3468 break; 3469 // Value: 2; The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form. 3470 case UPLOAD_ERR_FORM_SIZE : 3471 $msg = gTxt('upload_err_form_size'); 3472 break; 3473 // Value: 3; The uploaded file was only partially uploaded. 3474 case UPLOAD_ERR_PARTIAL: 3475 $msg = gTxt('upload_err_partial'); 3476 break; 3477 // Value: 4; No file was uploaded. 3478 case UPLOAD_ERR_NO_FILE: 3479 $msg = gTxt('upload_err_no_file'); 3480 break; 3481 // Value: 6; Missing a temporary folder. Introduced in PHP 4.3.10 and PHP 5.0.3. 3482 case UPLOAD_ERR_NO_TMP_DIR: 3483 $msg = gTxt('upload_err_tmp_dir'); 3484 break; 3485 // Value: 7; Failed to write file to disk. Introduced in PHP 5.1.0. 3486 case UPLOAD_ERR_CANT_WRITE: 3487 $msg = gTxt('upload_err_cant_write'); 3488 break; 3489 // Value: 8; File upload stopped by extension. Introduced in PHP 5.2.0. 3490 case UPLOAD_ERR_EXTENSION: 3491 $msg = gTxt('upload_err_extension'); 3492 break; 3493 } 3494 3495 return $msg; 3496 } 3497 3498 /** 3499 * Formats a file size. 3500 * 3501 * @param int $bytes Size in bytes 3502 * @param int $decimals Number of decimals 3503 * @param string $format The format the size is represented 3504 * @return string Formatted file size 3505 * @package File 3506 * @example 3507 * echo format_filesize(168642); 3508 */ 3509 3510 function format_filesize($bytes, $decimals = 2, $format = '') 3511 { 3512 $units = array('b', 'k', 'm', 'g', 't', 'p', 'e', 'z', 'y'); 3513 3514 if (in_array($format, $units)) { 3515 $pow = array_search($format, $units); 3516 } else { 3517 $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); 3518 $pow = min($pow, count($units) - 1); 3519 } 3520 3521 $bytes /= pow(1024, $pow); 3522 3523 $separators = localeconv(); 3524 $sep_dec = isset($separators['decimal_point']) ? $separators['decimal_point'] : '.'; 3525 $sep_thous = isset($separators['thousands_sep']) ? $separators['thousands_sep'] : ','; 3526 3527 return number_format($bytes, $decimals, $sep_dec, $sep_thous).gTxt('units_'.$units[$pow]); 3528 } 3529 3530 /** 3531 * Gets a file download as an array. 3532 * 3533 * @param string $where SQL where clause 3534 * @return array|bool An array of files, or FALSE on failure 3535 * @package File 3536 * @example 3537 * if ($file = fileDownloadFetchInfo('id = 1')) 3538 * { 3539 * print_r($file); 3540 * } 3541 */ 3542 3543 function fileDownloadFetchInfo($where) 3544 { 3545 $rs = safe_row("*", 'txp_file', $where); 3546 3547 if ($rs) { 3548 return file_download_format_info($rs); 3549 } 3550 3551 return false; 3552 } 3553 3554 /** 3555 * Formats file download info. 3556 * 3557 * Takes a data array generated by fileDownloadFetchInfo() 3558 * and formats the contents. 3559 * 3560 * @param array $file The file info to format 3561 * @return array Formatted file info 3562 * @access private 3563 * @package File 3564 */ 3565 3566 function file_download_format_info($file) 3567 { 3568 if (($unix_ts = @strtotime($file['created'])) > 0) { 3569 $file['created'] = $unix_ts; 3570 } 3571 3572 if (($unix_ts = @strtotime($file['modified'])) > 0) { 3573 $file['modified'] = $unix_ts; 3574 } 3575 3576 return $file; 3577 } 3578 3579 /** 3580 * Formats file download's modification and creation timestamps. 3581 * 3582 * Used by file_download tags. 3583 * 3584 * @param array $params 3585 * @return string 3586 * @access private 3587 * @package File 3588 */ 3589 3590 function fileDownloadFormatTime($params) 3591 { 3592 extract(lAtts(array( 3593 'ftime' => '', 3594 'format' => '', 3595 ), $params)); 3596 3597 if (!empty($ftime)) { 3598 if ($format) { 3599 return safe_strftime($format, $ftime); 3600 } 3601 3602 return safe_strftime(get_pref('archive_dateformat'), $ftime); 3603 } 3604 3605 return ''; 3606 } 3607 3608 /** 3609 * Checks if the system is Windows. 3610 * 3611 * Exists for backwards compatibility. 3612 * 3613 * @return bool 3614 * @deprecated in 4.3.0 3615 * @see IS_WIN 3616 * @package System 3617 */ 3618 3619 function is_windows() 3620 { 3621 return IS_WIN; 3622 } 3623 3624 /** 3625 * Checks if PHP is run as CGI. 3626 * 3627 * Exists for backwards compatibility. 3628 * 3629 * @return bool 3630 * @deprecated in 4.3.0 3631 * @see IS_CGI 3632 * @package System 3633 */ 3634 3635 function is_cgi() 3636 { 3637 return IS_CGI; 3638 } 3639 3640 /** 3641 * Checks if PHP is run as Apache module. 3642 * 3643 * Exists for backwards compatibility. 3644 * 3645 * @return bool 3646 * @deprecated in 4.3.0 3647 * @see IS_APACHE 3648 * @package System 3649 */ 3650 3651 function is_mod_php() 3652 { 3653 return IS_APACHE; 3654 } 3655 3656 /** 3657 * Checks if a function is disabled. 3658 * 3659 * @param string $function The function name 3660 * @return bool TRUE if the function is disabled 3661 * @package System 3662 * @example 3663 * if (is_disabled('mail')) 3664 * { 3665 * echo "'mail' function is disabled."; 3666 * } 3667 */ 3668 3669 function is_disabled($function) 3670 { 3671 static $disabled; 3672 3673 if (!isset($disabled)) { 3674 $disabled = do_list(ini_get('disable_functions')); 3675 } 3676 3677 return in_array($function, $disabled); 3678 } 3679 3680 /** 3681 * Joins two strings to form a single filesystem path. 3682 * 3683 * @param string $base The base directory 3684 * @param string $path The second path, a relative filename 3685 * @return string A path to a file 3686 * @package File 3687 */ 3688 3689 function build_file_path($base, $path) 3690 { 3691 $base = rtrim($base, '/\\'); 3692 $path = ltrim($path, '/\\'); 3693 3694 return $base.DS.$path; 3695 } 3696 3697 /** 3698 * Gets a user's real name. 3699 * 3700 * @param string $name The username 3701 * @return string A real name, or username if empty 3702 * @package User 3703 */ 3704 3705 function get_author_name($name) 3706 { 3707 static $authors = array(); 3708 3709 if (isset($authors[$name])) { 3710 return $authors[$name]; 3711 } 3712 3713 $realname = fetch('RealName', 'txp_users', 'name', $name); 3714 $authors[$name] = $realname; 3715 3716 return ($realname) ? $realname : $name; 3717 } 3718 3719 /** 3720 * Gets a user's email address. 3721 * 3722 * @param string $name The username 3723 * @return string 3724 * @package User 3725 */ 3726 3727 function get_author_email($name) 3728 { 3729 static $authors = array(); 3730 3731 if (isset($authors[$name])) { 3732 return $authors[$name]; 3733 } 3734 3735 $email = fetch('email', 'txp_users', 'name', $name); 3736 $authors[$name] = $email; 3737 3738 return $email; 3739 } 3740 3741 /** 3742 * Checks if a database table contains items just from one user. 3743 * 3744 * @param string $table The database table 3745 * @param string $col The column 3746 * @return bool 3747 * @package User 3748 * @example 3749 * if (has_single_author('textpattern', 'AuthorID')) 3750 * { 3751 * echo "'textpattern' table has only content from one author."; 3752 * } 3753 */ 3754 3755 function has_single_author($table, $col = 'author') 3756 { 3757 static $cache = array(); 3758 3759 if (!isset($cache[$table][$col])) { 3760 $cache[$table][$col] = (safe_field("COUNT(name)", 'txp_users', "1 = 1") <= 1) && 3761 (safe_field("COUNT(DISTINCT(".doSlash($col)."))", doSlash($table), "1 = 1") <= 1); 3762 } 3763 3764 return $cache[$table][$col]; 3765 } 3766 3767 /** 3768 * Validates a string as a username. 3769 * 3770 * @param string $name The username 3771 * @return bool TRUE if the string valid 3772 * @since 4.6.0 3773 * @package User 3774 * @example 3775 * if (is_valid_username('john')) 3776 * { 3777 * echo "'john' is a valid username."; 3778 * } 3779 */ 3780 3781 function is_valid_username($name) 3782 { 3783 if (function_exists('mb_strlen')) { 3784 $length = mb_strlen($name, '8bit'); 3785 } else { 3786 $length = strlen($name); 3787 } 3788 3789 return $name && !preg_match('/^\s|[,\'"<>]|\s$/u', $name) && $length <= 64; 3790 } 3791 3792 /** 3793 * Assigns assets to a different user. 3794 * 3795 * Changes the owner of user's assets. It will move articles, files, images 3796 * and links from one user to another. 3797 * 3798 * Should be run when a user's permissions are taken away, a username is 3799 * renamed or the user is removed from the site. 3800 * 3801 * Affected database tables can be extended with a 'user.assign_assets > columns' 3802 * callback event. Callback functions get passed three arguments: '$event', 3803 * '$step' and '$columns'. The third parameter contains a reference to an 3804 * array of 'table => column' pairs. 3805 * 3806 * On a successful run, will trigger a 'user.assign_assets > done' callback event. 3807 * 3808 * @param string|array $owner List of current owners 3809 * @param string $new_owner The new owner 3810 * @return bool FALSE on error 3811 * @since 4.6.0 3812 * @package User 3813 * @example 3814 * if (assign_user_assets(array('user1', 'user2'), 'new_owner')) 3815 * { 3816 * echo "Assigned assets by 'user1' and 'user2' to 'new_owner'."; 3817 * } 3818 */ 3819 3820 function assign_user_assets($owner, $new_owner) 3821 { 3822 static $columns = null; 3823 3824 if (!$owner || !user_exists($new_owner)) { 3825 return false; 3826 } 3827 3828 if ($columns === null) { 3829 $columns = array( 3830 'textpattern' => 'AuthorID', 3831 'txp_file' => 'author', 3832 'txp_image' => 'author', 3833 'txp_link' => 'author', 3834 ); 3835 3836 callback_event_ref('user.assign_assets', 'columns', 0, $columns); 3837 } 3838 3839 $names = join(',', quote_list((array) $owner)); 3840 $assign = doSlash($new_owner); 3841 3842 foreach ($columns as $table => $column) { 3843 if (safe_update($table, "$column = '$assign'", "$column IN ($names)") === false) { 3844 return false; 3845 } 3846 } 3847 3848 callback_event('user.assign_assets', 'done', 0, compact('owner', 'new_owner', 'columns')); 3849 3850 return true; 3851 } 3852 3853 /** 3854 * Return private preferences required to be set for the given (new) user. 3855 * 3856 * The returned structure comprises a nested array. Each row is an 3857 * array, with key being the pref event, and array made up of: 3858 * -> pref type 3859 * -> position 3860 * -> html control 3861 * -> name (gTxt) 3862 * -> value 3863 * -> user name 3864 * 3865 * @param string $user_name The user name against which to assign the prefs 3866 * @since 4.6.0 3867 * @package Pref 3868 */ 3869 3870 function new_user_prefs($user_name) 3871 { 3872 return array( 3873 'publish' => array( 3874 array(PREF_CORE, 15, 'defaultPublishStatus', 'default_publish_status', STATUS_LIVE, $user_name), 3875 ), 3876 ); 3877 } 3878 3879 /** 3880 * Creates a user account. 3881 * 3882 * On a successful run, will trigger a 'user.create > done' callback event. 3883 * 3884 * @param string $name The login name 3885 * @param string $email The email address 3886 * @param string $password The password 3887 * @param string $realname The real name 3888 * @param int $group The user group 3889 * @return bool FALSE on error 3890 * @since 4.6.0 3891 * @package User 3892 * @example 3893 * if (create_user('john', 'john.doe@example.com', 'DancingWalrus', 'John Doe', 1)) 3894 * { 3895 * echo "User 'john' created."; 3896 * } 3897 */ 3898 3899 function create_user($name, $email, $password, $realname = '', $group = 0) 3900 { 3901 $levels = get_groups(); 3902 3903 if (!$password || !is_valid_username($name) || !is_valid_email($email) || user_exists($name) || !isset($levels[$group])) { 3904 return false; 3905 } 3906 3907 $nonce = md5(uniqid(mt_rand(), true)); 3908 $hash = Txp::get('\Textpattern\Password\Hash')->hash($password); 3909 3910 if ( 3911 safe_insert( 3912 'txp_users', 3913 "name = '".doSlash($name)."', 3914 email = '".doSlash($email)."', 3915 pass = '".doSlash($hash)."', 3916 nonce = '".doSlash($nonce)."', 3917 privs = ".intval($group).", 3918 RealName = '".doSlash($realname)."'" 3919 ) === false 3920 ) { 3921 return false; 3922 } 3923 3924 $privatePrefs = new_user_prefs($name); 3925 3926 foreach ($privatePrefs as $event => $event_prefs) { 3927 foreach ($event_prefs as $p) { 3928 create_pref($p[3], $p[4], $event, $p[0], $p[2], $p[1], $p[5]); 3929 } 3930 } 3931 3932 callback_event('user.create', 'done', 0, compact('name', 'email', 'password', 'realname', 'group', 'nonce', 'hash')); 3933 3934 return true; 3935 } 3936 3937 /** 3938 * Updates a user. 3939 * 3940 * Updates a user account's properties. The $user argument is used for 3941 * selecting the updated user, and rest of the arguments new values. 3942 * Use NULL to omit an argument. 3943 * 3944 * On a successful run, will trigger a 'user.update > done' callback event. 3945 * 3946 * @param string $user The updated user 3947 * @param string|null $email The email address 3948 * @param string|null $realname The real name 3949 * @param array|null $meta Additional meta fields 3950 * @return bool FALSE on error 3951 * @since 4.6.0 3952 * @package User 3953 * @example 3954 * if (update_user('login', null, 'John Doe')) 3955 * { 3956 * echo "Updated user's real name."; 3957 * } 3958 */ 3959 3960 function update_user($user, $email = null, $realname = null, $meta = array()) 3961 { 3962 if (($email !== null && !is_valid_email($email)) || !user_exists($user)) { 3963 return false; 3964 } 3965 3966 $meta = (array) $meta; 3967 $meta['RealName'] = $realname; 3968 $meta['email'] = $email; 3969 $set = array(); 3970 3971 foreach ($meta as $name => $value) { 3972 if ($value !== null) { 3973 $set[] = $name." = '".doSlash($value)."'"; 3974 } 3975 } 3976 3977 if ( 3978 safe_update( 3979 'txp_users', 3980 join(',', $set), 3981 "name = '".doSlash($user)."'" 3982 ) === false 3983 ) { 3984 return false; 3985 } 3986 3987 callback_event('user.update', 'done', 0, compact('user', 'email', 'realname', 'meta')); 3988 3989 return true; 3990 } 3991 3992 /** 3993 * Changes a user's password. 3994 * 3995 * On a successful run, will trigger a 'user.password_change > done' callback event. 3996 * 3997 * @param string $user The updated user 3998 * @param string $password The new password 3999 * @return bool FALSE on error 4000 * @since 4.6.0 4001 * @package User 4002 * @example 4003 * if (change_user_password('login', 'WalrusWasDancing')) 4004 * { 4005 * echo "Password changed."; 4006 * } 4007 */ 4008 4009 function change_user_password($user, $password) 4010 { 4011 if (!$user || !$password) { 4012 return false; 4013 } 4014 4015 $hash = Txp::get('\Textpattern\Password\Hash')->hash($password); 4016 4017 if ( 4018 safe_update( 4019 'txp_users', 4020 "pass = '".doSlash($hash)."'", 4021 "name = '".doSlash($user)."'" 4022 ) === false 4023 ) { 4024 return false; 4025 } 4026 4027 callback_event('user.password_change', 'done', 0, compact('user', 'password', 'hash')); 4028 4029 return true; 4030 } 4031 4032 /** 4033 * Removes a user. 4034 * 4035 * The user's assets are assigned to the given new owner. 4036 * 4037 * On a successful run, will trigger a 'user.remove > done' callback event. 4038 * 4039 * @param string|array $user List of removed users 4040 * @param string $new_owner Assign assets to 4041 * @return bool FALSE on error 4042 * @since 4.6.0 4043 * @package User 4044 * @example 4045 * if (remove_user('user', 'new_owner')) 4046 * { 4047 * echo "Removed 'user' and assigned assets to 'new_owner'."; 4048 * } 4049 */ 4050 4051 function remove_user($user, $new_owner) 4052 { 4053 if (!$user || !$new_owner) { 4054 return false; 4055 } 4056 4057 $names = join(',', quote_list((array) $user)); 4058 4059 if (assign_user_assets($user, $new_owner) === false) { 4060 return false; 4061 } 4062 4063 if (safe_delete('txp_prefs', "user_name IN ($names)") === false) { 4064 return false; 4065 } 4066 4067 if (safe_delete('txp_users', "name IN ($names)") === false) { 4068 return false; 4069 } 4070 4071 callback_event('user.remove', 'done', 0, compact('user', 'new_owner')); 4072 4073 return true; 4074 } 4075 4076 /** 4077 * Renames a user. 4078 * 4079 * On a successful run, will trigger a 'user.rename > done' callback event. 4080 * 4081 * @param string $user Updated user 4082 * @param string $newname The new name 4083 * @return bool FALSE on error 4084 * @since 4.6.0 4085 * @package User 4086 * @example 4087 * if (rename_user('login', 'newname')) 4088 * { 4089 * echo "'login' renamed to 'newname'."; 4090 * } 4091 */ 4092 4093 function rename_user($user, $newname) 4094 { 4095 if (!is_scalar($user) || !is_valid_username($newname)) { 4096 return false; 4097 } 4098 4099 if (assign_user_assets($user, $newname) === false) { 4100 return false; 4101 } 4102 4103 if ( 4104 safe_update( 4105 'txp_users', 4106 "name = '".doSlash($newname)."'", 4107 "name = '".doSlash($user)."'" 4108 ) === false 4109 ) { 4110 return false; 4111 } 4112 4113 callback_event('user.rename', 'done', 0, compact('user', 'newname')); 4114 4115 return true; 4116 } 4117 4118 /** 4119 * Checks if a user exists. 4120 * 4121 * @param string $user The user 4122 * @return bool TRUE if the user exists 4123 * @since 4.6.0 4124 * @package User 4125 * @example 4126 * if (user_exists('john')) 4127 * { 4128 * echo "'john' exists."; 4129 * } 4130 */ 4131 4132 function user_exists($user) 4133 { 4134 return (bool) safe_row("name", 'txp_users', "name = '".doSlash($user)."'"); 4135 } 4136 4137 /** 4138 * Changes a user's group. 4139 * 4140 * On a successful run, will trigger a 'user.change_group > done' callback event. 4141 * 4142 * @param string|array $user Updated users 4143 * @param int $group The new group 4144 * @return bool FALSE on error 4145 * @since 4.6.0 4146 * @package User 4147 * @example 4148 * if (change_user_group('john', 1)) 4149 * { 4150 * echo "'john' is now publisher."; 4151 * } 4152 */ 4153 4154 function change_user_group($user, $group) 4155 { 4156 $levels = get_groups(); 4157 4158 if (!$user || !isset($levels[$group])) { 4159 return false; 4160 } 4161 4162 $names = join(',', quote_list((array) $user)); 4163 4164 if ( 4165 safe_update( 4166 'txp_users', 4167 "privs = ".intval($group), 4168 "name IN ($names)" 4169 ) === false 4170 ) { 4171 return false; 4172 } 4173 4174 callback_event('user.change_group', 'done', 0, compact('user', 'group')); 4175 4176 return true; 4177 } 4178 4179 /** 4180 * Validates the given user credentials. 4181 * 4182 * Validates a given login and a password combination. If the combination is 4183 * correct, the user's login name is returned, FALSE otherwise. 4184 * 4185 * If $log is TRUE, also checks that the user has permissions to access the 4186 * admin side interface. On success, updates the user's last access timestamp. 4187 * 4188 * @param string $user The login 4189 * @param string $password The password 4190 * @param bool $log If TRUE, requires privilege level greater than 'none' 4191 * @return string|bool The user's login name or FALSE on error 4192 * @package User 4193 */ 4194 4195 function txp_validate($user, $password, $log = true) 4196 { 4197 global $DB; 4198 4199 $safe_user = doSlash($user); 4200 $name = false; 4201 4202 $r = safe_row("name, pass, privs", 'txp_users', "name = '$safe_user'"); 4203 4204 if (!$r) { 4205 return false; 4206 } 4207 4208 // Check post-4.3-style passwords. 4209 if (Txp::get('\Textpattern\Password\Hash')->verify($password, $r['pass'])) { 4210 if (!$log || $r['privs'] > 0) { 4211 $name = $r['name']; 4212 } 4213 } else { 4214 // No good password: check 4.3-style passwords. 4215 $passwords = array(); 4216 $passwords[] = "PASSWORD(LOWER('".doSlash($password)."'))"; 4217 $passwords[] = "PASSWORD('".doSlash($password)."')"; 4218 4219 $name = safe_field("name", 'txp_users', 4220 "name = '$safe_user' AND (pass = ".join(" OR pass = ", $passwords).") AND privs > 0"); 4221 4222 // Old password is good: migrate password to phpass. 4223 if ($name !== false) { 4224 safe_update('txp_users', "pass = '".doSlash(Txp::get('\Textpattern\Password\Hash')->hash($password))."'", "name = '$safe_user'"); 4225 } 4226 } 4227 4228 if ($name !== false && $log) { 4229 // Update the last access time. 4230 safe_update('txp_users', "last_access = NOW()", "name = '$safe_user'"); 4231 } 4232 4233 return $name; 4234 } 4235 4236 /** 4237 * Calculates a password hash. 4238 * 4239 * @param string $password The password 4240 * @return string A hash 4241 * @see PASSWORD_COMPLEXITY 4242 * @see PASSWORD_PORTABILITY 4243 * @package User 4244 */ 4245 4246 function txp_hash_password($password) 4247 { 4248 static $phpass = null; 4249 4250 if (!$phpass) { 4251 include_once txpath.'/lib/PasswordHash.php'; 4252 $phpass = new PasswordHash(PASSWORD_COMPLEXITY, PASSWORD_PORTABILITY); 4253 } 4254 4255 return $phpass->HashPassword($password); 4256 } 4257 4258 /** 4259 * Create a secure token hash in the database from the passed information. 4260 * 4261 * @param int $ref Reference to the user's account (user_id) 4262 * @param string $type Flavour of token to create 4263 * @param int $expiryTimestamp UNIX timestamp of when the token will expire 4264 * @param string $pass Password, used as part of the token generation 4265 * @param string $nonce Random nonce associated with the user's account 4266 * @return string Secure token suitable for emailing as part of a link 4267 * @since 4.6.1 4268 */ 4269 4270 function generate_user_token($ref, $type, $expiryTimestamp, $pass, $nonce) 4271 { 4272 $ref = assert_int($ref); 4273 $expiry = strftime('%Y-%m-%d %H:%M:%S', $expiryTimestamp); 4274 4275 // The selector becomes an indirect reference to the user row id, 4276 // and thus does not leak information when publicly displayed. 4277 $selector = Txp::get('\Textpattern\Password\Random')->generate(12); 4278 4279 // Use a hash of the nonce, selector and password. 4280 // This ensures that requests expire automatically when: 4281 // a) The person logs in, or 4282 // b) They successfully set/change their password 4283 // Using the selector in the hash just injects randomness, otherwise two requests 4284 // back-to-back would generate the same code. 4285 // Old requests for the same user id are purged when password is set. 4286 $token = bin2hex(pack('H*', substr(hash(HASHING_ALGORITHM, $nonce . $selector . $pass), 0, SALT_LENGTH))); 4287 $user_token = $token.$selector; 4288 4289 // Remove any previous activation tokens and insert the new one. 4290 $safe_type = doSlash($type); 4291 safe_delete("txp_token", "reference_id = $ref AND type = '$safe_type'"); 4292 safe_insert("txp_token", 4293 "reference_id = $ref, 4294 type = '$safe_type', 4295 selector = '".doSlash($selector)."', 4296 token = '".doSlash($token)."', 4297 expires = '".doSlash($expiry)."' 4298 "); 4299 4300 return $user_token; 4301 } 4302 4303 /** 4304 * Extracts a statement from a if/else condition. 4305 * 4306 * @param string $thing Statement in Textpattern tag markup presentation 4307 * @param bool $condition TRUE to return if statement, FALSE to else 4308 * @return string Either if or else statement 4309 * @deprecated in 4.6.0 4310 * @see parse_else 4311 * @package TagParser 4312 * @example 4313 * echo parse(EvalElse('true <txp:else /> false', 1 === 1)); 4314 */ 4315 4316 function EvalElse($thing, $condition) 4317 { 4318 global $txp_parsed, $txp_else; 4319 4320 if (strpos($thing, ':else') === false || empty($txp_parsed[$hash = sha1($thing)])) { 4321 return $condition ? $thing : ''; 4322 } 4323 4324 $tag = $txp_parsed[$hash]; 4325 list($first, $last) = $txp_else[$hash]; 4326 4327 if ($condition) { 4328 $last = $first - 2; 4329 $first = 1; 4330 } elseif ($first <= $last) { 4331 $first += 2; 4332 } else { 4333 return ''; 4334 } 4335 4336 for ($out = $tag[$first - 1]; $first <= $last; $first++) { 4337 $out .= $tag[$first][0] . $tag[$first][3] . $tag[$first][4] . $tag[++$first]; 4338 } 4339 4340 return $out; 4341 } 4342 4343 /** 4344 * Gets a form template's contents. 4345 * 4346 * The form template's reading method can be modified by registering a handler 4347 * to a 'form.fetch' callback event. Any value returned by the callback function 4348 * will be used as the form template markup. 4349 * 4350 * @param string $name The form 4351 * @return string 4352 * @package TagParser 4353 */ 4354 4355 function fetch_form($name) 4356 { 4357 global $production_status, $trace; 4358 4359 static $forms = array(); 4360 4361 $name = (string) $name; 4362 4363 if (!isset($forms[$name])) { 4364 if (has_handler('form.fetch')) { 4365 $form = callback_event('form.fetch', '', false, compact('name')); 4366 } else { 4367 $form = safe_field("Form", 'txp_form', "name = '".doSlash($name)."'"); 4368 } 4369 4370 if ($form === false) { 4371 trigger_error(gTxt('form_not_found').': '.$name); 4372 4373 return ''; 4374 } 4375 4376 $forms[$name] = $form; 4377 } 4378 4379 if ($production_status === 'debug') { 4380 $trace->log("[Form: '$name']"); 4381 } 4382 4383 return $forms[$name]; 4384 } 4385 4386 /** 4387 * Parses a form template. 4388 * 4389 * @param string $name The form 4390 * @return string The parsed contents 4391 * @package TagParser 4392 */ 4393 4394 function parse_form($name) 4395 { 4396 global $production_status, $txp_current_form, $trace; 4397 static $stack = array(); 4398 4399 $out = ''; 4400 $name = (string) $name; 4401 $f = fetch_form($name); 4402 4403 if ($f) { 4404 if (in_array($name, $stack, true)) { 4405 trigger_error(gTxt('form_circular_reference', array('{name}' => $name))); 4406 4407 return ''; 4408 } 4409 4410 $old_form = $txp_current_form; 4411 $txp_current_form = $stack[] = $name; 4412 if ($production_status === 'debug') { 4413 $trace->log("[Nesting forms: '".join("' / '", $stack)."']"); 4414 } 4415 $out = parse($f); 4416 $txp_current_form = $old_form; 4417 array_pop($stack); 4418 } 4419 4420 return $out; 4421 } 4422 4423 /** 4424 * Gets a page template's contents. 4425 * 4426 * The page template's reading method can be modified by registering a handler 4427 * to a 'page.fetch' callback event. Any value returned by the callback function 4428 * will be used as the template markup. 4429 * 4430 * @param string $name The template 4431 * @return string|bool The page template, or FALSE on error 4432 * @package TagParser 4433 * @since 4.6.0 4434 * @example 4435 * echo fetch_page('default'); 4436 */ 4437 4438 function fetch_page($name) 4439 { 4440 global $trace; 4441 4442 if (has_handler('page.fetch')) { 4443 $page = callback_event('page.fetch', '', false, compact('name')); 4444 } else { 4445 $page = safe_field("user_html", 'txp_page', "name = '".doSlash($name)."'"); 4446 } 4447 4448 if ($page === false) { 4449 return false; 4450 } 4451 4452 $trace->log("[Page: '$name']"); 4453 4454 return $page; 4455 } 4456 4457 /** 4458 * Parses a page template. 4459 * 4460 * @param string $name The template 4461 * @return string|bool The parsed page template, or FALSE on error 4462 * @since 4.6.0 4463 * @package TagParser 4464 * @example 4465 * echo parse_page('default'); 4466 */ 4467 4468 function parse_page($name) 4469 { 4470 global $pretext, $trace; 4471 4472 $page = fetch_page($name); 4473 4474 if ($page !== false) { 4475 $pretext['secondpass'] = false; 4476 $page = parse($page); 4477 $pretext['secondpass'] = true; 4478 $trace->log('[ ~~~ secondpass ~~~ ]'); 4479 $page = parse($page); 4480 } 4481 4482 return $page; 4483 } 4484 4485 /** 4486 * Gets a category's title. 4487 * 4488 * @param string $name The category 4489 * @param string $type Category's type. Either "article", "file", "image" or "link" 4490 * @return string|bool The title or FALSE on error 4491 */ 4492 4493 function fetch_category_title($name, $type = 'article') 4494 { 4495 static $cattitles = array(); 4496 global $thiscategory; 4497 4498 if (isset($cattitles[$type][$name])) { 4499 return $cattitles[$type][$name]; 4500 } 4501 4502 if (!empty($thiscategory['title']) && $thiscategory['name'] == $name && $thiscategory['type'] == $type) { 4503 $cattitles[$type][$name] = $thiscategory['title']; 4504 4505 return $thiscategory['title']; 4506 } 4507 4508 $f = safe_field("title", 'txp_category', "name = '".doSlash($name)."' AND type = '".doSlash($type)."'"); 4509 $cattitles[$type][$name] = $f; 4510 4511 return $f; 4512 } 4513 4514 /** 4515 * Gets a section's title. 4516 * 4517 * @param string $name The section 4518 * @return string|bool The title or FALSE on error 4519 */ 4520 4521 function fetch_section_title($name) 4522 { 4523 static $sectitles = array(); 4524 global $thissection; 4525 4526 // Try cache. 4527 if (isset($sectitles[$name])) { 4528 return $sectitles[$name]; 4529 } 4530 4531 // Try global set by section_list(). 4532 if (!empty($thissection['title']) && $thissection['name'] == $name) { 4533 $sectitles[$name] = $thissection['title']; 4534 4535 return $thissection['title']; 4536 } 4537 4538 if ($name == 'default' or empty($name)) { 4539 return ''; 4540 } 4541 4542 $f = safe_field("title", 'txp_section', "name = '".doSlash($name)."'"); 4543 $sectitles[$name] = $f; 4544 4545 return $f; 4546 } 4547 4548 /** 4549 * Updates an article's comment count. 4550 * 4551 * @param int $id The article 4552 * @return bool 4553 * @package Comment 4554 */ 4555 4556 function update_comments_count($id) 4557 { 4558 $id = assert_int($id); 4559 $thecount = safe_field("COUNT(*)", 'txp_discuss', "parentid = ".$id." AND visible = ".VISIBLE); 4560 $thecount = assert_int($thecount); 4561 $updated = safe_update('textpattern', "comments_count = ".$thecount, "ID = ".$id); 4562 4563 return ($updated) ? true : false; 4564 } 4565 4566 /** 4567 * Recalculates and updates comment counts. 4568 * 4569 * @param array $parentids List of articles to update 4570 * @package Comment 4571 */ 4572 4573 function clean_comment_counts($parentids) 4574 { 4575 $parentids = array_map('assert_int', $parentids); 4576 $rs = safe_rows_start("parentid, COUNT(*) AS thecount", 'txp_discuss', "parentid IN (".implode(',', $parentids).") AND visible = ".VISIBLE." GROUP BY parentid"); 4577 4578 if (!$rs) { 4579 return; 4580 } 4581 4582 $updated = array(); 4583 4584 while ($a = nextRow($rs)) { 4585 safe_update('textpattern', "comments_count = ".$a['thecount'], "ID = ".$a['parentid']); 4586 $updated[] = $a['parentid']; 4587 } 4588 4589 // We still need to update all those, that have zero comments left. 4590 $leftover = array_diff($parentids, $updated); 4591 4592 if ($leftover) { 4593 safe_update('textpattern', "comments_count = 0", "ID IN (".implode(',', $leftover).")"); 4594 } 4595 } 4596 4597 /** 4598 * Parses and formats comment message using Textile. 4599 * 4600 * @param string $msg The comment message 4601 * @return string HTML markup 4602 * @package Comment 4603 */ 4604 4605 function markup_comment($msg) 4606 { 4607 $textile = new \Textpattern\Textile\Parser(); 4608 4609 return $textile->textileRestricted($msg); 4610 } 4611 4612 /** 4613 * Updates site's last modification date. 4614 * 4615 * When this action is performed, it will trigger a 4616 * 'site.update > {event}' callback event and pass 4617 * any record set that triggered the update, along 4618 * with the exact time the update was triggered. 4619 * 4620 * @param $trigger Textpattern event or step that triggered the update 4621 * @param $rs Record set data at the time of update 4622 * @package Pref 4623 * @example 4624 * update_lastmod(); 4625 */ 4626 4627 function update_lastmod($trigger = '', $rs = array()) 4628 { 4629 $whenStamp = time(); 4630 $whenDate = strftime('%Y-%m-%d %H:%M:%S', $whenStamp); 4631 4632 safe_upsert('txp_prefs', "val = '$whenDate'", "name = 'lastmod'"); 4633 callback_event('site.update', $trigger, 0, $rs, compact('whenStamp', 'whenDate')); 4634 } 4635 4636 /** 4637 * Gets the site's last modification date. 4638 * 4639 * @param int $unix_ts UNIX timestamp 4640 * @return int UNIX timestamp 4641 * @package Pref 4642 */ 4643 4644 function get_lastmod($unix_ts = null) 4645 { 4646 if ($unix_ts === null) { 4647 $unix_ts = @strtotime(get_pref('lastmod')); 4648 } 4649 4650 // Check for future articles that are now visible. 4651 if ($max_article = safe_field("UNIX_TIMESTAMP(Posted)", 'textpattern', "Posted <= ".now('posted')." AND Status >= 4 ORDER BY Posted DESC LIMIT 1")) { 4652 $unix_ts = max($unix_ts, $max_article); 4653 } 4654 4655 return $unix_ts; 4656 } 4657 4658 /** 4659 * Sends and handles a lastmod header. 4660 * 4661 * @param int|null $unix_ts The last modification date as a UNIX timestamp 4662 * @param bool $exit If TRUE, terminates the script 4663 * @return array|null Array of sent HTTP status and the lastmod header, or NULL 4664 * @package Pref 4665 */ 4666 4667 function handle_lastmod($unix_ts = null, $exit = true) 4668 { 4669 if (get_pref('send_lastmod') && get_pref('production_status') == 'live') { 4670 $unix_ts = get_lastmod($unix_ts); 4671 4672 // Make sure lastmod isn't in the future. 4673 $unix_ts = min($unix_ts, time()); 4674 4675 // Or too far in the past (7 days). 4676 $unix_ts = max($unix_ts, time() - 3600 * 24 * 7); 4677 4678 $last = safe_strftime('rfc822', $unix_ts, 1); 4679 header("Last-Modified: $last"); 4680 header('Cache-Control: no-cache'); 4681 4682 $hims = serverSet('HTTP_IF_MODIFIED_SINCE'); 4683 4684 if ($hims and @strtotime($hims) >= $unix_ts) { 4685 log_hit('304'); 4686 4687 if (!$exit) { 4688 return array('304', $last); 4689 } 4690 4691 txp_status_header('304 Not Modified'); 4692 4693 header('Content-Length: 0'); 4694 4695 // Discard all output. 4696 while (@ob_end_clean()); 4697 exit; 4698 } 4699 4700 if (!$exit) { 4701 return array('200', $last); 4702 } 4703 } 4704 } 4705 4706 /** 4707 * Gets preferences as an array. 4708 * 4709 * Returns preference values from the database as an array. Shouldn't be used to 4710 * retrieve selected preferences, see get_pref() instead. 4711 * 4712 * By default only the global preferences are returned. 4713 * If the optional user name parameter is supplied, the private preferences 4714 * for that user are returned. 4715 * 4716 * @param string $user User name. 4717 * @return array 4718 * @package Pref 4719 * @access private 4720 * @see get_pref() 4721 */ 4722 4723 function get_prefs($user = '') 4724 { 4725 $out = array(); 4726 $r = safe_rows_start("name, val", 'txp_prefs', "prefs_id = 1 AND user_name = '".doSlash($user)."'"); 4727 4728 if ($r) { 4729 while ($a = nextRow($r)) { 4730 $out[$a['name']] = $a['val']; 4731 } 4732 } 4733 4734 return $out; 4735 } 4736 4737 /** 4738 * Creates or updates a preference. 4739 * 4740 * @param string $name The name 4741 * @param string $val The value 4742 * @param string $event The section the preference appears in 4743 * @param int $type Either PREF_CORE, PREF_PLUGIN, PREF_HIDDEN 4744 * @param string $html The HTML control type the field uses. Can take a custom function name 4745 * @param int $position Used to sort the field on the Preferences panel 4746 * @param bool $is_private If PREF_PRIVATE, is created as a user pref 4747 * @return bool FALSE on error 4748 * @package Pref 4749 * @example 4750 * if (set_pref('myPref', 'value')) 4751 * { 4752 * echo "'myPref' created or updated."; 4753 * } 4754 */ 4755 4756 function set_pref($name, $val, $event = 'publish', $type = PREF_CORE, $html = 'text_input', $position = 0, $is_private = PREF_GLOBAL) 4757 { 4758 $user_name = null; 4759 4760 if ($is_private == PREF_PRIVATE) { 4761 $user_name = PREF_PRIVATE; 4762 } 4763 4764 if (pref_exists($name, $user_name)) { 4765 return update_pref($name, (string) $val, null, null, null, null, $user_name); 4766 } 4767 4768 return create_pref($name, $val, $event, $type, $html, $position, $user_name); 4769 } 4770 4771 /** 4772 * Gets a preference string. 4773 * 4774 * Prefers global system-wide preferences over a user's private preferences. 4775 * 4776 * @param string $thing The named variable 4777 * @param mixed $default Used as a replacement if named pref isn't found 4778 * @param bool $from_db If TRUE checks database opposed $prefs variable in memory 4779 * @return string Preference value or $default 4780 * @package Pref 4781 * @example 4782 * if (get_pref('enable_xmlrpc_server')) 4783 * { 4784 * echo "XML-RPC server is enabled."; 4785 * } 4786 */ 4787 4788 function get_pref($thing, $default = '', $from_db = false) 4789 { 4790 global $prefs, $txp_user; 4791 4792 if ($from_db) { 4793 $name = doSlash($thing); 4794 $user_name = doSlash($txp_user); 4795 4796 $field = safe_field( 4797 "val", 4798 'txp_prefs', 4799 "name = '$name' AND (user_name = '' OR user_name = '$user_name') ORDER BY user_name LIMIT 1" 4800 ); 4801 4802 if ($field !== false) { 4803 $prefs[$thing] = $field; 4804 } 4805 } 4806 4807 if (isset($prefs[$thing])) { 4808 return $prefs[$thing]; 4809 } 4810 4811 return $default; 4812 } 4813 4814 /** 4815 * Removes a preference string. 4816 * 4817 * Removes preference strings based on the given arguments. Use NULL to omit an argument. 4818 * 4819 * @param string|null $name The preference string name 4820 * @param string|null $event The preference event 4821 * @param string|null|bool $user_name The owner. If PREF_PRIVATE, the current user 4822 * @return bool TRUE on success 4823 * @since 4.6.0 4824 * @package Pref 4825 * @example 4826 * if (remove_pref(null, 'myEvent')) 4827 * { 4828 * echo "Removed all preferences from 'myEvent'."; 4829 * } 4830 */ 4831 4832 function remove_pref($name = null, $event = null, $user_name = null) 4833 { 4834 global $txp_user; 4835 4836 $sql = array(); 4837 4838 if ($user_name === PREF_PRIVATE) { 4839 if (!$txp_user) { 4840 return false; 4841 } 4842 4843 $user_name = $txp_user; 4844 } 4845 4846 if ($user_name !== null) { 4847 $sql[] = "user_name = '".doSlash((string) $user_name)."'"; 4848 } 4849 4850 if ($event !== null) { 4851 $sql[] = "event = '".doSlash($event)."'"; 4852 } 4853 4854 if ($name !== null) { 4855 $sql[] = "name = '".doSlash($name)."'"; 4856 } 4857 4858 if ($sql) { 4859 return safe_delete('txp_prefs', join(" AND ", $sql)); 4860 } 4861 4862 return false; 4863 } 4864 4865 /** 4866 * Checks if a preference string exists. 4867 * 4868 * Searches for matching preference strings based on the given arguments. 4869 * 4870 * The $user_name argument can be used to limit the search to a specifc user, 4871 * or to global and private strings. If NULL, matches are searched from both 4872 * private and global strings. 4873 * 4874 * @param string $name The preference string name 4875 * @param string|null|bool $user_name Either the username, NULL, PREF_PRIVATE or PREF_GLOBAL 4876 * @return bool TRUE if the string exists, or FALSE on error 4877 * @since 4.6.0 4878 * @package Pref 4879 * @example 4880 * if (pref_exists('myPref')) 4881 * { 4882 * echo "'myPref' exists."; 4883 * } 4884 */ 4885 4886 function pref_exists($name, $user_name = null) 4887 { 4888 global $txp_user; 4889 4890 $sql = array(); 4891 $sql[] = "name = '".doSlash($name)."'"; 4892 4893 if ($user_name === PREF_PRIVATE) { 4894 if (!$txp_user) { 4895 return false; 4896 } 4897 4898 $user_name = $txp_user; 4899 } 4900 4901 if ($user_name !== null) { 4902 $sql[] = "user_name = '".doSlash((string) $user_name)."'"; 4903 } 4904 4905 if (safe_row("name", 'txp_prefs', join(" AND ", $sql))) { 4906 return true; 4907 } 4908 4909 return false; 4910 } 4911 4912 /** 4913 * Creates a preference string. 4914 * 4915 * When a string is created, will trigger a 'preference.create > done' callback event. 4916 * 4917 * @param string $name The name 4918 * @param string $val The value 4919 * @param string $event The section the preference appears in 4920 * @param int $type Either PREF_CORE, PREF_PLUGIN, PREF_HIDDEN 4921 * @param string $html The HTML control type the field uses. Can take a custom function name 4922 * @param int $position Used to sort the field on the Preferences panel 4923 * @param string|bool $user_name The user name, PREF_GLOBAL or PREF_PRIVATE 4924 * @return bool TRUE if the string exists, FALSE on error 4925 * @since 4.6.0 4926 * @package Pref 4927 * @example 4928 * if (create_pref('myPref', 'value', 'site', PREF_PLUGIN, 'text_input', 25)) 4929 * { 4930 * echo "'myPref' created."; 4931 * } 4932 */ 4933 4934 function create_pref($name, $val, $event = 'publish', $type = PREF_CORE, $html = 'text_input', $position = 0, $user_name = PREF_GLOBAL) 4935 { 4936 global $txp_user; 4937 4938 if ($user_name === PREF_PRIVATE) { 4939 if (!$txp_user) { 4940 return false; 4941 } 4942 4943 $user_name = $txp_user; 4944 } 4945 4946 if (pref_exists($name, $user_name)) { 4947 return true; 4948 } 4949 4950 if ( 4951 safe_insert( 4952 'txp_prefs', 4953 "prefs_id = 1, 4954 name = '".doSlash($name)."', 4955 val = '".doSlash($val)."', 4956 event = '".doSlash($event)."', 4957 html = '".doSlash($html)."', 4958 type = ".intval($type).", 4959 position = ".intval($position).", 4960 user_name = '".doSlash((string) $user_name)."'" 4961 ) === false 4962 ) { 4963 return false; 4964 } 4965 4966 callback_event('preference.create', 'done', 0, compact('name', 'val', 'event', 'type', 'html', 'position', 'user_name')); 4967 4968 return true; 4969 } 4970 4971 /** 4972 * Updates a preference string. 4973 * 4974 * Updates a preference string's properties. The $name and $user_name 4975 * arguments are used for selecting the updated string, and rest of the 4976 * arguments take the new values. Use NULL to omit an argument. 4977 * 4978 * When a string is updated, will trigger a 'preference.update > done' callback event. 4979 * 4980 * @param string $name The update preference string's name 4981 * @param string|null $val The value 4982 * @param string|null $event The section the preference appears in 4983 * @param int|null $type Either PREF_CORE, PREF_PLUGIN, PREF_HIDDEN 4984 * @param string|null $html The HTML control type the field uses. Can take a custom function name 4985 * @param int|null $position Used to sort the field on the Preferences panel 4986 * @param string|bool|null $user_name The updated string's owner, PREF_GLOBAL or PREF_PRIVATE 4987 * @return bool FALSE on error 4988 * @since 4.6.0 4989 * @package Pref 4990 * @example 4991 * if (update_pref('myPref', 'New value.')) 4992 * { 4993 * echo "Updated 'myPref' value."; 4994 * } 4995 */ 4996 4997 function update_pref($name, $val = null, $event = null, $type = null, $html = null, $position = null, $user_name = PREF_GLOBAL) 4998 { 4999 global $txp_user; 5000 5001 $where = $set = array(); 5002 $where[] = "name = '".doSlash($name)."'"; 5003 5004 if ($user_name === PREF_PRIVATE) { 5005 if (!$txp_user) { 5006 return false; 5007 } 5008 5009 $user_name = $txp_user; 5010 } 5011 5012 if ($user_name !== null) { 5013 $where[] = "user_name = '".doSlash((string) $user_name)."'"; 5014 } 5015 5016 foreach (array('val', 'event', 'type', 'html', 'position') as $field) { 5017 if ($$field !== null) { 5018 $set[] = $field." = '".doSlash($$field)."'"; 5019 } 5020 } 5021 5022 if ($set && safe_update('txp_prefs', join(', ', $set), join(" AND ", $where))) { 5023 callback_event('preference.update', 'done', 0, compact('name', 'val', 'event', 'type', 'html', 'position', 'user_name')); 5024 5025 return true; 5026 } 5027 5028 return false; 5029 } 5030 5031 /** 5032 * Renames a preference string. 5033 * 5034 * When a string is renamed, will trigger a 'preference.rename > done' callback event. 5035 * 5036 * @param string $newname The new name 5037 * @param string $name The current name 5038 * @param string $user_name Either the username, PREF_GLOBAL or PREF_PRIVATE 5039 * @return bool FALSE on error 5040 * @since 4.6.0 5041 * @package Pref 5042 * @example 5043 * if (rename_pref('mynewPref', 'myPref')) 5044 * { 5045 * echo "Renamed 'myPref' to 'mynewPref'."; 5046 * } 5047 */ 5048 5049 function rename_pref($newname, $name, $user_name = null) 5050 { 5051 global $txp_user; 5052 5053 $where = array(); 5054 $where[] = "name = '".doSlash($name)."'"; 5055 5056 if ($user_name === PREF_PRIVATE) { 5057 if (!$txp_user) { 5058 return false; 5059 } 5060 5061 $user_name = $txp_user; 5062 } 5063 5064 if ($user_name !== null) { 5065 $where[] = "user_name = '".doSlash((string) $user_name)."'"; 5066 } 5067 5068 if (safe_update('txp_prefs', "name = '".doSlash($newname)."'", join(" AND ", $where))) { 5069 callback_event('preference.rename', 'done', 0, compact('newname', 'name', 'user_name')); 5070 5071 return true; 5072 } 5073 5074 return false; 5075 } 5076 5077 /** 5078 * Gets a list of custom fields. 5079 * 5080 * @return array 5081 * @package CustomField 5082 */ 5083 5084 function getCustomFields() 5085 { 5086 global $prefs; 5087 static $out = null; 5088 5089 // Have cache? 5090 if (!is_array($out)) { 5091 $cfs = preg_grep('/^custom_\d+_set/', array_keys($prefs)); 5092 $out = array(); 5093 5094 foreach ($cfs as $name) { 5095 preg_match('/(\d+)/', $name, $match); 5096 5097 if (!empty($prefs[$name])) { 5098 $out[$match[1]] = strtolower($prefs[$name]); 5099 } 5100 } 5101 } 5102 5103 return $out; 5104 } 5105 5106 /** 5107 * Build a query qualifier to filter non-matching custom fields from the 5108 * result set. 5109 * 5110 * @param array $custom An array of 'custom_field_name' => field_number tupels 5111 * @param array $pairs Filter criteria: An array of 'name' => value tupels 5112 * @return bool|string An SQL qualifier for a query's 'WHERE' part 5113 * @package CustomField 5114 */ 5115 5116 function buildCustomSql($custom, $pairs) 5117 { 5118 if ($pairs) { 5119 $pairs = doSlash($pairs); 5120 5121 foreach ($pairs as $k => $v) { 5122 if (in_array($k, $custom)) { 5123 $no = array_keys($custom, $k); 5124 $out[] = "and custom_".$no[0]." like '$v'"; 5125 } 5126 } 5127 } 5128 5129 return !empty($out) ? ' '.join(' ', $out).' ' : false; 5130 } 5131 5132 /** 5133 * Sends a HTTP status header. 5134 * 5135 * @param string $status The HTTP status code 5136 * @package Network 5137 * @example 5138 * txp_status_header('403 Forbidden'); 5139 */ 5140 5141 function txp_status_header($status = '200 OK') 5142 { 5143 if (IS_FASTCGI) { 5144 header("Status: $status"); 5145 } elseif (serverSet('SERVER_PROTOCOL') == 'HTTP/1.0') { 5146 header("HTTP/1.0 $status"); 5147 } else { 5148 header("HTTP/1.1 $status"); 5149 } 5150 } 5151 5152 /** 5153 * Terminates normal page rendition and outputs an error page. 5154 * 5155 * @param string|array $msg The error message 5156 * @param string $status HTTP status code 5157 * @param string $url Redirects to the specified URL. Can be used with $status of 301, 302 and 307 5158 * @package Tag 5159 */ 5160 5161 function txp_die($msg, $status = '503', $url = '') 5162 { 5163 global $connected, $txp_error_message, $txp_error_status, $txp_error_code; 5164 5165 // Make it possible to call this function as a tag, e.g. in an article 5166 // <txp:txp_die status="410" />. 5167 if (is_array($msg)) { 5168 extract(lAtts(array( 5169 'msg' => '', 5170 'status' => '503', 5171 'url' => '', 5172 ), $msg)); 5173 } 5174 5175 // Intentionally incomplete - just the ones we're likely to use. 5176 $codes = array( 5177 '200' => 'OK', 5178 '301' => 'Moved Permanently', 5179 '302' => 'Found', 5180 '303' => 'See Other', 5181 '304' => 'Not Modified', 5182 '307' => 'Temporary Redirect', 5183 '401' => 'Unauthorized', 5184 '403' => 'Forbidden', 5185 '404' => 'Not Found', 5186 '410' => 'Gone', 5187 '414' => 'Request-URI Too Long', 5188 '500' => 'Internal Server Error', 5189 '501' => 'Not Implemented', 5190 '503' => 'Service Unavailable', 5191 ); 5192 5193 if ($status) { 5194 if (isset($codes[strval($status)])) { 5195 $status = strval($status).' '.$codes[$status]; 5196 } 5197 5198 txp_status_header($status); 5199 } 5200 5201 $code = (int) $status; 5202 5203 callback_event('txp_die', $code, 0, $url); 5204 5205 // Redirect with status. 5206 if ($url && in_array($code, array(301, 302, 303, 307))) { 5207 ob_end_clean(); 5208 header("Location: $url", true, $code); 5209 die('<html><head><meta http-equiv="refresh" content="0;URL='.txpspecialchars($url).'"></head><body></body></html>'); 5210 } 5211 5212 if ($connected && @txpinterface == 'public') { 5213 $out = safe_field("user_html", 'txp_page', "name = 'error_".doSlash($code)."'"); 5214 5215 if ($out === false) { 5216 $out = safe_field("user_html", 'txp_page', "name = 'error_default'"); 5217 } 5218 } else { 5219 $out = <<<eod 5220 <!DOCTYPE html> 5221 <html lang="en"> 5222 <head> 5223 <meta charset="utf-8"> 5224 <title>Textpattern Error: <txp:error_status /></title> 5225 </head> 5226 <body> 5227 <p><txp:error_message /></p> 5228 </body> 5229 </html> 5230 eod; 5231 } 5232 5233 header("Content-type: text/html; charset=utf-8"); 5234 5235 if (is_callable('parse')) { 5236 $txp_error_message = $msg; 5237 $txp_error_status = $status; 5238 $txp_error_code = $code; 5239 set_error_handler("tagErrorHandler"); 5240 die(parse($out)); 5241 } else { 5242 $out = preg_replace( 5243 array('@<txp:error_status[^>]*/>@', '@<txp:error_message[^>]*/>@'), 5244 array($status, $msg), 5245 $out 5246 ); 5247 die($out); 5248 } 5249 } 5250 5251 /** 5252 * Gets a URL-encoded and HTML entity-escaped query string for a URL. 5253 * 5254 * Builds a HTTP query string from an associative array. 5255 * 5256 * @param array $q The parameters for the query 5257 * @return string The query, including starting "?". 5258 * @package URL 5259 * @example 5260 * echo join_qs(array('param1' => 'value1', 'param2' => 'value2')); 5261 */ 5262 5263 function join_qs($q) 5264 { 5265 $qs = array(); 5266 5267 foreach ($q as $k => $v) { 5268 if (is_array($v)) { 5269 $v = join(',', $v); 5270 } 5271 5272 if ($k && (string) $v !== '') { 5273 $qs[$k] = urlencode($k).'='.urlencode($v); 5274 } 5275 } 5276 5277 $str = join('&', $qs); 5278 5279 return ($str ? '?'.$str : ''); 5280 } 5281 5282 /** 5283 * Builds a HTML attribute list from an array. 5284 * 5285 * Takes an array of raw HTML attributes, and returns a properly 5286 * sanitised HTML attribute string for use in a HTML tag. 5287 * 5288 * Internally handles HTML boolean attributes, array lists and query strings. 5289 * If an attributes value is set as a boolean, the attribute is considered 5290 * as one too. If a value is NULL, it's omitted and the attribute is added 5291 * without a value. An array value is converted to a space-separated list, 5292 * or for 'href' and 'src' to a URL encoded query string. 5293 * 5294 * @param array|string $atts HTML attributes 5295 * @param int $flags TEXTPATTERN_STRIP_EMPTY_STRING 5296 * @return string HTML attribute list 5297 * @since 4.6.0 5298 * @package HTML 5299 * @example 5300 * echo join_atts(array('class' => 'myClass', 'disabled' => true)); 5301 */ 5302 5303 function join_atts($atts, $flags = TEXTPATTERN_STRIP_EMPTY_STRING) 5304 { 5305 if (!is_array($atts)) { 5306 return $atts ? ' '.trim($atts) : ''; 5307 } 5308 5309 $list = array(); 5310 5311 foreach ($atts as $name => $value) { 5312 if (($flags & TEXTPATTERN_STRIP_EMPTY && !$value) || ($value === false)) { 5313 continue; 5314 } elseif ($value === null) { 5315 $list[] = $name; 5316 continue; 5317 } elseif (is_array($value)) { 5318 if ($name == 'href' || $name == 'src') { 5319 $value = join_qs($value); 5320 } else { 5321 $value = txpspecialchars(join(' ', $value)); 5322 } 5323 } else { 5324 $value = txpspecialchars($value === true ? $name : $value); 5325 } 5326 5327 if (!($flags & TEXTPATTERN_STRIP_EMPTY_STRING && $value === '')) { 5328 $list[] = $name.'="'.$value.'"'; 5329 } 5330 } 5331 5332 return $list ? ' '.join(' ', $list) : ''; 5333 } 5334 5335 /** 5336 * Builds a page URL from an array of parameters. 5337 * 5338 * The $inherit can be used to add parameters to an existing url, e.g: 5339 * pagelinkurl(array('pg' => 2), $pretext). 5340 * 5341 * Cannot be used to link to an article. See permlinkurl() and permlinkurl_id() instead. 5342 * 5343 * @param array $parts The parts used to construct the URL 5344 * @param array $inherit Can be used to add parameters to an existing url 5345 * @return string 5346 * @see permlinkurl() 5347 * @see permlinkurl_id() 5348 * @package URL 5349 */ 5350 5351 function pagelinkurl($parts, $inherit = array()) 5352 { 5353 global $permlink_mode, $prefs; 5354 5355 $keys = array_merge($inherit, $parts); 5356 5357 if (isset($prefs['custom_url_func']) 5358 and is_callable($prefs['custom_url_func']) 5359 and ($url = call_user_func($prefs['custom_url_func'], $keys, PAGELINKURL)) !== false) { 5360 return $url; 5361 } 5362 5363 // Can't use this to link to an article. 5364 if (isset($keys['id'])) { 5365 unset($keys['id']); 5366 } 5367 5368 if (isset($keys['s']) && $keys['s'] == 'default') { 5369 unset($keys['s']); 5370 } 5371 5372 // 'article' context is implicit, no need to add it to the page URL. 5373 if (isset($keys['context']) && $keys['context'] == 'article') { 5374 unset($keys['context']); 5375 } 5376 5377 if ($permlink_mode == 'messy') { 5378 if (!empty($keys['context'])) { 5379 $keys['context'] = gTxt($keys['context'].'_context'); 5380 } 5381 5382 return hu.'index.php'.join_qs($keys); 5383 } else { 5384 // All clean URL modes use the same schemes for list pages. 5385 $url = ''; 5386 5387 if (!empty($keys['rss'])) { 5388 $url = hu.'rss/'; 5389 unset($keys['rss']); 5390 5391 return $url.join_qs($keys); 5392 } elseif (!empty($keys['atom'])) { 5393 $url = hu.'atom/'; 5394 unset($keys['atom']); 5395 5396 return $url.join_qs($keys); 5397 } elseif (!empty($keys['s'])) { 5398 if (!empty($keys['context'])) { 5399 $keys['context'] = gTxt($keys['context'].'_context'); 5400 } 5401 $url = hu.urlencode($keys['s']).'/'; 5402 unset($keys['s']); 5403 5404 return $url.join_qs($keys); 5405 } elseif (!empty($keys['author'])) { 5406 $ct = empty($keys['context']) ? '' : strtolower(urlencode(gTxt($keys['context'].'_context'))).'/'; 5407 $url = hu.strtolower(urlencode(gTxt('author'))).'/'.$ct.urlencode($keys['author']).'/'; 5408 unset($keys['author'], $keys['context']); 5409 5410 return $url.join_qs($keys); 5411 } elseif (!empty($keys['c'])) { 5412 $ct = empty($keys['context']) ? '' : strtolower(urlencode(gTxt($keys['context'].'_context'))).'/'; 5413 $url = hu.strtolower(urlencode(gTxt('category'))).'/'.$ct.urlencode($keys['c']).'/'; 5414 unset($keys['c'], $keys['context']); 5415 5416 return $url.join_qs($keys); 5417 } 5418 5419 return hu.join_qs($keys); 5420 } 5421 } 5422 5423 /** 5424 * Gets a URL for the given article. 5425 * 5426 * If you need to generate a list of article URLs from already fetched table 5427 * rows, consider using permlinkurl() over this due to performance benefits. 5428 * 5429 * @param int $id The article ID 5430 * @return string The URL 5431 * @see permlinkurl() 5432 * @package URL 5433 * @example 5434 * echo permlinkurl_id(12); 5435 */ 5436 5437 function permlinkurl_id($id) 5438 { 5439 global $permlinks; 5440 5441 $id = (int) $id; 5442 5443 if (isset($permlinks[$id])) { 5444 return $permlinks[$id]; 5445 } 5446 5447 $rs = safe_row( 5448 "ID AS thisid, Section AS section, Title AS title, url_title, UNIX_TIMESTAMP(Posted) AS posted, UNIX_TIMESTAMP(Expires) AS expires", 5449 'textpattern', 5450 "ID = $id" 5451 ); 5452 5453 return permlinkurl($rs); 5454 } 5455 5456 /** 5457 * Generates an article URL from the given data array. 5458 * 5459 * @param array $article_array An array consisting of keys 'thisid', 'section', 'title', 'url_title', 'posted', 'expires' 5460 * @return string The URL 5461 * @package URL 5462 * @see permlinkurl_id() 5463 * @example 5464 * echo permlinkurl_id(array( 5465 * 'thisid' => 12, 5466 * 'section' => 'blog', 5467 * 'url_title' => 'my-title', 5468 * 'posted' => 1345414041, 5469 * 'expires' => 1345444077 5470 * )); 5471 */ 5472 5473 function permlinkurl($article_array) 5474 { 5475 global $permlink_mode, $prefs, $permlinks, $production_status; 5476 5477 if (!$article_array || !is_array($article_array)) { 5478 return; 5479 } 5480 5481 if (isset($prefs['custom_url_func']) 5482 and is_callable($prefs['custom_url_func']) 5483 and ($url = call_user_func($prefs['custom_url_func'], $article_array, PERMLINKURL)) !== false) { 5484 return $url; 5485 } 5486 5487 extract(lAtts(array( 5488 'thisid' => null, 5489 'id' => null, 5490 'title' => null, 5491 'url_title' => null, 5492 'section' => null, 5493 'posted' => null, 5494 'expires' => null, 5495 ), array_change_key_case($article_array, CASE_LOWER), false)); 5496 5497 if (empty($thisid)) { 5498 $thisid = $id; 5499 } 5500 5501 $thisid = (int) $thisid; 5502 5503 if (isset($permlinks[$thisid])) { 5504 return $permlinks[$thisid]; 5505 } 5506 5507 if (empty($prefs['publish_expired_articles']) && 5508 !empty($expires) && 5509 $expires < time() && 5510 $production_status != 'live' && 5511 txpinterface == 'public' 5512 ) { 5513 trigger_error(gTxt('permlink_to_expired_article', array('{id}' => $thisid)), E_USER_NOTICE); 5514 } 5515 5516 if (empty($url_title)) { 5517 $url_title = stripSpace($title); 5518 } 5519 5520 $section = urlencode($section); 5521 $url_title = urlencode($url_title); 5522 5523 switch ($permlink_mode) { 5524 case 'section_id_title': 5525 if ($prefs['attach_titles_to_permalinks']) { 5526 $out = hu."$section/$thisid/$url_title"; 5527 } else { 5528 $out = hu."$section/$thisid/"; 5529 } 5530 break; 5531 case 'year_month_day_title': 5532 list($y, $m, $d) = explode("-", date("Y-m-d", $posted)); 5533 $out = hu."$y/$m/$d/$url_title"; 5534 break; 5535 case 'id_title': 5536 if ($prefs['attach_titles_to_permalinks']) { 5537 $out = hu."$thisid/$url_title"; 5538 } else { 5539 $out = hu."$thisid/"; 5540 } 5541 break; 5542 case 'section_title': 5543 $out = hu."$section/$url_title"; 5544 break; 5545 case 'title_only': 5546 $out = hu."$url_title"; 5547 break; 5548 case 'messy': 5549 $out = hu."index.php?id=$thisid"; 5550 break; 5551 } 5552 5553 return $permlinks[$thisid] = $out; 5554 } 5555 5556 /** 5557 * Gets a file download URL. 5558 * 5559 * @param int $id The ID 5560 * @param string $filename The filename 5561 * @return string 5562 * @package File 5563 */ 5564 5565 function filedownloadurl($id, $filename = '') 5566 { 5567 global $permlink_mode; 5568 5569 if ($permlink_mode == 'messy') { 5570 return hu.'index.php'.join_qs(array( 5571 's' => 'file_download', 5572 'id' => (int) $id, 5573 )); 5574 } 5575 5576 if ($filename) { 5577 $filename = '/'.urlencode($filename); 5578 5579 // FIXME: work around yet another mod_deflate problem (double compression) 5580 // http://blogs.msdn.com/wndp/archive/2006/08/21/Content-Encoding-not-equal-Content-Type.aspx 5581 if (preg_match('/gz$/i', $filename)) { 5582 $filename .= a; 5583 } 5584 } 5585 5586 return hu.urlencode(gTxt('file_download')).'/'.intval($id).$filename; 5587 } 5588 5589 /** 5590 * Gets an image's absolute URL. 5591 * 5592 * @param int $id The image 5593 * @param string $ext The file extension 5594 * @param bool $thumbnail If TRUE returns a URL to the thumbnail 5595 * @return string 5596 * @package Image 5597 */ 5598 5599 function imagesrcurl($id, $ext, $thumbnail = false) 5600 { 5601 global $img_dir; 5602 $thumbnail = $thumbnail ? 't' : ''; 5603 5604 return ihu.$img_dir.'/'.$id.$thumbnail.$ext; 5605 } 5606 5607 /** 5608 * Checks if a value exists in a list. 5609 * 5610 * @param string $val The searched value 5611 * @param string $list The value list 5612 * @param string $delim The list boundary 5613 * @return bool Returns TRUE if $val is found, FALSE otherwise 5614 * @example 5615 * if (in_list('red', 'blue, green, red, yellow')) 5616 * { 5617 * echo "'red' found from the list."; 5618 * } 5619 */ 5620 5621 function in_list($val, $list, $delim = ',') 5622 { 5623 $args = do_list($list, $delim); 5624 5625 return in_array((string) $val, $args, true); 5626 } 5627 5628 /** 5629 * Split a string by string. 5630 * 5631 * Trims the created values of whitespace. 5632 * 5633 * @param string $list The string 5634 * @param string $delim The boundary 5635 * @return array 5636 * @example 5637 * print_r( 5638 * do_list('value1, value2, value3') 5639 * ); 5640 */ 5641 5642 function do_list($list, $delim = ',') 5643 { 5644 return array_map('trim', explode($delim, $list)); 5645 } 5646 5647 /** 5648 * Split a string by string, returning only unique results. 5649 * 5650 * Trims unique values of whitespace. Flags permit exclusion of empty strings. 5651 * 5652 * @param string $list The string 5653 * @param string $delim The boundary 5654 * @param int $flags TEXTPATTERN_STRIP_NONE | TEXTPATTERN_STRIP_EMPTY | TEXTPATTERN_STRIP_EMPTY_STRING 5655 * @return array 5656 * @example 5657 * print_r( 5658 * do_list_unique('value1, value2, value3') 5659 * ); 5660 */ 5661 5662 function do_list_unique($list, $delim = ',', $flags = TEXTPATTERN_STRIP_EMPTY_STRING) 5663 { 5664 $out = array_unique(array_map('trim', explode($delim, $list))); 5665 5666 if ($flags & TEXTPATTERN_STRIP_EMPTY) { 5667 $out = array_filter($out); 5668 } 5669 5670 if ($flags & TEXTPATTERN_STRIP_EMPTY_STRING) { 5671 $out = array_filter($out, function ($v) {return ($v=='') ? false : true;}); 5672 } 5673 5674 return $out; 5675 } 5676 5677 /** 5678 * Wraps a string in single quotes. 5679 * 5680 * @param string $val The input string 5681 * @return string 5682 */ 5683 5684 function doQuote($val) 5685 { 5686 return "'$val'"; 5687 } 5688 5689 /** 5690 * Escapes special characters for use in an SQL statement and wraps the value 5691 * in quote. 5692 * 5693 * Useful for creating an array of values for use in an SQL statement. 5694 * 5695 * @param string|array $in The input value 5696 * @return mixed 5697 * @package DB 5698 * @example 5699 * if ($r = safe_row('name', 'myTable', 'type in(' . quote_list(array('value1', 'value2')) . ')') 5700 * { 5701 * echo "Found '{$r['name']}'."; 5702 * } 5703 */ 5704 5705 function quote_list($in) 5706 { 5707 $out = doSlash($in); 5708 5709 return doArray($out, 'doQuote'); 5710 } 5711 5712 /** 5713 * Adds a line to the tag trace. 5714 * 5715 * @param string $msg The message 5716 * @param int $tracelevel_diff Change trace level 5717 * @deprecated in 4.6.0 5718 * @package Debug 5719 */ 5720 5721 function trace_add($msg, $level = 0, $dummy = null) 5722 { 5723 global $trace; 5724 5725 if ((int) $level > 0) { 5726 $trace->start($msg); 5727 } elseif ((int) $level < 0) { 5728 $trace->stop(); 5729 } else { 5730 $trace->log($msg); 5731 } 5732 5733 // Uncomment this to trigger deprecated warning in a version (or two). 5734 // Due to the radical changes under the hood, plugin authors will probably 5735 // support dual 4.5/4.6 plugins for the short term. Deprecating this 5736 // immediately causes unnecessary pain for developers. 5737 // trigger_error(gTxt('deprecated_function_with', array('{name}' => __FUNCTION__, '{with}' => 'class Trace')), E_USER_NOTICE); 5738 } 5739 5740 /** 5741 * Push current article on the end of data stack. 5742 * 5743 * Populates $stack_article global with the current $thisarticle. 5744 */ 5745 5746 function article_push() 5747 { 5748 global $thisarticle, $stack_article; 5749 $stack_article[] = $thisarticle; 5750 } 5751 5752 /** 5753 * Advance to the next article in the current data stack. 5754 * 5755 * Populates $thisarticle global with the last article from the 5756 * stack stored in $stack_article. 5757 */ 5758 5759 function article_pop() 5760 { 5761 global $thisarticle, $stack_article; 5762 $thisarticle = array_pop($stack_article); 5763 } 5764 5765 /** 5766 * Gets a path relative to the site's root directory. 5767 * 5768 * @param string $path The filename to parse 5769 * @param string $pfx The root directory 5770 * @return string The absolute $path converted to relative 5771 * @package File 5772 */ 5773 5774 function relative_path($path, $pfx = null) 5775 { 5776 if ($pfx === null) { 5777 $pfx = dirname(txpath); 5778 } 5779 5780 return preg_replace('@^/'.preg_quote(ltrim($pfx, '/'), '@').'/?@', '', $path); 5781 } 5782 5783 /** 5784 * Gets a backtrace. 5785 * 5786 * @param int $num The limit 5787 * @param int $start The offset 5788 * @return array A backtrace 5789 * @package Debug 5790 */ 5791 5792 function get_caller($num = 1, $start = 2) 5793 { 5794 $out = array(); 5795 5796 if (!is_callable('debug_backtrace')) { 5797 return $out; 5798 } 5799 5800 $bt = debug_backtrace(); 5801 5802 for ($i = $start; $i < $num+$start; $i++) { 5803 if (!empty($bt[$i])) { 5804 $t = ''; 5805 5806 if (!empty($bt[$i]['file'])) { 5807 $t .= relative_path($bt[$i]['file']); 5808 } 5809 5810 if (!empty($bt[$i]['line'])) { 5811 $t .= ':'.$bt[$i]['line']; 5812 } 5813 5814 if ($t) { 5815 $t .= ' '; 5816 } 5817 5818 if (!empty($bt[$i]['class'])) { 5819 $t .= $bt[$i]['class']; 5820 } 5821 5822 if (!empty($bt[$i]['type'])) { 5823 $t .= $bt[$i]['type']; 5824 } 5825 5826 if (!empty($bt[$i]['function'])) { 5827 $t .= $bt[$i]['function']; 5828 $t .= '()'; 5829 } 5830 5831 $out[] = $t; 5832 } 5833 } 5834 5835 return $out; 5836 } 5837 5838 /** 5839 * Sets a locale. 5840 * 5841 * The function name is misleading but remains for legacy reasons. 5842 * 5843 * @param string $lang 5844 * @return string Current locale 5845 * @package L10n 5846 * @deprecated in 4.6.0 5847 * @see \Textpattern\L10n\Locale::setLocale() 5848 */ 5849 5850 function getlocale($lang) 5851 { 5852 global $locale; 5853 5854 try { 5855 Txp::get('\Textpattern\L10n\Locale')->setLocale(LC_TIME, array($lang, $locale)); 5856 } catch (Exception $e) { 5857 } 5858 5859 return Txp::get('\Textpattern\L10n\Locale')->getLocale(LC_TIME); 5860 } 5861 5862 /** 5863 * Fetch meta description from the given (or automatic) context. 5864 * 5865 * Category context may be refined by specifying the content type as well 5866 * after a dot. e.g. category.image to check image context category. 5867 * 5868 * @param string $type Flavour of meta content to fetch (section, category, article) 5869 */ 5870 5871 function getMetaDescription($type = null) 5872 { 5873 global $thisarticle, $thiscategory, $thissection, $c, $s, $context; 5874 5875 $content = ''; 5876 5877 if ($type === null) { 5878 if ($thiscategory) { 5879 $content = $thiscategory['description']; 5880 } elseif ($thissection) { 5881 $content = $thissection['description']; 5882 } elseif ($thisarticle) { 5883 $content = $thisarticle['description']; 5884 } elseif ($c) { 5885 $content = safe_field("description", 'txp_category', "name = '".doSlash($c)."' AND type = '" . doSlash($context) . "'"); 5886 } elseif ($s) { 5887 $content = safe_field("description", 'txp_section', "name = '".doSlash($s)."'"); 5888 } 5889 } else { 5890 if (strpos($type, 'category') === 0) { 5891 // Category context. 5892 if ($thiscategory) { 5893 $content = $thiscategory['description']; 5894 } else { 5895 $thisContext = $context; 5896 $catParts = do_list($type, '.'); 5897 5898 if (isset($catParts[1])) { 5899 $thisContext = $catParts[1]; 5900 } 5901 5902 $clause = " AND type = '".$thisContext."'"; 5903 $content = safe_field("description", 'txp_category', "name = '".doSlash($c)."'" . $clause); 5904 } 5905 } elseif ($type === 'section') { 5906 $theSection = ($thissection) ? $thissection['name'] : $s; 5907 $content = safe_field("description", 'txp_section', "name = '".doSlash($theSection)."'"); 5908 } elseif ($type === 'article') { 5909 assert_article(); 5910 $content = ($thisarticle? $thisarticle['description'] : ''); 5911 } 5912 } 5913 5914 return $content; 5915 } 5916 5917 /** 5918 * Assert article context error. 5919 */ 5920 5921 function assert_article() 5922 { 5923 global $thisarticle; 5924 5925 if (empty($thisarticle)) { 5926 trigger_error(gTxt('error_article_context')); 5927 } 5928 } 5929 5930 /** 5931 * Assert comment context error. 5932 */ 5933 5934 function assert_comment() 5935 { 5936 global $thiscomment; 5937 5938 if (empty($thiscomment)) { 5939 trigger_error(gTxt('error_comment_context')); 5940 } 5941 } 5942 5943 /** 5944 * Assert file context error. 5945 */ 5946 5947 function assert_file() 5948 { 5949 global $thisfile; 5950 5951 if (empty($thisfile)) { 5952 trigger_error(gTxt('error_file_context')); 5953 } 5954 } 5955 5956 /** 5957 * Assert image context error. 5958 */ 5959 5960 function assert_image() 5961 { 5962 global $thisimage; 5963 5964 if (empty($thisimage)) { 5965 trigger_error(gTxt('error_image_context')); 5966 } 5967 } 5968 5969 /** 5970 * Assert link context error. 5971 */ 5972 5973 function assert_link() 5974 { 5975 global $thislink; 5976 5977 if (empty($thislink)) { 5978 trigger_error(gTxt('error_link_context')); 5979 } 5980 } 5981 5982 /** 5983 * Assert section context error. 5984 */ 5985 5986 function assert_section() 5987 { 5988 global $thissection; 5989 5990 if (empty($thissection)) { 5991 trigger_error(gTxt('error_section_context')); 5992 } 5993 } 5994 5995 /** 5996 * Assert category context error. 5997 */ 5998 5999 function assert_category() 6000 { 6001 global $thiscategory; 6002 6003 if (empty($thiscategory)) { 6004 trigger_error(gTxt('error_category_context')); 6005 } 6006 } 6007 6008 /** 6009 * Validate a variable as an integer. 6010 * 6011 * @param mixed $myvar The variable 6012 * @return int|bool The variable or FALSE on error 6013 */ 6014 6015 function assert_int($myvar) 6016 { 6017 if (is_numeric($myvar) and $myvar == intval($myvar)) { 6018 return (int) $myvar; 6019 } 6020 6021 trigger_error("'".txpspecialchars((string) $myvar)."' is not an integer", E_USER_ERROR); 6022 6023 return false; 6024 } 6025 6026 /** 6027 * Validate a variable as a string. 6028 * 6029 * @param mixed $myvar The variable 6030 * @return string|bool The variable or FALSE on error 6031 */ 6032 6033 function assert_string($myvar) 6034 { 6035 if (is_string($myvar)) { 6036 return $myvar; 6037 } 6038 6039 trigger_error("'".txpspecialchars((string) $myvar)."' is not a string", E_USER_ERROR); 6040 6041 return false; 6042 } 6043 6044 /** 6045 * Validate a variable as an array. 6046 * 6047 * @param mixed $myvar The variable 6048 * @return array|bool The variable or FALSE on error 6049 */ 6050 6051 function assert_array($myvar) 6052 { 6053 if (is_array($myvar)) { 6054 return $myvar; 6055 } 6056 6057 trigger_error("'".txpspecialchars((string) $myvar)."' is not an array", E_USER_ERROR); 6058 6059 return false; 6060 } 6061 6062 /** 6063 * Converts relative links in HTML markup to absolute. 6064 * 6065 * @param string $html The HTML to check 6066 * @param string $permalink Optional URL part appended to the links 6067 * @return string HTML 6068 * @package URL 6069 */ 6070 6071 function replace_relative_urls($html, $permalink = '') 6072 { 6073 global $siteurl; 6074 6075 // URLs like "/foo/bar" - relative to the domain. 6076 if (serverSet('HTTP_HOST')) { 6077 $html = preg_replace('@(<a[^>]+href=")/(?!/)@', '$1'.PROTOCOL.serverSet('HTTP_HOST').'/', $html); 6078 $html = preg_replace('@(<img[^>]+src=")/(?!/)@', '$1'.PROTOCOL.serverSet('HTTP_HOST').'/', $html); 6079 } 6080 6081 // "foo/bar" - relative to the textpattern root, 6082 // leave "http:", "mailto:" et al. as absolute URLs. 6083 $html = preg_replace('@(<a[^>]+href=")(?!\w+:|//)@', '$1'.PROTOCOL.$siteurl.'/$2', $html); 6084 $html = preg_replace('@(<img[^>]+src=")(?!\w+:|//)@', '$1'.PROTOCOL.$siteurl.'/$2', $html); 6085 6086 if ($permalink) { 6087 $html = preg_replace("/href=\\\"#(.*)\"/", "href=\"".$permalink."#\\1\"", $html); 6088 } 6089 6090 return ($html); 6091 } 6092 6093 /** 6094 * Used for clean URL test. 6095 * 6096 * @param array $pretext 6097 * @access private 6098 */ 6099 6100 function show_clean_test($pretext) 6101 { 6102 if (is_array($pretext) && isset($pretext['req'])) { 6103 echo md5($pretext['req']).n; 6104 } 6105 6106 if (serverSet('SERVER_ADDR') === serverSet('REMOTE_ADDR')) { 6107 var_export($pretext); 6108 } 6109 } 6110 6111 /** 6112 * Calculates paging. 6113 * 6114 * Takes a total number of items, a per page limit and the current page number, 6115 * and in return returns the page number, an offset and a number of pages. 6116 * 6117 * @param int $total The number of items in total 6118 * @param int $limit The number of items per page 6119 * @param int $page The page number 6120 * @return array Array of page, offset and number of pages. 6121 * @example 6122 * list($page, $offset, $num_pages) = pager(150, 10, 1); 6123 * echo "Page {$page} of {$num_pages}. Offset is {$offset}."; 6124 */ 6125 6126 function pager($total, $limit, $page) 6127 { 6128 $total = (int) $total; 6129 $limit = (int) $limit; 6130 $page = (int) $page; 6131 6132 $num_pages = ceil($total / $limit); 6133 6134 $page = min(max($page, 1), $num_pages); 6135 6136 $offset = max(($page - 1) * $limit, 0); 6137 6138 return array($page, $offset, $num_pages); 6139 } 6140 6141 /** 6142 * Word-wrap a string using a zero width space. 6143 * 6144 * @param string $text The input string 6145 * @param int $width Target line lenght 6146 * @param string $break Is not used 6147 * @return string 6148 */ 6149 6150 function soft_wrap($text, $width, $break = '​') 6151 { 6152 $wbr = chr(226).chr(128).chr(139); 6153 $words = explode(' ', $text); 6154 6155 foreach ($words as $wordnr => $word) { 6156 $word = preg_replace('|([,./\\>?!:;@-]+)(?=.)|', '$1 ', $word); 6157 $parts = explode(' ', $word); 6158 6159 foreach ($parts as $partnr => $part) { 6160 $len = strlen(utf8_decode($part)); 6161 6162 if (!$len) { 6163 continue; 6164 } 6165 6166 $parts[$partnr] = preg_replace('/(.{'.ceil($len/ceil($len/$width)).'})(?=.)/u', '$1'.$wbr, $part); 6167 } 6168 6169 $words[$wordnr] = join($wbr, $parts); 6170 } 6171 6172 return join(' ', $words); 6173 } 6174 6175 /** 6176 * Removes prefix from a string. 6177 * 6178 * @param string $str The string 6179 * @param string $pfx The prefix 6180 * @return string 6181 */ 6182 6183 function strip_prefix($str, $pfx) 6184 { 6185 return preg_replace('/^'.preg_quote($pfx, '/').'/', '', $str); 6186 } 6187 6188 /** 6189 * Sends an XML envelope. 6190 * 6191 * Wraps an array of name => value tupels into an XML envelope, supports one 6192 * level of nested arrays at most. 6193 * 6194 * @param array $response 6195 * @return string XML envelope 6196 * @package XML 6197 */ 6198 6199 function send_xml_response($response = array()) 6200 { 6201 static $headers_sent = false; 6202 6203 if (!$headers_sent) { 6204 ob_clean(); 6205 header('Content-Type: text/xml; charset=utf-8'); 6206 $out[] = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>'; 6207 $headers_sent = true; 6208 } 6209 6210 $default_response = array( 6211 'http-status' => '200 OK', 6212 ); 6213 6214 // Backfill default response properties. 6215 $response = $response + $default_response; 6216 6217 txp_status_header($response['http-status']); 6218 $out[] = '<textpattern>'; 6219 6220 foreach ($response as $element => $value) { 6221 if (is_array($value)) { 6222 $out[] = t."<$element>".n; 6223 6224 foreach ($value as $e => $v) { 6225 // Character escaping in values; 6226 // @see http://www.w3.org/TR/2000/WD-xml-c14n-20000119.html#charescaping. 6227 $v = str_replace(array("\t", "\n", "\r"), array("	", "
", "
"), htmlentities($v, ENT_QUOTES, 'UTF-8')); 6228 $out[] = t.t."<$e value='$v' />".n; 6229 } 6230 6231 $out[] = t."</$element>".n; 6232 } else { 6233 $value = str_replace(array("\t", "\n", "\r"), array("	", "
", "
"), htmlentities($value, ENT_QUOTES, 'UTF-8')); 6234 $out[] = t."<$element value='$value' />".n; 6235 } 6236 } 6237 6238 $out[] = '</textpattern>'; 6239 echo join(n, $out); 6240 } 6241 6242 /** 6243 * Sends a text/javascript response. 6244 * 6245 * @param string $out The JavaScript 6246 * @since 4.4.0 6247 * @package Ajax 6248 */ 6249 6250 function send_script_response($out = '') 6251 { 6252 static $headers_sent = false; 6253 6254 if (!$headers_sent) { 6255 ob_clean(); 6256 header('Content-Type: text/javascript; charset=utf-8'); 6257 txp_status_header('200 OK'); 6258 $headers_sent = true; 6259 } 6260 6261 echo ";\n".$out.";\n"; 6262 } 6263 6264 /** 6265 * Sends an application/json response. 6266 * 6267 * If the provided $out is not a string, its encoded as JSON. Any string is 6268 * treated as it were valid JSON. 6269 * 6270 * @param mixed $out The JSON 6271 * @since 4.6.0 6272 * @package Ajax 6273 */ 6274 6275 function send_json_response($out = '') 6276 { 6277 static $headers_sent = false; 6278 6279 if (!$headers_sent) { 6280 ob_clean(); 6281 header('Content-Type: application/json; charset=utf-8'); 6282 txp_status_header('200 OK'); 6283 $headers_sent = true; 6284 } 6285 6286 if (!is_string($out)) { 6287 $out = json_encode($out); 6288 } 6289 6290 echo $out; 6291 } 6292 6293 /** 6294 * Display a modal client message in response to an AJAX request and 6295 * halt execution. 6296 * 6297 * @param string|array $thing The $thing[0] is the message's text; $thing[1] is the message's type (one of E_ERROR or E_WARNING, anything else meaning "success"; not used) 6298 * @since 4.5.0 6299 * @package Ajax 6300 */ 6301 6302 function modal_halt($thing) 6303 { 6304 global $app_mode, $theme; 6305 6306 if ($app_mode == 'async') { 6307 send_script_response($theme->announce_async($thing, true)); 6308 die(); 6309 } 6310 } 6311 6312 /** 6313 * Sends an activity message to the client. 6314 * 6315 * @param string|array $message The message 6316 * @param int $type The type, either 0, E_ERROR, E_WARNING 6317 * @param int $flags Flags, consisting of TEXTPATTERN_ANNOUNCE_ADAPTIVE | TEXTPATTERN_ANNOUNCE_ASYNC | TEXTPATTERN_ANNOUNCE_MODAL | TEXTPATTERN_ANNOUNCE_REGULAR 6318 * @package Announce 6319 * @since 4.6.0 6320 * @example 6321 * echo announce('My message', E_WARNING); 6322 */ 6323 6324 function announce($message, $type = 0, $flags = TEXTPATTERN_ANNOUNCE_ADAPTIVE) 6325 { 6326 global $app_mode, $theme; 6327 6328 if (!is_array($message)) { 6329 $message = array($message, $type); 6330 } 6331 6332 if ($flags & TEXTPATTERN_ANNOUNCE_ASYNC || ($flags & TEXTPATTERN_ANNOUNCE_ADAPTIVE && $app_mode === 'async')) { 6333 return $theme->announce_async($message); 6334 } 6335 6336 if ($flags & TEXTPATTERN_ANNOUNCE_MODAL) { 6337 return $theme->announce_async($message, true); 6338 } 6339 6340 return $theme->announce($message); 6341 } 6342 6343 /** 6344 * Performs regular housekeeping. 6345 * 6346 * @access private 6347 */ 6348 6349 function janitor() 6350 { 6351 global $prefs, $auto_dst, $timezone_key, $is_dst; 6352 6353 // Update DST setting. 6354 if ($auto_dst && $timezone_key) { 6355 $is_dst = Txp::get('\Textpattern\Date\Timezone')->isDst(null, $timezone_key); 6356 6357 if ($is_dst != $prefs['is_dst']) { 6358 $prefs['is_dst'] = $is_dst; 6359 set_pref('is_dst', $is_dst, 'publish', 2); 6360 } 6361 } 6362 } 6363 6364 /** 6365 * Dealing with timezones. 6366 * 6367 * @package DateTime 6368 * @deprecated in 4.6.0 6369 */ 6370 6371 class timezone 6372 { 6373 /** 6374 * Render HTML <select> element for choosing a timezone. 6375 * 6376 * @param string $name Element name 6377 * @param string $value Selected timezone 6378 * @param bool $blank_first Add empty first option 6379 * @param bool|string $onchange 6380 * @param string $select_id HTML id attribute 6381 * @return string HTML markup 6382 * @deprecated in 4.6.0 6383 * @see \Textpattern\Date\Timezone::getTimeZones() 6384 */ 6385 6386 public function selectInput($name = '', $value = '', $blank_first = '', $onchange = '', $select_id = '') 6387 { 6388 if ($details = Txp::get('\Textpattern\Date\Timezone')->getTimeZones()) { 6389 $thiscontinent = ''; 6390 $selected = false; 6391 6392 foreach ($details as $timezone_id => $tz) { 6393 extract($tz); 6394 6395 if ($value == $timezone_id) { 6396 $selected = true; 6397 } 6398 6399 if ($continent !== $thiscontinent) { 6400 if ($thiscontinent !== '') { 6401 $out[] = n.'</optgroup>'; 6402 } 6403 6404 $out[] = n.'<optgroup label="'.gTxt($continent).'">'; 6405 $thiscontinent = $continent; 6406 } 6407 6408 $where = gTxt(str_replace('_', ' ', $city)) 6409 .(!empty($subcity) ? '/'.gTxt(str_replace('_', ' ', $subcity)) : '').t 6410 /*."($abbr)"*/; 6411 6412 $out[] = n.'<option value="'.txpspecialchars($timezone_id).'"'.($value == $timezone_id ? ' selected="selected"' : '').'>'.$where.'</option>'; 6413 } 6414 6415 $out[] = n.'</optgroup>'; 6416 6417 return n.'<select'.($select_id ? ' id="'.$select_id.'"' : '').' name="'.$name.'"'. 6418 ($onchange == 1 ? ' onchange="submit(this.form);"' : $onchange). 6419 '>'. 6420 ($blank_first ? n.'<option value=""'.($selected == false ? ' selected="selected"' : '').'> </option>' : ''). 6421 join('', $out). 6422 n.'</select>'; 6423 } 6424 6425 return ''; 6426 } 6427 6428 /** 6429 * Build a matrix of timezone details. 6430 * 6431 * @return array Array of timezone details indexed by timezone key 6432 * @deprecated in 4.6.0 6433 * @see \Textpattern\Date\Timezone::getTimeZones() 6434 */ 6435 6436 public function details() 6437 { 6438 return Txp::get('\Textpattern\Date\Timezone')->getTimeZones(); 6439 } 6440 6441 /** 6442 * Find a timezone key matching a given GMT offset. 6443 * 6444 * NB: More than one key might fit any given GMT offset, thus the returned 6445 * value is ambiguous and merely useful for presentation purposes. 6446 * 6447 * @param int $gmtoffset 6448 * @return string timezone key 6449 * @deprecated in 4.6.0 6450 * @see \Textpattern\Date\Timezone::getOffsetIdentifiers() 6451 */ 6452 6453 public function key($gmtoffset) 6454 { 6455 if ($idenfiers = Txp::get('\Textpattern\Date\Timezone')->getOffsetIdentifiers($gmtoffset)) { 6456 return $idenfiers[0]; 6457 } 6458 6459 return ''; 6460 } 6461 6462 /** 6463 * Is DST in effect? 6464 * 6465 * @param int $timestamp When? 6466 * @param string $timezone_key Where? 6467 * @return bool 6468 * @deprecated in 4.6.0 6469 * @see \Textpattern\Date\Timezone::isDst() 6470 */ 6471 6472 public static function is_dst($timestamp, $timezone_key) 6473 { 6474 return Txp::get('\Textpattern\Date\Timezone')->isDst($timestamp, $timezone_key); 6475 } 6476 6477 /** 6478 * Check for run-time timezone support. 6479 * 6480 * As of 4.6.0, always returns TRUE. 6481 * 6482 * @return bool Timezone feature is enabled 6483 * @deprecated in 4.6.0 6484 */ 6485 6486 public static function is_supported() 6487 { 6488 return true; 6489 } 6490 } 6491 6492 /** 6493 * Installs localisation strings from a Textpack. 6494 * 6495 * Created strings get a well-known static modifcation date set in the past. 6496 * This is done to avoid tampering with lastmod dates used for RPC server 6497 * interactions, caching and update checks. 6498 * 6499 * @param string $textpack The Textpack to install 6500 * @param bool $add_new_langs If TRUE, installs strings for any included language 6501 * @return int Number of installed strings 6502 * @package L10n 6503 */ 6504 6505 function install_textpack($textpack, $add_new_langs = false) 6506 { 6507 $parser = new \Textpattern\Textpack\Parser(); 6508 $parser->setLanguage(get_pref('language', 'en-gb')); 6509 $textpack = $parser->parse($textpack); 6510 6511 if (!$textpack) { 6512 return 0; 6513 } 6514 6515 $installed_langs = safe_column("lang", 'txp_lang', "1 = 1 GROUP BY lang"); 6516 $done = 0; 6517 6518 foreach ($textpack as $translation) { 6519 extract($translation); 6520 6521 if (!$add_new_langs && !in_array($lang, $installed_langs)) { 6522 continue; 6523 } 6524 6525 $where = "lang = '".doSlash($lang)."' AND name = '".doSlash($name)."'"; 6526 6527 if (safe_count('txp_lang', $where)) { 6528 $r = safe_update( 6529 'txp_lang', 6530 "lastmod = '2005-08-14', 6531 data = '".doSlash($data)."', 6532 event = '".doSlash($event)."', 6533 owner = '".doSlash($owner)."'", 6534 $where 6535 ); 6536 } else { 6537 $r = safe_insert( 6538 'txp_lang', 6539 "lastmod = '2005-08-14', 6540 data = '".doSlash($data)."', 6541 event = '".doSlash($event)."', 6542 owner = '".doSlash($owner)."', 6543 lang = '".doSlash($lang)."', 6544 name = '".doSlash($name)."'" 6545 ); 6546 } 6547 6548 if ($r) { 6549 $done++; 6550 } 6551 } 6552 6553 return $done; 6554 } 6555 6556 /** 6557 * Generate a ciphered token. 6558 * 6559 * The token is reproducible, unique among sites and users, expires later. 6560 * 6561 * @return string The token 6562 * @see bouncer() 6563 * @package CSRF 6564 */ 6565 6566 function form_token() 6567 { 6568 static $token; 6569 global $txp_user; 6570 6571 // Generate a ciphered token from the current user's nonce (thus valid for 6572 // login time plus 30 days) and a pinch of salt from the blog UID. 6573 if (empty($token)) { 6574 $nonce = safe_field("nonce", 'txp_users', "name = '".doSlash($txp_user)."'"); 6575 $token = md5($nonce.get_pref('blog_uid')); 6576 } 6577 6578 return $token; 6579 } 6580 6581 /** 6582 * Assert system requirements. 6583 * 6584 * @access private 6585 */ 6586 6587 function assert_system_requirements() 6588 { 6589 if (version_compare(REQUIRED_PHP_VERSION, PHP_VERSION) > 0) { 6590 txp_die('This server runs PHP version '.PHP_VERSION.'. Textpattern needs PHP version '.REQUIRED_PHP_VERSION.' or better.'); 6591 } 6592 } 6593 6594 /** 6595 * Validates admin steps and protects against CSRF attempts using tokens. 6596 * 6597 * Takes an admin step and validates it against an array of valid steps. 6598 * The valid steps array indicates the step's token based session riding 6599 * protection needs. 6600 * 6601 * If the step requires CSRF token protection, and the request doesn't come with 6602 * a valid token, the request is terminated, defeating any CSRF attempts. 6603 * 6604 * If the $step isn't in valid steps, it returns FALSE, but the request 6605 * isn't terminated. If the $step is valid and passes CSRF validation, 6606 * returns TRUE. 6607 * 6608 * @param string $step Requested admin step 6609 * @param array $steps An array of valid steps with flag indicating CSRF needs, e.g. array('savething' => true, 'listthings' => false) 6610 * @return bool If the $step is valid, proceeds and returns TRUE. Dies on CSRF attempt. 6611 * @see form_token() 6612 * @package CSRF 6613 * @example 6614 * global $step; 6615 * if (bouncer($step, array( 6616 * 'browse' => false, 6617 * 'edit' => false, 6618 * 'save' => true, 6619 * 'multi_edit' => true, 6620 * ))) 6621 * { 6622 * echo "The '{$step}' is valid."; 6623 * } 6624 */ 6625 6626 function bouncer($step, $steps) 6627 { 6628 global $event; 6629 6630 if (empty($step)) { 6631 return true; 6632 } 6633 6634 // Validate step. 6635 if (!array_key_exists($step, $steps)) { 6636 return false; 6637 } 6638 6639 // Does this step require a token? 6640 if (!$steps[$step]) { 6641 return true; 6642 } 6643 6644 // Validate token. 6645 if (gps('_txp_token') === form_token()) { 6646 return true; 6647 } 6648 6649 die(gTxt('get_off_my_lawn', array('{event}' => $event, '{step}' => $step))); 6650 } 6651 6652 /** 6653 * Test whether the client accepts a certain response format. 6654 * 6655 * Discards formats with a quality factor below 0.1 6656 * 6657 * @param string $format One of 'html', 'txt', 'js', 'css', 'json', 'xml', 'rdf', 'atom', 'rss' 6658 * @return boolean $format TRUE if accepted 6659 * @since 4.5.0 6660 * @package Network 6661 */ 6662 6663 function http_accept_format($format) 6664 { 6665 static $formats = array( 6666 'html' => array('text/html', 'application/xhtml+xml', '*/*'), 6667 'txt' => array('text/plain', '*/*'), 6668 'js' => array('application/javascript', 'application/x-javascript', 'text/javascript', 'application/ecmascript', 'application/x-ecmascript', '*/*'), 6669 'css' => array('text/css', '*/*'), 6670 'json' => array('application/json', 'application/x-json', '*/*'), 6671 'xml' => array('text/xml', 'application/xml', 'application/x-xml', '*/*'), 6672 'rdf' => array('application/rdf+xml', '*/*'), 6673 'atom' => array('application/atom+xml', '*/*'), 6674 'rss' => array('application/rss+xml', '*/*'), 6675 ); 6676 static $accepts = array(); 6677 static $q = array(); 6678 6679 if (empty($accepts)) { 6680 // Build cache of accepted formats. 6681 $accepts = preg_split('/\s*,\s*/', serverSet('HTTP_ACCEPT'), null, PREG_SPLIT_NO_EMPTY); 6682 6683 foreach ($accepts as $i => &$a) { 6684 // Sniff out quality factors if present. 6685 if (preg_match('/(.*)\s*;\s*q=([.0-9]*)/', $a, $m)) { 6686 $a = $m[1]; 6687 $q[$a] = floatval($m[2]); 6688 } else { 6689 $q[$a] = 1.0; 6690 } 6691 6692 // Discard formats with quality factors below an arbitrary threshold 6693 // as jQuery adds a wildcard '*/*; q=0.01' to the 'Accepts' header 6694 // for XHR requests. 6695 if ($q[$a] < 0.1) { 6696 unset($q[$a]); 6697 unset($accepts[$i]); 6698 } 6699 } 6700 } 6701 6702 return isset($formats[$format]) && count(array_intersect($formats[$format], $accepts)) > 0; 6703 } 6704 6705 /** 6706 * Return a list of status codes and their associated names. 6707 * 6708 * The list can be extended with a 'status.types > types' callback event. 6709 * Callback functions get passed three arguments: '$event', '$step' and 6710 * '$status_list'. The third parameter contains a reference to an array of 6711 * 'status_code => label' pairs. 6712 * 6713 * @param bool Return the list with L10n labels (for UI purposes) or raw values (for comparisons) 6714 * @param array List of status keys (numbers) to exclude 6715 * @return array A status array 6716 * @since 4.6.0 6717 */ 6718 6719 function status_list($labels = true, $exclude = array()) 6720 { 6721 $status_list = array( 6722 STATUS_DRAFT => 'draft', 6723 STATUS_HIDDEN => 'hidden', 6724 STATUS_PENDING => 'pending', 6725 STATUS_LIVE => 'live', 6726 STATUS_STICKY => 'sticky', 6727 ); 6728 6729 if (!is_array($exclude)) { 6730 $exclude = array(); 6731 } 6732 6733 foreach ($exclude as $remove) { 6734 unset($status_list[(int) $remove]); 6735 } 6736 6737 callback_event_ref('status.types', 'types', 0, $status_list); 6738 6739 if ($labels) { 6740 $status_list = array_map('gTxt', $status_list); 6741 } 6742 6743 return $status_list; 6744 } 6745 6746 /** 6747 * Translates article status names into numerical status codes. 6748 * 6749 * @param string $name Status name 6750 * @param int $default Status code to return if $name is not a defined status name 6751 * @return int Matching numerical status 6752 */ 6753 6754 function getStatusNum($name, $default = STATUS_LIVE) 6755 { 6756 $statuses = status_list(false); 6757 $status = strtolower($name); 6758 $num = array_search($status, $statuses); 6759 6760 if ($num === false) { 6761 $num = $default; 6762 } 6763 6764 return (int) $num; 6765 } 6766 6767 /** 6768 * Checks install's file integrity and returns results. 6769 * 6770 * Depending on the given $flags this function will either return an array of 6771 * file statuses, checksums or the digest of the install. It can also return the 6772 * parsed contents of the checksum file. 6773 * 6774 * @param int $flags Options are INTEGRITY_MD5 | INTEGRITY_STATUS | INTEGRITY_REALPATH | INTEGRITY_DIGEST 6775 * @return array|bool Array of files and status, or FALSE on error 6776 * @since 4.6.0 6777 * @package Debug 6778 * @example 6779 * print_r( 6780 * check_file_integrity(INTEGRITY_MD5 | INTEGRITY_REALPATH) 6781 * ); 6782 */ 6783 6784 function check_file_integrity($flags = INTEGRITY_STATUS) 6785 { 6786 static $files = null, $files_md5 = array(), $checksum_table = array(); 6787 6788 if ($files === null) { 6789 if ($cs = @file(txpath.'/checksums.txt')) { 6790 $files = array(); 6791 6792 foreach ($cs as $c) { 6793 if (preg_match('@^(\S+):(?: r?(\S+) | )\(?(.{32})\)?$@', trim($c), $m)) { 6794 list(, $relative, $r, $md5) = $m; 6795 $file = realpath(txpath.$relative); 6796 $checksum_table[$relative] = $md5; 6797 6798 if ($file === false) { 6799 $files[$relative] = INTEGRITY_MISSING; 6800 $files_md5[$relative] = false; 6801 continue; 6802 } 6803 6804 if (!is_readable($file)) { 6805 $files[$relative] = INTEGRITY_NOT_READABLE; 6806 $files_md5[$relative] = false; 6807 continue; 6808 } 6809 6810 if (!is_file($file)) { 6811 $files[$relative] = INTEGRITY_NOT_FILE; 6812 $files_md5[$relative] = false; 6813 continue; 6814 } 6815 6816 $files_md5[$relative] = md5_file($file); 6817 6818 if ($files_md5[$relative] !== $md5) { 6819 $files[$relative] = INTEGRITY_MODIFIED; 6820 } else { 6821 $files[$relative] = INTEGRITY_GOOD; 6822 } 6823 } 6824 } 6825 6826 if (!get_pref('enable_xmlrpc_server', true)) { 6827 unset( 6828 $files_md5['/../rpc/index.php'], 6829 $files_md5['/../rpc/TXP_RPCServer.php'], 6830 $files['/../rpc/index.php'], 6831 $files['/../rpc/TXP_RPCServer.php'] 6832 ); 6833 } 6834 } else { 6835 $files_md5 = $files = false; 6836 } 6837 } 6838 6839 if ($flags & INTEGRITY_DIGEST) { 6840 return $files_md5 ? md5(implode(n, $files_md5)) : false; 6841 } 6842 6843 if ($flags & INTEGRITY_TABLE) { 6844 return $checksum_table ? $checksum_table : false; 6845 } 6846 6847 $return = $files; 6848 6849 if ($flags & INTEGRITY_MD5) { 6850 $return = $files_md5; 6851 } 6852 6853 if ($return && $flags & INTEGRITY_REALPATH) { 6854 $relative = array(); 6855 6856 foreach ($return as $path => $status) { 6857 $realpath = realpath(txpath.$path); 6858 $relative[!$realpath ? $path : $realpath] = $status; 6859 } 6860 6861 return $relative; 6862 } 6863 6864 return $return; 6865 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title