Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/lib/txplib_misc.php - 3252 lines - 95757 bytes - Summary - Text - Print

   1  <?php
   2  
   3  /*
   4  $HeadURL: https://textpattern.googlecode.com/svn/releases/4.5.4/source/textpattern/lib/txplib_misc.php $
   5  $LastChangedRevision: 4816 $
   6  */
   7  
   8  // -------------------------------------------------------------
   9  	function deNull($in)
  10      {
  11          return (is_array($in) ? doArray($in, 'deNull') : strtr($in, array("\0" => '')));
  12      }
  13  
  14  // -------------------------------------------------------------
  15  	function deCRLF($in)
  16      {
  17          return (is_array($in) ? doArray($in, 'deCRLF') : strtr($in, array("\n" => '', "\r" => '')));
  18      }
  19  
  20  // -------------------------------------------------------------
  21  	function doArray($in,$function)
  22      {
  23          if(is_array($in))
  24          {
  25              return array_map($function, $in);
  26          }
  27          
  28          if(is_array($function))
  29          {
  30              return call_user_func($function, $in);
  31          }
  32          
  33          return $function($in);
  34      }
  35  
  36  // -------------------------------------------------------------
  37  	function doStrip($in)
  38      {
  39          return is_array($in) ? doArray($in, 'doStrip') : doArray($in, 'stripslashes');
  40      }
  41  
  42  // -------------------------------------------------------------
  43  	function doStripTags($in)
  44      {
  45          return is_array($in) ? doArray($in, 'doStripTags') : doArray($in,'strip_tags');
  46      }
  47  
  48  // -------------------------------------------------------------
  49  	function doDeEnt($in)
  50      {
  51          return doArray($in,'deEntBrackets');
  52      }
  53  
  54  // -------------------------------------------------------------
  55  	function deEntBrackets($in)
  56      {
  57          $array = array(
  58              '&#60;'  => '<',
  59              '&lt;'   => '<',
  60              '&#x3C;' => '<',
  61              '&#62;'  => '>',
  62              '&gt;'   => '>',
  63              '&#x3E;' => '>'
  64          );
  65  
  66          foreach($array as $k=>$v){
  67              $in = preg_replace("/".preg_quote($k)."/i",$v, $in);
  68          }
  69          return $in;
  70      }
  71  
  72  // -------------------------------------------------------------
  73  	function doSlash($in)
  74      {
  75          return doArray($in,'safe_escape');
  76      }
  77  
  78      /**
  79       * A shell for htmlspecialchars() with $flags defaulting to ENT_QUOTES
  80       *
  81       * @param string $string     The string being converted.
  82       * @param int $flags         A bitmask of one or more flags. The default is ENT_QUOTES
  83       * @param string $encoding     Defines encoding used in conversion. The default is UTF-8.
  84       * @param bool $double_encode When double_encode is turned off PHP will not encode existing html entities, the default is to convert everything.
  85       * @return string
  86       * @see http://www.php.net/manual/function.htmlspecialchars.php
  87       * @since 4.5.0
  88       */
  89  	function txpspecialchars($string, $flags = ENT_QUOTES, $encoding = 'UTF-8', $double_encode = true)
  90      {
  91  //        Ignore ENT_HTML5 and ENT_XHTML for now.
  92  //        ENT_HTML5 and ENT_XHTML are defined in PHP 5.4+ but we consistently encode single quotes as &#039; in any doctype.
  93  //        global $prefs;
  94  //        static $h5 = null;
  95  //        if (defined(ENT_HTML5)) {
  96  //            if ($h5 === null) {
  97  //                $h5 = ($prefs['doctype'] == 'html5' && txpinterface == 'public');
  98  //            }
  99  //            if ($h5) {
 100  //                $flags = ($flags | ENT_HTML5) & ~ENT_HTML401;
 101  //            }
 102  //        }
 103          return htmlspecialchars($string, $flags, $encoding, $double_encode);
 104      }
 105  
 106  // -------------------------------------------------------------
 107  	function doSpecial($in)
 108      {
 109          return doArray($in,'txpspecialchars');
 110      }
 111  
 112  // -------------------------------------------------------------
 113  	function _null($a)
 114      {
 115          return NULL;
 116      }
 117  // -------------------------------------------------------------
 118  	function array_null($in)
 119      {
 120          return array_map('_null', $in);
 121      }
 122  
 123  // -------------------------------------------------------------
 124  	function escape_title($title)
 125      {
 126          return strtr($title,
 127              array(
 128                  '<' => '&#60;',
 129                  '>' => '&#62;',
 130                  "'" => '&#39;',
 131                  '"' => '&#34;',
 132              )
 133          );
 134      }
 135  
 136  /**
 137   * Escape special string characters like \n or \\ for JavaScript.
 138   *
 139   * @param string $js JavaScript input
 140   * @return    string    Escaped JavaScript
 141   * @since 4.4
 142   */
 143  
 144  function escape_js($js)
 145  {
 146      return addcslashes($js, "\\\'\"\n\r");
 147  }
 148  
 149  // -------------------------------------------------------------
 150  // deprecated in 4.2.0
 151  	function escape_output($str)
 152      {
 153          trigger_error(gTxt('deprecated_function_with', array('{name}' => __FUNCTION__, '{with}' => 'txpspecialchars')), E_USER_NOTICE);
 154          return txpspecialchars($str);
 155      }
 156  
 157  // -------------------------------------------------------------
 158  // deprecated in 4.2.0
 159  	function escape_tags($str)
 160      {
 161          trigger_error(gTxt('deprecated_function', array('{name}' => __FUNCTION__)), E_USER_NOTICE);
 162          return strtr($str,
 163              array(
 164                  '<' => '&#60;',
 165                  '>' => '&#62;',
 166              )
 167          );
 168      }
 169  
 170  // -------------------------------------------------------------
 171  	function escape_cdata($str)
 172      {
 173          return '<![CDATA['.str_replace(']]>', ']]]><![CDATA[]>', $str).']]>';
 174      }
 175  
 176  //-------------------------------------------------------------
 177  	function gTxt($var, $atts=array(), $escape='html')
 178      {
 179          global $textarray;
 180  
 181          if (!is_array($atts)) {
 182              $atts = array();
 183          }
 184  
 185          if ($escape == 'html')
 186          {
 187              foreach ($atts as $key => $value)
 188              {
 189                  $atts[$key] = txpspecialchars($value);
 190              }
 191          }
 192  
 193          $v = strtolower($var);
 194          if (isset($textarray[$v])) {
 195              $out = $textarray[$v];
 196              if ($out !== '') return strtr($out, $atts);
 197          }
 198  
 199          if ($atts)
 200              return $var.': '.join(', ', $atts);
 201          return $var;
 202      }
 203  
 204  //-------------------------------------------------------------
 205  /**
 206   * Localize client scripts
 207   *
 208   * @param string|array $var scalar or array of string keys
 209   * @param array $atts array or array of arrays of variable substitution pairs
 210   * @since 4.5.0
 211   */
 212  	function gTxtScript($var, $atts = array())
 213      {
 214          global $textarray_script;
 215  
 216          if (!is_array($textarray_script)) {
 217              $textarray_script = array();
 218          }
 219  
 220          $data = (is_array($var) ? array_map('gTxt', $var, $atts) : (array)gTxt($var, $atts));
 221          $textarray_script = $textarray_script + array_combine((array)$var, $data);
 222      }
 223  
 224  //-------------------------------------------------------------
 225  	function gTime($timestamp)
 226      {
 227          return safe_strftime('%d&#160;%b&#160;%Y %X', $timestamp);
 228      }
 229  
 230  // -------------------------------------------------------------
 231  	function dmp()
 232      {
 233          static $f = FALSE;
 234  
 235          if(defined('txpdmpfile'))
 236          {
 237              global $prefs;
 238  
 239              if(!$f) $f = fopen($prefs['tempdir'].'/'.txpdmpfile, 'a');
 240  
 241              $stack = get_caller();
 242              fwrite($f, "\n[".$stack[0].t.safe_strftime('iso8601')."]\n");
 243          }
 244  
 245          $a = func_get_args();
 246  
 247          if(!$f) echo "<pre>".n;
 248  
 249          foreach ($a as $thing)
 250          {
 251              $out = is_scalar($thing) ? strval($thing) : var_export($thing, true);
 252  
 253              if ($f)
 254              {
 255                  fwrite($f, $out."\n");
 256              }
 257              else
 258              {
 259                  echo txpspecialchars($out), n;
 260              }
 261          }
 262  
 263          if(!$f) echo "</pre>".n;
 264      }
 265  
 266  // -------------------------------------------------------------
 267  	function load_lang($lang)
 268      {
 269          foreach(array($lang, 'en-gb') as $lang_code)
 270          {
 271              $rs = (txpinterface == 'admin')
 272                  ? safe_rows('name, data','txp_lang',"lang='".doSlash($lang_code)."'")
 273                  : safe_rows('name, data','txp_lang',"lang='".doSlash($lang_code)."' AND ( event='public' OR event='common')");
 274  
 275              if (!empty($rs)) break;
 276          }
 277  
 278          $out = array();
 279  
 280          if (!empty($rs))
 281          {
 282              foreach ($rs as $a)
 283              {
 284                  if (!empty($a['name'])) {
 285                      $out[$a['name']] = $a['data'];
 286                  }
 287              }
 288          }else{
 289              #backward compatibility stuff. Remove when necessary.
 290              $filename = is_file(txpath.'/lang/'.$lang.'.txt')
 291              ?    txpath.'/lang/'.$lang.'.txt'
 292              :    txpath.'/lang/en-gb.txt';
 293  
 294              $file = @fopen($filename, "r");
 295              if ($file) {
 296                  while (!feof($file)) {
 297                      $line = fgets($file, 4096);
 298                  if($line[0]=='#') continue;
 299                  @list($name,$val) = explode(' => ',trim($line));
 300                  $out[$name] = $val;
 301               }
 302                  @fclose($filename);
 303              }
 304          }
 305  
 306          return $out;
 307      }
 308  
 309  // -------------------------------------------------------------
 310  	function load_lang_dates($lang)
 311      {
 312          $filename = is_file(txpath.'/lang/'.$lang.'_dates.txt')?
 313              txpath.'/lang/'.$lang.'_dates.txt':
 314              txpath.'/lang/en-gb_dates.txt';
 315          $file = @file(txpath.'/lang/'.$lang.'_dates.txt','r');
 316          if(is_array($file)) {
 317              foreach($file as $line) {
 318                  if($line[0]=='#' || strlen($line) < 2) continue;
 319                  list($name,$val) = explode('=>',$line,2);
 320                  $out[trim($name)] = trim($val);
 321              }
 322              return $out;
 323          }
 324          return false;
 325      }
 326  // -------------------------------------------------------------
 327  
 328  	function load_lang_event($event)
 329      {
 330          $lang = LANG;
 331  
 332          $installed = (false !== safe_field('name', 'txp_lang',"lang='".doSlash($lang)."' limit 1"));
 333  
 334          $lang_code = ($installed)? $lang : 'en-gb';
 335  
 336          $rs = safe_rows_start('name, data','txp_lang',"lang='".doSlash($lang_code)."' AND event='".doSlash($event)."'");
 337  
 338          $out = array();
 339  
 340          if ($rs && !empty($rs))
 341          {
 342              while ($a = nextRow($rs))
 343              {
 344                  $out[$a['name']] = $a['data'];
 345              }
 346          }
 347          return ($out) ? $out : '';
 348      }
 349  
 350  // -------------------------------------------------------------
 351  // deprecated in 4.3.0
 352  	function check_privs()
 353      {
 354          trigger_error(gTxt('deprecated_function_with', array('{name}' => __FUNCTION__, '{with}' => 'require_privs')), E_USER_NOTICE);
 355          global $txp_user;
 356          $privs = safe_field("privs", "txp_users", "name='".doSlash($txp_user)."'");
 357          $args = func_get_args();
 358          if(!in_array($privs,$args)) {
 359              exit(pageTop('Restricted').'<p style="margin-top:3em;text-align:center">'.
 360                  gTxt('restricted_area').'</p>');
 361          }
 362      }
 363  
 364  // -------------------------------------------------------------
 365  	function add_privs($res, $perm = '1') // perm = '1,2,3'
 366      {
 367          global $txp_permissions;
 368          // Don't let them override privs that exist
 369          if (!isset($txp_permissions[$res]))
 370              $txp_permissions[$res] = $perm;
 371      }
 372  
 373  // -------------------------------------------------------------
 374  	function has_privs($res, $user='')
 375      {
 376          global $txp_user, $txp_permissions;
 377          static $privs;
 378  
 379          // If no user name is supplied, assume the current login name
 380          if (empty($user))
 381              $user = $txp_user;
 382  
 383          if (!isset($privs[$user]))
 384          {
 385              $privs[$user] = safe_field("privs", "txp_users", "name='".doSlash($user)."'");
 386          }
 387  
 388          if (isset($txp_permissions[$res]))
 389          {
 390              return in_array($privs[$user], explode(',', $txp_permissions[$res]));
 391          }
 392  
 393          else
 394          {
 395              return false;
 396          }
 397      }
 398  
 399  // -------------------------------------------------------------
 400  	function require_privs($res, $user='')
 401      {
 402          if (!has_privs($res, $user))
 403              exit(pageTop('Restricted').'<p class="restricted-area">'.
 404                  gTxt('restricted_area').'</p>');
 405      }
 406  
 407      /**
 408       * Get list of users having access to a resource
 409       *
 410       * @param string $res    resource, e.g. 'article.edit.published'
 411       * @return array
 412       * @since 4.5.0
 413       */
 414  	function the_privileged($res)
 415      {
 416          global $txp_permissions;
 417          if (isset($txp_permissions[$res])) {
 418              return safe_column('name', 'txp_users', "FIND_IN_SET(privs, '{$txp_permissions[$res]}') order by name asc");
 419          } else {
 420              return array();
 421          }
 422      }
 423  
 424  // -------------------------------------------------------------
 425  	function get_groups()
 426      {
 427          global $txp_groups;
 428          return doArray($txp_groups, 'gTxt');
 429      }
 430  
 431  // -------------------------------------------------------------
 432  	function sizeImage($name)
 433      {
 434          $size = @getimagesize($name);
 435          return(is_array($size)) ? $size[3] : false;
 436      }
 437  
 438  // -------------------------------------------------------------
 439  	function gps($thing) // checks GET and POST for a named variable, or creates it blank
 440      {
 441          $out = '';
 442          if (isset($_GET[$thing])) {
 443              if (MAGIC_QUOTES_GPC) {
 444                  $out = doStrip($_GET[$thing]);
 445              } else {
 446                  $out = $_GET[$thing];
 447              }
 448  
 449              $out = doArray( $out, 'deCRLF' ); # Remove CRLF from Get parameters
 450          } elseif (isset($_POST[$thing])) {
 451              if (MAGIC_QUOTES_GPC) {
 452                  $out = doStrip($_POST[$thing]);
 453              } else {
 454                  $out = $_POST[$thing]; # CRLF ok in posted vars
 455              }
 456          }
 457  
 458          $out = doArray($out, 'deNull'); # Remove Nulls to avoid string truncations in C calls (ie. All the filesystem routines)
 459  
 460          return $out;
 461      }
 462  
 463  // -------------------------------------------------------------
 464  	function gpsa($array) // performs gps() on an array of variable names
 465      {
 466          if(is_array($array)) {
 467              $out = array();
 468              foreach($array as $a) {
 469                  $out[$a] = gps($a);
 470              }
 471              return $out;
 472          }
 473          return false;
 474      }
 475  
 476  // -------------------------------------------------------------
 477      function ps($thing) // checks POST for a named variable, or creates it blank
 478      {
 479          $out = '';
 480          if (isset($_POST[$thing])) {
 481              if (MAGIC_QUOTES_GPC) {
 482                  $out = doStrip($_POST[$thing]);
 483              } else {
 484                  $out = $_POST[$thing];
 485              }
 486          }
 487  
 488          $out = doArray($out, 'deNull');
 489  
 490          return $out;
 491      }
 492  
 493  // -------------------------------------------------------------
 494  	function psa($array) // performs ps on an array of variable names
 495      {
 496          foreach($array as $a) {
 497              $out[$a] = ps($a);
 498          }
 499          return $out;
 500      }
 501  
 502  // -------------------------------------------------------------
 503  	function psas($array) // same as above, but does strip_tags on post values
 504      {
 505          foreach($array as $a) {
 506              $out[$a] = doStripTags(ps($a));
 507          }
 508          return $out;
 509      }
 510  
 511  // -------------------------------------------------------------
 512  	function stripPost()
 513      {
 514          if (isset($_POST)) {
 515              if (MAGIC_QUOTES_GPC) {
 516                  return doStrip($_POST);
 517              } else {
 518                  return $_POST;
 519              }
 520          }
 521          return '';
 522      }
 523  
 524  // -------------------------------------------------------------
 525  	function serverSet($thing) // Get a var from $_SERVER global array, or create it
 526      {
 527          return (isset($_SERVER[$thing])) ? $_SERVER[$thing] : '';
 528      }
 529  
 530  // -------------------------------------------------------------
 531  	function remote_addr()
 532      {
 533          $ip = serverSet('REMOTE_ADDR');
 534          if (($ip == '127.0.0.1' || $ip == '::1' || $ip == '::ffff:127.0.0.1' || $ip == serverSet('SERVER_ADDR')) && serverSet('HTTP_X_FORWARDED_FOR')) {
 535              $ips = explode(', ', serverSet('HTTP_X_FORWARDED_FOR'));
 536              $ip = $ips[0];
 537          }
 538          return $ip;
 539      }
 540  
 541  // -------------------------------------------------------------
 542   	function pcs($thing) //    Get a var from POST or COOKIE; if not, create it
 543      {
 544          if (isset($_COOKIE["txp_".$thing])) {
 545              if (MAGIC_QUOTES_GPC) {
 546                  return doStrip($_COOKIE["txp_".$thing]);
 547              } else return $_COOKIE["txp_".$thing];
 548          } elseif (isset($_POST[$thing])) {
 549              if (MAGIC_QUOTES_GPC) {
 550                  return doStrip($_POST[$thing]);
 551              } else return $_POST[$thing];
 552          }
 553          return '';
 554      }
 555  
 556  // -------------------------------------------------------------
 557       function cs($thing) //    Get a var from COOKIE; if not, create it
 558      {
 559          if (isset($_COOKIE[$thing])) {
 560              if (MAGIC_QUOTES_GPC) {
 561                  return doStrip($_COOKIE[$thing]);
 562              } else return $_COOKIE[$thing];
 563          }
 564          return '';
 565      }
 566  
 567  // -------------------------------------------------------------
 568  	function yes_no($status)
 569      {
 570          return ($status==0) ? (gTxt('no')) : (gTxt('yes'));
 571      }
 572  
 573  // -------------------------------------------------------------
 574  	function getmicrotime()
 575      {
 576          list($usec, $sec) = explode(" ",microtime());
 577          return ((float)$usec + (float)$sec);
 578      }
 579  
 580  // -------------------------------------------------------------
 581  	function load_plugin($name, $force=false)
 582      {
 583          global $plugins, $plugins_ver, $prefs, $txp_current_plugin;
 584  
 585          if (is_array($plugins) and in_array($name,$plugins)) {
 586              return true;
 587          }
 588  
 589          if (!empty($prefs['plugin_cache_dir'])) {
 590              $dir = rtrim($prefs['plugin_cache_dir'], '/') . '/';
 591              # in case it's a relative path
 592              if (!is_dir($dir))
 593                  $dir = rtrim(realpath(txpath.'/'.$dir), '/') . '/';
 594              if (is_file($dir . $name . '.php')) {
 595                  $plugins[] = $name;
 596                  set_error_handler("pluginErrorHandler");
 597                  if (isset($txp_current_plugin)) $txp_parent_plugin = $txp_current_plugin;
 598                  $txp_current_plugin = $name;
 599                  include($dir . $name . '.php');
 600                  $txp_current_plugin = (isset($txp_parent_plugin) ? $txp_parent_plugin : NULL);
 601                  $plugins_ver[$name] = @$plugin['version'];
 602                  restore_error_handler();
 603                  return true;
 604              }
 605          }
 606  
 607          $rs = safe_row("name,code,version","txp_plugin", ($force ? '' : 'status = 1 AND '). "name='".doSlash($name)."'");
 608          if ($rs) {
 609              $plugins[] = $rs['name'];
 610              $plugins_ver[$rs['name']] = $rs['version'];
 611  
 612              set_error_handler("pluginErrorHandler");
 613              if (isset($txp_current_plugin)) $txp_parent_plugin = $txp_current_plugin;
 614              $txp_current_plugin = $rs['name'];
 615              eval($rs['code']);
 616              $txp_current_plugin = (isset($txp_parent_plugin) ? $txp_parent_plugin : NULL);
 617              restore_error_handler();
 618  
 619              return true;
 620          }
 621  
 622          return false;
 623      }
 624  
 625  // -------------------------------------------------------------
 626  	function require_plugin($name)
 627      {
 628          if (!load_plugin($name)) {
 629              trigger_error("Unable to include required plugin \"{$name}\"",E_USER_ERROR);
 630              return false;
 631          }
 632          return true;
 633      }
 634  
 635  // -------------------------------------------------------------
 636  	function include_plugin($name)
 637      {
 638          if (!load_plugin($name)) {
 639              trigger_error("Unable to include plugin \"{$name}\"",E_USER_WARNING);
 640              return false;
 641          }
 642          return true;
 643      }
 644  
 645  // -------------------------------------------------------------
 646  	function pluginErrorHandler($errno, $errstr, $errfile, $errline)
 647      {
 648          global $production_status;
 649  
 650          $error = array( E_WARNING => "Warning", E_NOTICE => "Notice", E_RECOVERABLE_ERROR => "Catchable fatal error",
 651                          E_USER_ERROR => "User_Error", E_USER_WARNING => "User_Warning", E_USER_NOTICE => "User_Notice");
 652  
 653          if (!($errno & error_reporting())) return;
 654          if ($production_status == 'live' || ($production_status != 'debug' && $errno == E_USER_NOTICE)) return;
 655  
 656          global $txp_current_plugin, $production_status;
 657          printf ("<pre>".gTxt('plugin_load_error').' <b>%s</b> -> <b>%s: %s on line %s</b></pre>',
 658                  $txp_current_plugin, $error[$errno], $errstr, $errline);
 659          if ($production_status == 'debug')
 660              print "\n<pre style=\"padding-left: 2em;\" class=\"backtrace\"><code>".txpspecialchars(join("\n", get_caller(10)))."</code></pre>";
 661      }
 662  
 663  // -------------------------------------------------------------
 664  	function tagErrorHandler($errno, $errstr, $errfile, $errline)
 665      {
 666          global $production_status;
 667  
 668          $error = array( E_WARNING => "Warning", E_NOTICE => "Notice", E_RECOVERABLE_ERROR => "Textpattern Catchable fatal error",
 669                          E_USER_ERROR => "Textpattern Error", E_USER_WARNING => "Textpattern Warning", E_USER_NOTICE => "Textpattern Notice");
 670  
 671          if (!($errno & error_reporting())) return;
 672          if ($production_status == 'live') return;
 673  
 674          global $txp_current_tag, $txp_current_form, $pretext;
 675          $page = (empty($pretext['page']) ? gTxt('none') : $pretext['page']);
 676          if (!isset($txp_current_form)) $txp_current_form = gTxt('none');
 677          $locus = gTxt('while_parsing_page_form', array('{page}' => txpspecialchars($page), '{form}' => txpspecialchars($txp_current_form)));
 678  
 679          printf ("<pre>".gTxt('tag_error').' <b>%s</b> -> <b> %s: %s %s</b></pre>',
 680                  txpspecialchars($txp_current_tag), $error[$errno], $errstr, $locus );
 681          if ($production_status == 'debug')
 682              {
 683              print "\n<pre style=\"padding-left: 2em;\" class=\"backtrace\"><code>".txpspecialchars(join("\n", get_caller(10)))."</code></pre>";
 684  
 685              $trace_msg = gTxt('tag_error').' '.$txp_current_tag.' -> '.$error[$errno].': '.$errstr.' '.$locus;
 686              trace_add( $trace_msg );
 687              }
 688      }
 689  
 690  // -------------------------------------------------------------
 691  	function feedErrorHandler($errno, $errstr, $errfile, $errline)
 692      {
 693          global $production_status;
 694  
 695          if ($production_status != 'debug') return;
 696  
 697          return tagErrorHandler($errno, $errstr, $errfile, $errline);
 698      }
 699  
 700  // -------------------------------------------------------------
 701  	function adminErrorHandler($errno, $errstr, $errfile, $errline)
 702      {
 703          global $production_status, $theme, $event, $step;
 704  
 705          if (!error_reporting()) return;
 706  
 707          // When even a minimum environment is missing...
 708          if (!isset($production_status)) {
 709              echo '<pre>'.gTxt('internal_error').' "'.$errstr.'"'.n."in $errfile at line $errline".'</pre>';
 710              return;
 711          }
 712  
 713          if ($production_status == 'live' || ($production_status != 'debug' && $errno == E_USER_NOTICE)) {
 714              $backtrace = $msg = '';
 715          } else {
 716              $backtrace = '';
 717              $msg = gTxt('internal_error');
 718  
 719              if (has_privs('debug.verbose')) {
 720                  $msg .= ' "'.$errstr.'"';
 721              };
 722  
 723              if (($production_status == 'debug')) {
 724                  if (has_privs('debug.backtrace')) {
 725                      $msg .= n."in $errfile at line $errline";
 726                      $backtrace = join(n, get_caller(5,1));
 727                  }
 728              }
 729          }
 730  
 731          $httpstatus = in_array($errno, array(E_ERROR, E_USER_ERROR)) ? '500' : '200';
 732          $out = "$msg.\n$backtrace";
 733  
 734          if (http_accept_format('html')) {
 735              if (!empty($backtrace)) {
 736                  echo "<pre>$msg.</pre>".
 737                      n.'<pre style="padding-left: 2em;" class="backtrace"><code>'.
 738                      txpspecialchars($backtrace).'</code></pre>';
 739              } elseif (!empty($msg)) {
 740                  echo is_object($theme) ? $theme->announce(array($out, E_ERROR), true) : "<pre>$out</pre>";
 741              }
 742              $c = array('in' => '', 'out' => '');
 743          } elseif (http_accept_format('js')) {
 744              send_script_response(
 745                  is_object($theme) && !empty($msg) ?
 746                  $theme->announce_async(array($out, E_ERROR), true) :
 747                  "/* $out */"
 748              );
 749              $c = array('in' => '/* ', 'out' => ' */');
 750          } elseif (http_accept_format('xml')) {
 751              send_xml_response(array('http-status' => $httpstatus, 'internal_error' => "$out"));
 752              $c = array('in' => '<!-- ', 'out' => ' -->');
 753          } else {
 754              txp_die($msg, 500);
 755          }
 756  
 757          if ($production_status != 'live' && in_array($errno, array(E_ERROR, E_USER_ERROR))) {
 758              die($c['in'].gTxt('get_off_my_lawn', array('{event}' => $event, '{step}' => $step)).$c['out']);
 759          }
 760      }
 761  
 762  // -------------------------------------------------------------
 763  	function publicErrorHandler($errno, $errstr, $errfile, $errline)
 764      {
 765          global $production_status;
 766  
 767          $error = array( E_WARNING => "Warning", E_NOTICE => "Notice", E_USER_ERROR => "Textpattern Error",
 768                          E_USER_WARNING => "Textpattern Warning", E_USER_NOTICE => "Textpattern Notice");
 769  
 770          if (!($errno & error_reporting())) return;
 771          if ($production_status == 'live' || ($production_status != 'debug' && $errno == E_USER_NOTICE)) return;
 772  
 773          global $production_status;
 774          printf ("<pre>".gTxt('general_error').' <b>%s: %s on line %s</b></pre>',
 775              $error[$errno], $errstr, $errline);
 776          if ($production_status == 'debug')
 777              print "\n<pre style=\"padding-left: 2em;\" class=\"backtrace\"><code>".txpspecialchars(join("\n", get_caller(10)))."</code></pre>";
 778      }
 779  
 780  // -------------------------------------------------------------
 781  	function load_plugins($type=0)
 782      {
 783          global $prefs, $plugins, $plugins_ver, $app_mode;
 784  
 785          if (!is_array($plugins)) $plugins = array();
 786  
 787          if (!empty($prefs['plugin_cache_dir'])) {
 788              $dir = rtrim($prefs['plugin_cache_dir'], '/') . '/';
 789              // in case it's a relative path
 790              if (!is_dir($dir))
 791                  $dir = rtrim(realpath(txpath.'/'.$dir), '/') . '/';
 792              $files = glob($dir.'*.php');
 793              if ($files) {
 794                  natsort($files);
 795                  foreach ($files as $f) {
 796                      load_plugin(basename($f, '.php'));
 797                  }
 798              }
 799          }
 800  
 801          $admin = ($app_mode == 'async' && !AJAXALLY_CHALLENGED ? '4,5' : '1,3,4,5');
 802          $where = 'status = 1 AND type IN ('.($type ? $admin : '0,1,5').')';
 803  
 804          $rs = safe_rows("name, code, version", "txp_plugin", $where.' order by load_order');
 805          if ($rs) {
 806              $old_error_handler = set_error_handler("pluginErrorHandler");
 807              foreach($rs as $a) {
 808                  if (!in_array($a['name'],$plugins)) {
 809                      $plugins[] = $a['name'];
 810                      $plugins_ver[$a['name']] = $a['version'];
 811                      $GLOBALS['txp_current_plugin'] = $a['name'];
 812                      $eval_ok = eval($a['code']);
 813                      if ($eval_ok === FALSE)
 814                          echo gTxt('plugin_load_error_above').strong($a['name']).n.br;
 815                      unset($GLOBALS['txp_current_plugin']);
 816                  }
 817              }
 818              restore_error_handler();
 819          }
 820      }
 821  
 822  // -------------------------------------------------------------
 823  	function register_callback($func, $event, $step='', $pre=0)
 824      {
 825          global $plugin_callback;
 826  
 827          $plugin_callback[] = array('function'=>$func, 'event'=>$event, 'step'=>$step, 'pre'=>$pre);
 828      }
 829  
 830  // -------------------------------------------------------------
 831  	function register_page_extension($func, $event, $step='', $top=0)
 832      {
 833          # For now this just does the same as register_callback
 834          register_callback($func, $event, $step, $top);
 835      }
 836  
 837  // -------------------------------------------------------------
 838  	function callback_event($event, $step='', $pre=0)
 839      {
 840          global $plugin_callback, $production_status;
 841  
 842          if (!is_array($plugin_callback))
 843              return '';
 844  
 845          $return_value = '';
 846  
 847          // any payload parameters?
 848          $argv = func_get_args();
 849          $argv = (count($argv) > 3) ? array_slice($argv, 3) : array();
 850  
 851          foreach ($plugin_callback as $c) {
 852              if ($c['event'] == $event and (empty($c['step']) or $c['step'] == $step) and $c['pre'] == $pre) {
 853                  if (is_callable($c['function'])) {
 854                      $return_value .= call_user_func_array($c['function'], array('event' => $event, 'step' => $step) + $argv);
 855                  } elseif ($production_status == 'debug') {
 856                      trigger_error(gTxt('unknown_callback_function', array('{function}' => callback_tostring($c['function']))), E_USER_WARNING);
 857                  }
 858              }
 859          }
 860          return $return_value;
 861      }
 862  
 863  
 864  // -------------------------------------------------------------
 865  /**
 866   * Call an event's callback with two optional byref parameters
 867   * @param string $event
 868   * @param string $step
 869   * @param boolean $pre 0|1
 870   * @param mixed $data optional arguments for event handlers
 871   * @param mixed $options optional arguments for event handlers
 872   * @return array collection of return values from event handlers
 873   * @since 4.5.0
 874   */
 875   	function callback_event_ref($event, $step='', $pre=0, &$data=null, &$options=null)
 876      {
 877          global $plugin_callback, $production_status;
 878  
 879          if (!is_array($plugin_callback))
 880              return array();
 881  
 882          $return_value = array();
 883  
 884          foreach ($plugin_callback as $c) {
 885              if ($c['event'] == $event and (empty($c['step']) or $c['step'] == $step) and $c['pre'] == $pre) {
 886                  if (is_callable($c['function'])) {
 887                      // cannot call event handler via call_user_func() as this would dereference all arguments.
 888                      // side effect: callback handler *must* be ordinary function, *must not* be class method in PHP <5.4 (@see https://bugs.php.net/bug.php?id=47160)
 889                      $return_value[] = $c['function']($event, $step, $data, $options);
 890                  } elseif ($production_status == 'debug') {
 891                      trigger_error(gTxt('unknown_callback_function', array('{function}' => callback_tostring($c['function']))), E_USER_WARNING);
 892                  }
 893              }
 894          }
 895          return $return_value;
 896      }
 897  
 898  /**
 899   * @param string|array $callback a PHP "callback"
 900   * @return string $callback as a human-readable string
 901   * @since 4.5.0
 902   */
 903  	function callback_tostring($callback)
 904      {
 905          if (is_array($callback)) {
 906              return join('::', array_filter($callback, 'is_scalar'));
 907          }
 908          elseif (!is_scalar($callback)) {
 909              return '';
 910          }
 911          return $callback;
 912      }
 913  
 914  // -------------------------------------------------------------
 915  	function register_tab($area, $event, $title)
 916      {
 917          global $plugin_areas;
 918  
 919          if (!isset($GLOBALS['event']) || ($GLOBALS['event'] !== 'plugin'))
 920          {
 921              $plugin_areas[$area][$title] = $event;
 922          }
 923      }
 924  
 925  // -------------------------------------------------------------
 926  	function pluggable_ui($event, $element, $default='')
 927      {
 928          $argv = func_get_args();
 929          $argv = array_slice($argv, 2);
 930          // custom user interface, anyone?
 931          // signature for called functions:
 932          // string my_called_func(string $event, string $step, string $default_markup[, mixed $context_data...])
 933          $ui = call_user_func_array('callback_event', array('event' => $event, 'step' => $element, 'pre' => 0) + $argv);
 934          // either plugins provided a user interface, or we render our own
 935          return ($ui === '')? $default : $ui;
 936      }
 937  
 938  // -------------------------------------------------------------
 939      // deprecated in 4.2.0
 940  	function getAtt($name, $default=NULL)
 941      {
 942          trigger_error(gTxt('deprecated_function_with', array('{name}' => __FUNCTION__, '{with}' => 'lAtts')), E_USER_NOTICE);
 943          global $theseatts;
 944          return isset($theseatts[$name]) ? $theseatts[$name] : $default;
 945      }
 946  
 947  // -------------------------------------------------------------
 948      // deprecated in 4.2.0
 949  	function gAtt(&$atts, $name, $default=NULL)
 950      {
 951          trigger_error(gTxt('deprecated_function_with', array('{name}' => __FUNCTION__, '{with}' => 'lAtts')), E_USER_NOTICE);
 952          return isset($atts[$name]) ? $atts[$name] : $default;
 953      }
 954  
 955  // -------------------------------------------------------------
 956  	function lAtts($pairs, $atts, $warn=1)
 957      {
 958          global $production_status;
 959  
 960          foreach($atts as $name => $value)
 961          {
 962              if (array_key_exists($name, $pairs))
 963              {
 964                  $pairs[$name] = $value;
 965              }
 966              elseif ($warn and $production_status != 'live')
 967              {
 968                  trigger_error(gTxt('unknown_attribute', array('{att}' => $name)));
 969              }
 970          }
 971  
 972          return ($pairs) ? $pairs : false;
 973      }
 974  
 975  // -------------------------------------------------------------
 976      // deprecated in 4.5.0
 977  	function select_buttons()
 978      {
 979          return
 980          gTxt('select').
 981          n.fInput('button','selall',gTxt('all'),'','select all','selectall();').
 982          n.fInput('button','selnone',gTxt('none'),'','select none','deselectall();').
 983          n.fInput('button','selrange',gTxt('range'),'','select range','selectrange();');
 984      }
 985  
 986  // -------------------------------------------------------------
 987  	function stripSpace($text, $force=0)
 988      {
 989          global $prefs;
 990          if ($force or !empty($prefs['attach_titles_to_permalinks']))
 991          {
 992              $text = trim(sanitizeForUrl($text), '-');
 993              if ($prefs['permalink_title_format']) {
 994                  return (function_exists('mb_strtolower') ? mb_strtolower($text, 'UTF-8') : strtolower($text));
 995              } else {
 996                  return str_replace('-','',$text);
 997              }
 998          }
 999      }
1000  
1001  // -------------------------------------------------------------
1002  	function sanitizeForUrl($text)
1003      {
1004          // any overrides?
1005          $out = callback_event('sanitize_for_url', '', 0, $text);
1006          if ($out !== '') return $out;
1007  
1008          $in = $text;
1009          // Remove names entities and tags
1010          $text = preg_replace("/(^|&\S+;)|(<[^>]*>)/U","",dumbDown($text));
1011          // Dashify high-order chars leftover from dumbDown()
1012          $text = preg_replace("/[\x80-\xff]/","-",$text);
1013          // Collapse spaces, minuses, (back-)slashes and non-words
1014          $text = preg_replace('/[\s\-\/\\\\]+/', '-', trim(preg_replace('/[^\w\s\-\/\\\\]/', '', $text)));
1015          // Remove all non-whitelisted characters
1016          $text = preg_replace("/[^A-Za-z0-9\-_]/","",$text);
1017          // Sanitizing shouldn't leave us with plain nothing to show.
1018          // Fall back on percent-encoded URLs as a last resort for RFC 1738 conformance.
1019          if (empty($text) || $text == '-')
1020          {
1021              $text = rawurlencode($in);
1022          }
1023          return $text;
1024      }
1025  
1026  // -------------------------------------------------------------
1027  	function sanitizeForFile($text)
1028      {
1029          // any overrides?
1030          $out = callback_event('sanitize_for_file', '', 0, $text);
1031          if ($out !== '') return $out;
1032  
1033          // Remove control characters and " * \ : < > ? / |
1034          $text = preg_replace('/[\x00-\x1f\x22\x2a\x2f\x3a\x3c\x3e\x3f\x5c\x7c\x7f]+/', '', $text);
1035          // Remove duplicate dots and any leading or trailing dots/spaces
1036          $text = preg_replace('/[.]{2,}/', '.', trim($text, '. '));
1037          return $text;
1038      }
1039  
1040  // -------------------------------------------------------------
1041  	function sanitizeForPage($text)
1042      {
1043          // any overrides?
1044          $out = callback_event('sanitize_for_page', '', 0, $text);
1045          if ($out !== '') return $out;
1046  
1047          return trim(preg_replace('/[<>&"\']/', '', $text));
1048      }
1049  
1050  // -------------------------------------------------------------
1051  	function dumbDown($str, $lang=LANG)
1052      {
1053          static $array;
1054          if (empty($array[$lang])) {
1055              $array[$lang] = array( // nasty, huh?.
1056                  '&#192;'=>'A','&Agrave;'=>'A','&#193;'=>'A','&Aacute;'=>'A','&#194;'=>'A','&Acirc;'=>'A',
1057                  '&#195;'=>'A','&Atilde;'=>'A','&#196;'=>'Ae','&Auml;'=>'A','&#197;'=>'A','&Aring;'=>'A',
1058                  '&#198;'=>'Ae','&AElig;'=>'AE',
1059                  '&#256;'=>'A','&#260;'=>'A','&#258;'=>'A',
1060                  '&#199;'=>'C','&Ccedil;'=>'C','&#262;'=>'C','&#268;'=>'C','&#264;'=>'C','&#266;'=>'C',
1061                  '&#270;'=>'D','&#272;'=>'D','&#208;'=>'D','&ETH;'=>'D',
1062                  '&#200;'=>'E','&Egrave;'=>'E','&#201;'=>'E','&Eacute;'=>'E','&#202;'=>'E','&Ecirc;'=>'E','&#203;'=>'E','&Euml;'=>'E',
1063                  '&#274;'=>'E','&#280;'=>'E','&#282;'=>'E','&#276;'=>'E','&#278;'=>'E',
1064                  '&#284;'=>'G','&#286;'=>'G','&#288;'=>'G','&#290;'=>'G',
1065                  '&#292;'=>'H','&#294;'=>'H',
1066                  '&#204;'=>'I','&Igrave;'=>'I','&#205;'=>'I','&Iacute;'=>'I','&#206;'=>'I','&Icirc;'=>'I','&#207;'=>'I','&Iuml;'=>'I',
1067                  '&#298;'=>'I','&#296;'=>'I','&#300;'=>'I','&#302;'=>'I','&#304;'=>'I',
1068                  '&#306;'=>'IJ',
1069                  '&#308;'=>'J',
1070                  '&#310;'=>'K',
1071                  '&#321;'=>'K','&#317;'=>'K','&#313;'=>'K','&#315;'=>'K','&#319;'=>'K',
1072                  '&#209;'=>'N','&Ntilde;'=>'N','&#323;'=>'N','&#327;'=>'N','&#325;'=>'N','&#330;'=>'N',
1073                  '&#210;'=>'O','&Ograve;'=>'O','&#211;'=>'O','&Oacute;'=>'O','&#212;'=>'O','&Ocirc;'=>'O','&#213;'=>'O','&Otilde;'=>'O',
1074                  '&#214;'=>'Oe','&Ouml;'=>'Oe',
1075                  '&#216;'=>'O','&Oslash;'=>'O','&#332;'=>'O','&#336;'=>'O','&#334;'=>'O',
1076                  '&#338;'=>'OE',
1077                  '&#340;'=>'R','&#344;'=>'R','&#342;'=>'R',
1078                  '&#346;'=>'S','&#352;'=>'S','&#350;'=>'S','&#348;'=>'S','&#536;'=>'S',
1079                  '&#356;'=>'T','&#354;'=>'T','&#358;'=>'T','&#538;'=>'T',
1080                  '&#217;'=>'U','&Ugrave;'=>'U','&#218;'=>'U','&Uacute;'=>'U','&#219;'=>'U','&Ucirc;'=>'U',
1081                  '&#220;'=>'Ue','&#362;'=>'U','&Uuml;'=>'Ue',
1082                  '&#366;'=>'U','&#368;'=>'U','&#364;'=>'U','&#360;'=>'U','&#370;'=>'U',
1083                  '&#372;'=>'W',
1084                  '&#221;'=>'Y','&Yacute;'=>'Y','&#374;'=>'Y','&#376;'=>'Y',
1085                  '&#377;'=>'Z','&#381;'=>'Z','&#379;'=>'Z',
1086                  '&#222;'=>'T','&THORN;'=>'T',
1087                  '&#224;'=>'a','&#225;'=>'a','&#226;'=>'a','&#227;'=>'a','&#228;'=>'ae',
1088                  '&auml;'=>'ae',
1089                  '&#229;'=>'a','&#257;'=>'a','&#261;'=>'a','&#259;'=>'a','&aring;'=>'a',
1090                  '&#230;'=>'ae',
1091                  '&#231;'=>'c','&#263;'=>'c','&#269;'=>'c','&#265;'=>'c','&#267;'=>'c',
1092                  '&#271;'=>'d','&#273;'=>'d','&#240;'=>'d',
1093                  '&#232;'=>'e','&#233;'=>'e','&#234;'=>'e','&#235;'=>'e','&#275;'=>'e',
1094                  '&#281;'=>'e','&#283;'=>'e','&#277;'=>'e','&#279;'=>'e',
1095                  '&#402;'=>'f',
1096                  '&#285;'=>'g','&#287;'=>'g','&#289;'=>'g','&#291;'=>'g',
1097                  '&#293;'=>'h','&#295;'=>'h',
1098                  '&#236;'=>'i','&#237;'=>'i','&#238;'=>'i','&#239;'=>'i','&#299;'=>'i',
1099                  '&#297;'=>'i','&#301;'=>'i','&#303;'=>'i','&#305;'=>'i',
1100                  '&#307;'=>'ij',
1101                  '&#309;'=>'j',
1102                  '&#311;'=>'k','&#312;'=>'k',
1103                  '&#322;'=>'l','&#318;'=>'l','&#314;'=>'l','&#316;'=>'l','&#320;'=>'l',
1104                  '&#241;'=>'n','&#324;'=>'n','&#328;'=>'n','&#326;'=>'n','&#329;'=>'n',
1105                  '&#331;'=>'n',
1106                  '&#242;'=>'o','&#243;'=>'o','&#244;'=>'o','&#245;'=>'o','&#246;'=>'oe',
1107                  '&ouml;'=>'oe',
1108                  '&#248;'=>'o','&#333;'=>'o','&#337;'=>'o','&#335;'=>'o',
1109                  '&#339;'=>'oe',
1110                  '&#341;'=>'r','&#345;'=>'r','&#343;'=>'r',
1111                  '&#353;'=>'s',
1112                  '&#249;'=>'u','&#250;'=>'u','&#251;'=>'u','&#252;'=>'ue','&#363;'=>'u',
1113                  '&uuml;'=>'ue',
1114                  '&#367;'=>'u','&#369;'=>'u','&#365;'=>'u','&#361;'=>'u','&#371;'=>'u',
1115                  '&#373;'=>'w',
1116                  '&#253;'=>'y','&#255;'=>'y','&#375;'=>'y',
1117                  '&#382;'=>'z','&#380;'=>'z','&#378;'=>'z',
1118                  '&#254;'=>'t',
1119                  '&#223;'=>'ss',
1120                  '&#383;'=>'ss',
1121                  '&agrave;'=>'a','&aacute;'=>'a','&acirc;'=>'a','&atilde;'=>'a','&auml;'=>'ae',
1122                  '&aring;'=>'a','&aelig;'=>'ae','&ccedil;'=>'c','&eth;'=>'d',
1123                  '&egrave;'=>'e','&eacute;'=>'e','&ecirc;'=>'e','&euml;'=>'e',
1124                  '&igrave;'=>'i','&iacute;'=>'i','&icirc;'=>'i','&iuml;'=>'i',
1125                  '&ntilde;'=>'n',
1126                  '&ograve;'=>'o','&oacute;'=>'o','&ocirc;'=>'o','&otilde;'=>'o','&ouml;'=>'oe',
1127                  '&oslash;'=>'o',
1128                  '&ugrave;'=>'u','&uacute;'=>'u','&ucirc;'=>'u','&uuml;'=>'ue',
1129                  '&yacute;'=>'y','&yuml;'=>'y',
1130                  '&thorn;'=>'t',
1131                  '&szlig;'=>'ss'
1132              );
1133  
1134  
1135              if (is_file(txpath.'/lib/i18n-ascii.txt')) {
1136                  $i18n = parse_ini_file(txpath.'/lib/i18n-ascii.txt', true);
1137                  # load the global map
1138                  if (isset($i18n['default']) && is_array($i18n['default'])) {
1139                      $array[$lang] = array_merge($array[$lang], $i18n['default']);
1140                      # base language overrides: 'de-AT' applies the 'de' section
1141                      if (preg_match('/([a-zA-Z]+)-.+/', $lang, $m)) {
1142                          if (isset($i18n[$m[1]]) && is_array($i18n[$m[1]]))
1143                              $array[$lang] = array_merge($array[$lang], $i18n[$m[1]]);
1144                      };
1145                      # regional language overrides: 'de-AT' applies the 'de-AT' section
1146                      if (isset($i18n[$lang]) && is_array($i18n[$lang]))
1147                          $array[$lang] = array_merge($array[$lang], $i18n[$lang]);
1148                  }
1149                  # load an old file (no sections) just in case
1150                  else
1151                      $array[$lang] = array_merge($array[$lang], $i18n);
1152              }
1153          }
1154  
1155          return strtr($str, $array[$lang]);
1156      }
1157  
1158  // -------------------------------------------------------------
1159  	function clean_url($url)
1160      {
1161          return preg_replace("/\"|'|(?:\s.*$)/",'',$url);
1162      }
1163  
1164  // -------------------------------------------------------------
1165  	function noWidow($str)
1166      {
1167          // replace the last space with a nbsp
1168          if (REGEXP_UTF8 == 1)
1169              return preg_replace('@[ ]+([[:punct:]]?[\p{L}\p{N}\p{Pc}]+[[:punct:]]?)$@u', '&#160;$1', rtrim($str));
1170          return preg_replace('@[ ]+([[:punct:]]?\w+[[:punct:]]?)$@', '&#160;$1', rtrim($str));
1171      }
1172  
1173  // -------------------------------------------------------------
1174  	function is_blacklisted($ip, $checks = '')
1175      {
1176          global $prefs;
1177  
1178          if (!$checks)
1179          {
1180              $checks = do_list($prefs['spam_blacklists']);
1181          }
1182  
1183          $rip = join('.', array_reverse(explode('.', $ip)));
1184  
1185          foreach ($checks as $a)
1186          {
1187              $parts = explode(':', $a, 2);
1188              $rbl   = $parts[0];
1189  
1190              if (isset($parts[1]))
1191              {
1192                  foreach (explode(':', $parts[1]) as $code)
1193                  {
1194                      $codes[] = strpos($code, '.') ? $code : '127.0.0.'.$code;
1195                  }
1196              }
1197  
1198              $hosts = $rbl ? @gethostbynamel($rip.'.'.trim($rbl, '. ').'.') : FALSE;
1199  
1200              if ($hosts and (!isset($codes) or array_intersect($hosts, $codes)))
1201              {
1202                  $listed[] = $rbl;
1203              }
1204          }
1205  
1206          return (!empty($listed)) ? join(', ', $listed) : false;
1207      }
1208  
1209  // -------------------------------------------------------------
1210  	function is_logged_in($user = '')
1211      {
1212          $name = substr(cs('txp_login_public'), 10);
1213  
1214          if (!strlen($name) or strlen($user) and $user !== $name)
1215          {
1216              return FALSE;
1217          }
1218  
1219          $rs = safe_row('nonce, name, RealName, email, privs', 'txp_users', "name = '".doSlash($name)."'");
1220  
1221          if ($rs and substr(md5($rs['nonce']), -10) === substr(cs('txp_login_public'), 0, 10))
1222          {
1223              unset($rs['nonce']);
1224              return $rs;
1225          }
1226          else
1227          {
1228              return FALSE;
1229          }
1230      }
1231  
1232  // -------------------------------------------------------------
1233  	function updateSitePath($here)
1234      {
1235          $here = doSlash($here);
1236          $rs = safe_field ("name",'txp_prefs',"name = 'path_to_site'");
1237          if (!$rs) {
1238              safe_insert("txp_prefs","prefs_id=1,name='path_to_site',val='$here'");
1239          } else {
1240              safe_update('txp_prefs',"val='$here'","name='path_to_site'");
1241          }
1242      }
1243  
1244  // -------------------------------------------------------------
1245  	function splat($text)
1246      {
1247          $atts  = array();
1248  
1249          if (preg_match_all('@(\w+)\s*=\s*(?:"((?:[^"]|"")*)"|\'((?:[^\']|\'\')*)\'|([^\s\'"/>]+))@s', $text, $match, PREG_SET_ORDER))
1250          {
1251              foreach ($match as $m)
1252              {
1253                  switch (count($m))
1254                  {
1255                      case 3:
1256                          $val = str_replace('""', '"', $m[2]);
1257                          break;
1258                      case 4:
1259                          $val = str_replace("''", "'", $m[3]);
1260  
1261                          if (strpos($m[3], '<txp:') !== FALSE)
1262                          {
1263                              trace_add("[attribute '".$m[1]."']");
1264                              $val = parse($val);
1265                              trace_add("[/attribute]");
1266                          }
1267  
1268                          break;
1269                      case 5:
1270                          $val = $m[4];
1271                          trigger_error(gTxt('attribute_values_must_be_quoted'), E_USER_WARNING);
1272                          break;
1273                  }
1274  
1275                  $atts[strtolower($m[1])] = $val;
1276              }
1277  
1278          }
1279  
1280          return $atts;
1281      }
1282  
1283  // -------------------------------------------------------------
1284  	function maxMemUsage($message = 'none', $returnit = 0)
1285      {
1286          static $memory_top = 0;
1287          static $memory_message;
1288  
1289          if (is_callable('memory_get_usage'))
1290          {
1291              $memory_now = memory_get_usage();
1292              if ($memory_now > $memory_top)
1293              {
1294                  $memory_top = $memory_now;
1295                  $memory_message = $message;
1296              }
1297          }
1298  
1299          if ($returnit != 0)
1300          {
1301              if (is_callable('memory_get_usage'))
1302                  return n.comment(sprintf('Memory: %sKb, %s',
1303                      ceil($memory_top/1024),$memory_message));
1304              else
1305                  return n.comment('Memory: no info available');
1306          }
1307      }
1308  
1309  // -------------------------------------------------------------
1310  	function strip_rn($str)
1311      {
1312          return strtr($str, "\r\n", '  ');
1313      }
1314  
1315  // -------------------------------------------------------------
1316  
1317  	function is_valid_email($address)
1318      {
1319          return preg_match('/^[a-z0-9](\.?[a-z0-9_+%-])*@([a-z0-9](-*[a-z0-9])*\.)+[a-z]{2,6}$/i', $address);
1320      }
1321  
1322  // -------------------------------------------------------------
1323  
1324  	function txpMail($to_address, $subject, $body, $reply_to = null)
1325      {
1326          global $txp_user, $prefs;
1327  
1328          // if mailing isn't possible, don't even try
1329          if (is_disabled('mail'))
1330          {
1331              return false;
1332          }
1333  
1334          // Likely sending passwords
1335          if (isset($txp_user))
1336          {
1337              if (is_valid_email($prefs['publisher_email'])) {
1338                  // explicit publisher email address preferred
1339                  $RealName = safe_field('RealName', 'txp_users', "name = '".doSlash($txp_user)."'");
1340                  $email = $prefs['publisher_email'];
1341              } else {
1342                  // default: current user invites new users using her personal email address
1343                  extract(safe_row('RealName, email', 'txp_users', "name = '".doSlash($txp_user)."'"));
1344              }
1345          }
1346  
1347          // Likely sending comments -> "to" equals "from"
1348          else
1349          {
1350              extract(safe_row('RealName, email', 'txp_users', "email = '".doSlash($to_address)."'"));
1351          }
1352  
1353          if ($prefs['override_emailcharset'] and is_callable('utf8_decode'))
1354          {
1355              $charset = 'ISO-8859-1';
1356  
1357              $RealName = utf8_decode($RealName);
1358              $subject = utf8_decode($subject);
1359              $body = utf8_decode($body);
1360          }
1361  
1362          else
1363          {
1364              $charset = 'UTF-8';
1365          }
1366  
1367          $RealName = encode_mailheader(strip_rn($RealName), 'phrase');
1368          $subject = encode_mailheader(strip_rn($subject), 'text');
1369          $email = strip_rn($email);
1370  
1371          if (!is_null($reply_to))
1372          {
1373              $reply_to = strip_rn($reply_to);
1374          }
1375  
1376          $sep = !IS_WIN ? "\n" : "\r\n";
1377  
1378          $body = str_replace("\r\n", "\n", $body);
1379          $body = str_replace("\r", "\n", $body);
1380          $body = str_replace("\n", $sep, $body);
1381  
1382          $headers = "From: $RealName <$email>".
1383              $sep.'Reply-To: '.( isset($reply_to) ? $reply_to : "$RealName <$email>" ).
1384              $sep.'X-Mailer: Textpattern'.
1385              $sep.'Content-Transfer-Encoding: 8bit'.
1386              $sep.'Content-Type: text/plain; charset="'.$charset.'"'.
1387              $sep;
1388  
1389          if (is_valid_email($prefs['smtp_from']))
1390          {
1391              if (IS_WIN)
1392              {
1393                  ini_set('sendmail_from', $prefs['smtp_from']);
1394              }
1395              elseif (!ini_get('safe_mode'))
1396              {
1397                  return mail($to_address, $subject, $body, $headers, '-f'.$prefs['smtp_from']);
1398              }
1399          }
1400  
1401          return mail($to_address, $subject, $body, $headers);
1402      }
1403  
1404  // -------------------------------------------------------------
1405  	function encode_mailheader($string, $type)
1406      {
1407          global $prefs;
1408          if (!strstr($string,'=?') and !preg_match('/[\x00-\x1F\x7F-\xFF]/', $string)) {
1409              if ("phrase" == $type) {
1410                  if (preg_match('/[][()<>@,;:".\x5C]/', $string)) {
1411                      $string = '"'. strtr($string, array("\\" => "\\\\", '"' => '\"')) . '"';
1412                  }
1413              }
1414              elseif ( "text" != $type) {
1415                  trigger_error( 'Unknown encode_mailheader type', E_USER_WARNING);
1416              }
1417              return $string;
1418          }
1419          if ($prefs['override_emailcharset'] and is_callable('utf8_decode')) {
1420              $start = '=?ISO-8859-1?B?';
1421              $pcre  = '/.{1,42}/s';
1422          }
1423          else {
1424              $start = '=?UTF-8?B?';
1425              $pcre  = '/.{1,45}(?=[\x00-\x7F\xC0-\xFF]|$)/s';
1426          }
1427          $end = '?=';
1428          $sep = IS_WIN ? "\r\n" : "\n";
1429          preg_match_all($pcre, $string, $matches);
1430          return $start . join($end.$sep.' '.$start, array_map('base64_encode',$matches[0])) . $end;
1431      }
1432  
1433  // -------------------------------------------------------------
1434  	function stripPHP($in)
1435      {
1436          return preg_replace("/".chr(60)."\?(?:php)?|\?".chr(62)."/i",'',$in);
1437      }
1438  
1439  // -------------------------------------------------------------
1440  
1441  /**
1442   * PEDRO:
1443   * Helper functions for common textpattern event files actions.
1444   * Code refactoring from original files. Intended to do easy and less error
1445   * prone the future build of new textpattern extensions, and to add new
1446   * events to multiedit forms.
1447   */
1448  
1449   	function event_category_popup($name, $cat = '', $id = '')
1450      {
1451          $arr = array('');
1452          $rs = getTree('root', $name);
1453  
1454          if ($rs)
1455          {
1456              return treeSelectInput('category', $rs, $cat, $id);
1457          }
1458  
1459          return false;
1460      }
1461  
1462  // -------------------------------------------------------------
1463   	function event_change_pageby($name)
1464      {
1465          global $event;
1466          $qty = gps('qty');
1467          assert_int($qty);
1468          $pageby = $name.'_list_pageby';
1469          $GLOBALS[$pageby] = $qty;
1470  
1471          set_pref($pageby, $qty, $event, PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE);
1472  
1473          return;
1474      }
1475  
1476  // -------------------------------------------------------------
1477  // DEPRECATED in v4.5.0: use multi_edit() instead
1478  	function event_multiedit_form($name, $methods = null, $page, $sort, $dir, $crit, $search_method)
1479      {
1480          $method = ps('edit_method');
1481  
1482          if ($methods === NULL)
1483          {
1484              $methods = array(
1485                  'delete' => gTxt('delete')
1486              );
1487          }
1488  
1489          return '<label for="withselected">'.gTxt('with_selected').'</label>'.
1490              n.selectInput('edit_method', $methods, $method, 1, ' id="withselected" onchange="poweredit(this); return false;"').
1491              n.eInput($name).
1492              n.sInput($name.'_multi_edit').
1493              n.hInput('page', $page).
1494              ( $sort ? n.hInput('sort', $sort).n.hInput('dir', $dir) : '' ).
1495              ( ($crit != '') ? n.hInput('crit', $crit).n.hInput('search_method', $search_method) : '' ).
1496              n.fInput('submit', '', gTxt('go'));
1497      }
1498  
1499  // -------------------------------------------------------------
1500  
1501  	function event_multi_edit($table, $id_key)
1502      {
1503          $method = ps('edit_method');
1504          $selected = ps('selected');
1505  
1506          if ($selected)
1507          {
1508              if ($method == 'delete')
1509              {
1510                  foreach ($selected as $id)
1511                  {
1512                      $id = assert_int($id);
1513  
1514                      if (safe_delete($table, "$id_key = $id"))
1515                      {
1516                          $ids[] = $id;
1517                      }
1518                  }
1519  
1520                  return join(', ', $ids);
1521              }
1522          }
1523  
1524          return '';
1525      }
1526  
1527  // -------------------------------------------------------------
1528  	function since($stamp)
1529      {
1530          $diff = (time() - $stamp);
1531          if ($diff <= 3600) {
1532              $mins = round($diff / 60);
1533              $since = ($mins <= 1)
1534              ?    ($mins==1)
1535                  ?    '1 '.gTxt('minute')
1536                  :    gTxt('a_few_seconds')
1537              :    "$mins ".gTxt('minutes');
1538          } else if (($diff <= 86400) && ($diff > 3600)) {
1539              $hours = round($diff / 3600);
1540              $since = ($hours <= 1) ? '1 '.gTxt('hour') : "$hours ".gTxt('hours');
1541          } else if ($diff >= 86400) {
1542              $days = round($diff / 86400);
1543              $since = ($days <= 1) ? "1 ".gTxt('day') : "$days ".gTxt('days');
1544          }
1545          return $since.' '.gTxt('ago'); // sorry, this needs to be hacked until a truly multilingual version is done
1546      }
1547  
1548  // -------------------------------------------------------------
1549  // Calculate the offset between the server local time and the
1550  // user's selected time zone at a given point in time
1551  	function tz_offset($timestamp = NULL)
1552      {
1553          global $gmtoffset, $timezone_key;
1554  
1555          if (is_null($timestamp)) $timestamp = time();
1556  
1557          extract(getdate($timestamp));
1558          $serveroffset = gmmktime($hours,$minutes,0,$mon,$mday,$year) - mktime($hours,$minutes,0,$mon,$mday,$year);
1559  
1560          $real_dst = timezone::is_dst($timestamp, $timezone_key);
1561          return $gmtoffset - $serveroffset + ($real_dst ? 3600 : 0);
1562      }
1563  
1564  // -------------------------------------------------------------
1565  // Format a time, respecting the locale and local time zone,
1566  // and make sure the output string is safe for UTF-8
1567  	function safe_strftime($format, $time='', $gmt=0, $override_locale='')
1568      {
1569          global $locale;
1570          $old_locale = $locale;
1571  
1572          if (!$time)
1573              $time = time();
1574  
1575          # we could add some other formats here
1576          if ($format == 'iso8601' or $format == 'w3cdtf') {
1577              $format = '%Y-%m-%dT%H:%M:%SZ';
1578              $gmt = 1;
1579          }
1580          elseif ($format == 'rfc822') {
1581              $format = '%a, %d %b %Y %H:%M:%S GMT';
1582              $gmt = 1;
1583              $override_locale = 'en-gb';
1584          }
1585  
1586          if ($override_locale)
1587              getlocale($override_locale);
1588  
1589          if ($format == 'since')
1590              $str = since($time);
1591          elseif ($gmt)
1592              $str = gmstrftime($format, $time);
1593          else
1594              $str = strftime($format, $time + tz_offset($time));
1595  
1596          @list($lang, $charset) = explode('.', $locale);
1597          if (empty($charset))
1598              $charset = 'ISO-8859-1';
1599          elseif (IS_WIN and is_numeric($charset))
1600              // Score -1 for consistent naming conventions
1601              $charset = 'Windows-'.$charset;
1602  
1603          if ($charset != 'UTF-8' and $format != 'since') {
1604              $new = '';
1605              if (is_callable('iconv'))
1606                  $new = @iconv($charset, 'UTF-8', $str);
1607  
1608              if ($new)
1609                  $str = $new;
1610              elseif (is_callable('utf8_encode'))
1611                  $str = utf8_encode($str);
1612          }
1613  
1614          # revert to the old locale
1615          if ($override_locale)
1616              $locale = setlocale(LC_ALL, $old_locale);
1617  
1618          return $str;
1619      }
1620  
1621  // -------------------------------------------------------------
1622  // Convert a time string from the Textpattern time zone to GMT
1623  	function safe_strtotime($time_str)
1624      {
1625          $ts = strtotime($time_str);
1626          return strtotime($time_str, time() + tz_offset($ts)) - tz_offset($ts);
1627      }
1628  
1629  // -------------------------------------------------------------
1630  	function myErrorHandler($errno, $errstr, $errfile, $errline)
1631      {
1632          # error_reporting() returns 0 when the '@' suppression
1633          # operator is used
1634          if (!error_reporting())
1635              return;
1636  
1637          echo '<pre>'.n.n."$errno: $errstr in $errfile at line $errline\n";
1638          # Requires PHP 4.3
1639          if (is_callable('debug_backtrace')) {
1640              echo "Backtrace:\n";
1641              $trace = debug_backtrace();
1642              foreach($trace as $ent) {
1643                  if(isset($ent['file'])) echo $ent['file'].':';
1644                  if(isset($ent['function'])) {
1645                      echo $ent['function'].'(';
1646                      if(isset($ent['args'])) {
1647                          $args='';
1648                          foreach($ent['args'] as $arg) { $args.=$arg.','; }
1649                          echo rtrim($args,',');
1650                      }
1651                      echo ') ';
1652                  }
1653                  if(isset($ent['line'])) echo 'at line '.$ent['line'].' ';
1654                  if(isset($ent['file'])) echo 'in '.$ent['file'];
1655                  echo "\n";
1656              }
1657          }
1658          echo "</pre>";
1659      }
1660  
1661  // -------------------------------------------------------------
1662  	function find_temp_dir()
1663      {
1664          global $path_to_site, $img_dir;
1665  
1666          if (IS_WIN) {
1667              $guess = array(txpath.DS.'tmp', getenv('TMP'), getenv('TEMP'), getenv('SystemRoot').DS.'Temp', 'C:'.DS.'Temp', $path_to_site.DS.$img_dir);
1668              foreach ($guess as $k=>$v)
1669                  if (empty($v)) unset($guess[$k]);
1670          }
1671          else
1672              $guess = array(txpath.DS.'tmp', '', DS.'tmp', $path_to_site.DS.$img_dir);
1673  
1674          foreach ($guess as $dir) {
1675              $tf = @tempnam($dir, 'txp_');
1676              if ($tf) $tf = realpath($tf);
1677              if ($tf and file_exists($tf)) {
1678                  unlink($tf);
1679                  return dirname($tf);
1680              }
1681          }
1682  
1683          return false;
1684      }
1685  
1686  // -------------------------------------------------------------
1687  	function get_uploaded_file($f, $dest='')
1688      {
1689          global $tempdir;
1690  
1691          if (!is_uploaded_file($f))
1692              return false;
1693  
1694          if ($dest) {
1695              $newfile = $dest;
1696          }
1697          else {
1698              $newfile = tempnam($tempdir, 'txp_');
1699              if (!$newfile)
1700                  return false;
1701          }
1702  
1703          # $newfile is created by tempnam(), but move_uploaded_file
1704          # will overwrite it
1705          if (move_uploaded_file($f, $newfile))
1706              return $newfile;
1707      }
1708  
1709  // --------------------------------------------------------------
1710  	function set_error_level($level)
1711      {
1712  
1713          if ($level == 'debug') {
1714              // We need to violate/disable E_STRICT for PHP 4.x compatibility
1715              // E_STRICT bitmask calculation stems from the variations for E_ALL in PHP 4.x, 5.{0,1,2,3}, and 5.4+
1716              // E_STRICT is defined since PHP 5.x and is set in E_ALL in PHP 5.4
1717              error_reporting(E_ALL & ~(defined('E_STRICT') ? E_STRICT : 0));
1718          }
1719          elseif ($level == 'live') {
1720              // don't show errors on screen
1721              $suppress = E_WARNING | E_NOTICE;
1722              if (defined('E_STRICT') && (E_ALL & E_STRICT)) $suppress |= E_STRICT;
1723              if (defined('E_DEPRECATED')) $suppress |= E_DEPRECATED;
1724              error_reporting(E_ALL ^ $suppress);
1725              @ini_set("display_errors","1");
1726          }
1727          else {
1728              // default is 'testing': display everything except notices and strict
1729              error_reporting((E_ALL ^ E_NOTICE) & ~(defined('E_STRICT') ? E_STRICT : 0));
1730          }
1731      }
1732  
1733  
1734  // -------------------------------------------------------------
1735  	function shift_uploaded_file($f, $dest)
1736      {
1737          // Rename might not work, but it's worth a try
1738          if (@rename($f, $dest))
1739              return true;
1740  
1741          if (@copy($f, $dest)) {
1742              unlink($f);
1743              return true;
1744          }
1745      }
1746  // -------------------------------------------------------------
1747  	function upload_get_errormsg($err_code)
1748      {
1749          $msg = '';
1750          switch ($err_code)
1751          {
1752                  // Value: 0; There is no error, the file uploaded with success.
1753              case UPLOAD_ERR_OK         : $msg = '';break;
1754                  // Value: 1; The uploaded file exceeds the upload_max_filesize directive in php.ini.
1755              case UPLOAD_ERR_INI_SIZE   : $msg = gTxt('upload_err_ini_size');break;
1756                  // Value: 2; The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.
1757              case UPLOAD_ERR_FORM_SIZE  : $msg = gTxt('upload_err_form_size');break;
1758                  // Value: 3; The uploaded file was only partially uploaded.
1759              case UPLOAD_ERR_PARTIAL    : $msg = gTxt('upload_err_partial');break;
1760                  // Value: 4; No file was uploaded.
1761              case UPLOAD_ERR_NO_FILE    : $msg = gTxt('upload_err_no_file');break;
1762                  // Value: 6; Missing a temporary folder. Introduced in PHP 4.3.10 and PHP 5.0.3.
1763              case UPLOAD_ERR_NO_TMP_DIR : $msg = gTxt('upload_err_tmp_dir');break;
1764                  // Value: 7; Failed to write file to disk. Introduced in PHP 5.1.0.
1765              case UPLOAD_ERR_CANT_WRITE : $msg = gTxt('upload_err_cant_write');break;
1766                  // Value: 8; File upload stopped by extension. Introduced in PHP 5.2.0.
1767              case UPLOAD_ERR_EXTENSION  : $msg = gTxt('upload_err_extension');break;
1768          }
1769          return $msg;
1770      }
1771  
1772  /**
1773   * Formats a file size
1774   *
1775   * @param    int        $bytes        Size in bytes
1776   * @param    int        $decimals    Number of decimals
1777   * @param    string    $format        The format the size is represented
1778   * @return    string                Formatted file size
1779   */
1780  
1781  	function format_filesize($bytes, $decimals=2, $format='')
1782      {
1783          $units = array('b', 'k', 'm', 'g', 't', 'p', 'e', 'z', 'y');
1784  
1785          if (in_array($format, $units))
1786          {
1787              $pow = array_search($format, $units);
1788          }
1789          else
1790          {
1791              $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
1792              $pow = min($pow, count($units) - 1);
1793          }
1794  
1795          $bytes /= pow(1024, $pow);
1796  
1797          $separators = localeconv();
1798          $sep_dec = isset($separators['decimal_point']) ? $separators['decimal_point'] : '.';
1799          $sep_thous = isset($separators['thousands_sep']) ? $separators['thousands_sep'] : ',';
1800  
1801          return number_format($bytes, $decimals, $sep_dec, $sep_thous) . gTxt('units_' . $units[$pow]);
1802      }
1803  
1804  // -------------------------------------------------------------
1805      // for b/c only
1806  	function is_windows()
1807      {
1808          return IS_WIN;
1809      }
1810  
1811  // -------------------------------------------------------------
1812  	function is_cgi()
1813      {
1814          return IS_CGI;
1815      }
1816  
1817  // -------------------------------------------------------------
1818  	function is_mod_php()
1819      {
1820          return IS_APACHE;
1821      }
1822  
1823  // -------------------------------------------------------------
1824  
1825  	function is_disabled($function)
1826      {
1827          static $disabled;
1828  
1829          if (!isset($disabled))
1830          {
1831              $disabled = do_list(ini_get('disable_functions'));
1832          }
1833  
1834          return in_array($function, $disabled);
1835      }
1836  
1837  // --------------------------------------------------------------
1838  	function build_file_path($base,$path)
1839      {
1840          $base = rtrim($base,'/\\');
1841          $path = ltrim($path,'/\\');
1842  
1843          return $base.DIRECTORY_SEPARATOR.$path;
1844      }
1845  
1846  // --------------------------------------------------------------
1847  	function get_author_name($name)
1848      {
1849          static $authors = array();
1850  
1851          if (isset($authors[$name]))
1852              return $authors[$name];
1853  
1854          $realname = fetch('RealName','txp_users','name',doSlash($name));
1855          $authors[$name] = $realname;
1856          return ($realname) ? $realname : $name;
1857      }
1858  
1859  // --------------------------------------------------------------
1860  	function get_author_email($name)
1861      {
1862          static $authors = array();
1863  
1864          if (isset($authors[$name]))
1865              return $authors[$name];
1866  
1867          $email = fetch('email','txp_users','name',doSlash($name));
1868          $authors[$name] = $email;
1869          return $email;
1870      }
1871  
1872  // --------------------------------------------------------------
1873  	function has_single_author($table, $col='author')
1874      {
1875          return (safe_field('COUNT(name)', 'txp_users', '1=1') <= 1) &&
1876              (safe_field('COUNT(DISTINCT('.doSlash($col).'))', doSlash($table), '1=1') <= 1);
1877      }
1878  
1879  // --------------------------------------------------------------
1880  	function EvalElse($thing, $condition)
1881      {
1882          global $txp_current_tag;
1883          static $gTxtTrue = NULL, $gTxtFalse;
1884  
1885          if (empty($gTxtTrue))
1886          {
1887              $gTxtTrue = gTxt('true');
1888              $gTxtFalse = gTxt('false');
1889          }
1890  
1891          trace_add("[$txp_current_tag: ".($condition ? $gTxtTrue : $gTxtFalse)."]");
1892  
1893          $els = strpos($thing, '<txp:else');
1894  
1895          if ($els === FALSE)
1896          {
1897              return $condition ? $thing : '';
1898          }
1899          elseif ($els === strpos($thing, '<txp:'))
1900          {
1901              return $condition
1902                  ? substr($thing, 0, $els)
1903                  : substr($thing, strpos($thing, '>', $els) + 1);
1904          }
1905  
1906          $tag    = FALSE;
1907          $level  = 0;
1908          $str    = '';
1909          $regex  = '@(</?txp:\w+(?:\s+\w+\s*=\s*(?:"(?:[^"]|"")*"|\'(?:[^\']|\'\')*\'|[^\s\'"/>]+))*\s*/?'.chr(62).')@s';
1910          $parsed = preg_split($regex, $thing, -1, PREG_SPLIT_DELIM_CAPTURE);
1911  
1912          foreach ($parsed as $chunk)
1913          {
1914              if ($tag)
1915              {
1916                  if ($level === 0 and strpos($chunk, 'else') === 5 and substr($chunk, -2, 1) === '/')
1917                  {
1918                      return $condition
1919                          ? $str
1920                          : substr($thing, strlen($str)+strlen($chunk));
1921                  }
1922                  elseif (substr($chunk, 1, 1) === '/')
1923                  {
1924                      $level--;
1925                  }
1926                  elseif (substr($chunk, -2, 1) !== '/')
1927                  {
1928                      $level++;
1929                  }
1930              }
1931  
1932              $tag = !$tag;
1933              $str .= $chunk;
1934          }
1935  
1936          return $condition ? $thing : '';
1937      }
1938  
1939  // --------------------------------------------------------------
1940  	function fetch_form($name)
1941      {
1942          static $forms = array();
1943  
1944          if (isset($forms[$name]))
1945              $f = $forms[$name];
1946          else {
1947              $row = safe_row('Form', 'txp_form',"name='".doSlash($name)."'");
1948              if (!$row) {
1949                  trigger_error(gTxt('form_not_found').': '.$name);
1950                  return;
1951              }
1952              $f = $row['Form'];
1953              $forms[$name] = $f;
1954          }
1955  
1956          trace_add('['.gTxt('form').': '.$name.']');
1957          return $f;
1958      }
1959  
1960  // --------------------------------------------------------------
1961  	function parse_form($name)
1962      {
1963          global $txp_current_form;
1964          static $stack = array();
1965  
1966          $f = fetch_form($name);
1967          if ($f) {
1968              if (in_array($name, $stack)) {
1969                  trigger_error(gTxt('form_circular_reference', array('{name}' => $name)));
1970                  return;
1971              }
1972              $old_form = $txp_current_form;
1973              $txp_current_form = $stack[] = $name;
1974              $out = parse($f);
1975              $txp_current_form = $old_form;
1976              array_pop($stack);
1977              return $out;
1978          }
1979      }
1980  
1981  // --------------------------------------------------------------
1982  	function fetch_category_title($name, $type='article')
1983      {
1984          static $cattitles = array();
1985          global $thiscategory;
1986  
1987          if (isset($cattitles[$type][$name]))
1988              return $cattitles[$type][$name];
1989  
1990          if(!empty($thiscategory['title']) && $thiscategory['name'] == $name && $thiscategory['type'] == $type)
1991          {
1992              $cattitles[$type][$name] = $thiscategory['title'];
1993              return $thiscategory['title'];
1994          }
1995  
1996          $f = safe_field('title','txp_category',"name='".doSlash($name)."' and type='".doSlash($type)."'");
1997          $cattitles[$type][$name] = $f;
1998          return $f;
1999      }
2000  
2001  // -------------------------------------------------------------
2002  	function fetch_section_title($name)
2003      {
2004          static $sectitles = array();
2005          global $thissection;
2006  
2007          // try cache
2008          if (isset($sectitles[$name]))
2009              return $sectitles[$name];
2010  
2011          // try global set by section_list()
2012          if(!empty($thissection['title']) && $thissection['name'] == $name)
2013          {
2014              $sectitles[$name] = $thissection['title'];
2015              return $thissection['title'];
2016          }
2017  
2018          if($name == 'default' or empty($name))
2019              return '';
2020  
2021          $f = safe_field('title','txp_section',"name='".doSlash($name)."'");
2022          $sectitles[$name] = $f;
2023          return $f;
2024      }
2025  
2026  // -------------------------------------------------------------
2027  	function update_comments_count($id)
2028      {
2029          $id = assert_int($id);
2030          $thecount = safe_field('count(*)','txp_discuss','parentid='.$id.' and visible='.VISIBLE);
2031          $thecount = assert_int($thecount);
2032          $updated = safe_update('textpattern','comments_count='.$thecount,'ID='.$id);
2033          return ($updated) ? true : false;
2034      }
2035  
2036  // -------------------------------------------------------------
2037  	function clean_comment_counts($parentids)
2038      {
2039          $parentids = array_map('assert_int',$parentids);
2040          $rs = safe_rows_start('parentid, count(*) as thecount','txp_discuss','parentid IN ('.implode(',',$parentids).') AND visible='.VISIBLE.' group by parentid');
2041          if (!$rs) return;
2042  
2043          $updated = array();
2044          while($a = nextRow($rs)) {
2045              safe_update('textpattern',"comments_count=".$a['thecount'],"ID=".$a['parentid']);
2046              $updated[] = $a['parentid'];
2047          }
2048          // We still need to update all those, that have zero comments left.
2049          $leftover = array_diff($parentids, $updated);
2050          if ($leftover)
2051              safe_update('textpattern',"comments_count=0","ID IN (".implode(',',$leftover).")");
2052      }
2053  
2054  // -------------------------------------------------------------
2055  
2056  	function markup_comment($msg)
2057      {
2058          global $prefs;
2059  
2060          $disallow_images = !empty($prefs['comments_disallow_images']);
2061          $lite = empty($prefs['comments_use_fat_textile']);
2062  
2063          $rel = !empty($prefs['comment_nofollow']) ? 'nofollow' : '';
2064  
2065          include_once txpath.'/lib/classTextile.php';
2066  
2067          $textile = new Textile($prefs['doctype']);
2068  
2069          return $textile->TextileRestricted($msg, $lite, $disallow_images, $rel);
2070      }
2071  
2072  //-------------------------------------------------------------
2073  	function update_lastmod() {
2074          safe_upsert("txp_prefs", "val = now()", "name = 'lastmod'");
2075      }
2076  
2077  //-------------------------------------------------------------
2078  	function get_lastmod($unix_ts=NULL) {
2079          global $prefs;
2080  
2081          if ($unix_ts === NULL)
2082              $unix_ts = @strtotime($prefs['lastmod']);
2083  
2084          # check for future articles that are now visible
2085          if ($max_article = safe_field('unix_timestamp(Posted)', 'textpattern', "Posted <= now() and Status >= 4 order by Posted desc limit 1")) {
2086              $unix_ts = max($unix_ts, $max_article);
2087          }
2088  
2089          return $unix_ts;
2090      }
2091  
2092  //-------------------------------------------------------------
2093  	function handle_lastmod($unix_ts=NULL, $exit=1) {
2094          global $prefs;
2095          extract($prefs);
2096  
2097          if($send_lastmod and $production_status == 'live') {
2098              $unix_ts = get_lastmod($unix_ts);
2099  
2100              # make sure lastmod isn't in the future
2101              $unix_ts = min($unix_ts, time());
2102              # or too far in the past (7 days)
2103              $unix_ts = max($unix_ts, time() - 3600*24*7);
2104  
2105              $last = safe_strftime('rfc822', $unix_ts, 1);
2106              header("Last-Modified: $last");
2107              header('Cache-Control: no-cache');
2108  
2109              $hims = serverset('HTTP_IF_MODIFIED_SINCE');
2110              if ($hims and @strtotime($hims) >= $unix_ts) {
2111                  log_hit('304');
2112                  if (!$exit)
2113                      return array('304', $last);
2114                  txp_status_header('304 Not Modified');
2115                  # some mod_deflate versions have a bug that breaks subsequent
2116                  # requests when keepalive is used.  dropping the connection
2117                  # is the only reliable way to fix this.
2118                  if (empty($lastmod_keepalive))
2119                      header('Connection: close');
2120                  header('Content-Length: 0');
2121                  # discard all output
2122                  while (@ob_end_clean());
2123                  exit;
2124              }
2125  
2126              if (!$exit)
2127                  return array('200', $last);
2128          }
2129      }
2130  
2131  //-------------------------------------------------------------
2132  	function set_pref($name, $val, $event='publish',  $type=0, $html='text_input', $position=0, $is_private=PREF_GLOBAL)
2133      {
2134          global $txp_user;
2135          extract(doSlash(func_get_args()));
2136  
2137          $user_name = '';
2138          if ($is_private == PREF_PRIVATE) {
2139              if (empty($txp_user))
2140                  return false;
2141  
2142              $user_name = 'user_name = \''.doSlash($txp_user).'\'';
2143          }
2144  
2145          if (!safe_row('name', 'txp_prefs', "name = '$name'" . ($user_name ? " AND $user_name" : ''))) {
2146              $user_name = ($user_name ? "$user_name," : '');
2147              return safe_insert('txp_prefs', "
2148                  name  = '$name',
2149                  val   = '$val',
2150                  event = '$event',
2151                  html  = '$html',
2152                  type  = '$type',
2153                  position = '$position',
2154                  $user_name
2155                  prefs_id = 1"
2156              );
2157          } else {
2158              return safe_update('txp_prefs', "val = '$val'","name like '$name'" . ($user_name ? " AND $user_name" : ''));
2159          }
2160      }
2161  
2162  //-------------------------------------------------------------
2163  	function get_pref($thing, $default='', $from_db=0) // checks $prefs for a named variable, or creates a default
2164      {
2165          global $prefs, $txp_user;
2166  
2167          if ($from_db)
2168          {
2169              $name = doSlash($thing);
2170              $user_name = doSlash($txp_user);
2171              // prefer system prefs over user's prefs
2172              $field = safe_field('val', 'txp_prefs',
2173                                  "name='$name' AND (user_name='' OR user_name='$user_name') order by user_name limit 1");
2174              if ($field !== false)
2175              {
2176                  $prefs[$thing] = $field;
2177              }
2178          }
2179          return (isset($prefs[$thing])) ? $prefs[$thing] : $default;
2180      }
2181  
2182  // -------------------------------------------------------------
2183  	function getCustomFields()
2184      {
2185          global $prefs;
2186          static $out = NULL;
2187  
2188          // have cache?
2189          if (!is_array($out))
2190          {
2191              $cfs = preg_grep('/^custom_\d+_set/', array_keys($prefs));
2192  
2193              $out = array();
2194              foreach ($cfs as $name) {
2195                  preg_match('/(\d+)/', $name, $match);
2196                  if (!empty($prefs[$name])) {
2197                      $out[$match[1]] = strtolower($prefs[$name]);
2198                  }
2199              }
2200          }
2201          return $out;
2202      }
2203  
2204  /**
2205   * Build a query qualifier to filter non-matching custom fields from the result set
2206   *
2207   * @param array $custom     An array of 'custom_field_name' => field_number tupels
2208   * @param array $pairs         Filter criteria: An array of 'name' => value tupels
2209   * @return bool|string         A SQL qualifier for a querys 'WHERE' part
2210   */
2211  // -------------------------------------------------------------
2212  	function buildCustomSql($custom,$pairs)
2213      {
2214          if ($pairs) {
2215              $pairs = doSlash($pairs);
2216              foreach($pairs as $k => $v) {
2217                  if(in_array($k,$custom)) {
2218                      $no = array_keys($custom,$k);
2219                      # nb - use 'like' here to allow substring matches
2220                      $out[] = "and custom_".$no[0]." like '$v'";
2221                  }
2222              }
2223          }
2224          return (!empty($out)) ? ' '.join(' ',$out).' ' : false;
2225      }
2226  
2227  // -------------------------------------------------------------
2228  	function txp_status_header($status='200 OK')
2229      {
2230          if (IS_FASTCGI)
2231              header("Status: $status");
2232          elseif ($_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.0')
2233              header("HTTP/1.0 $status");
2234          else
2235              header("HTTP/1.1 $status");
2236      }
2237  
2238  // -------------------------------------------------------------
2239  	function txp_die($msg, $status='503', $url='')
2240      {
2241          // 503 status might discourage search engines from indexing or caching the error message
2242  
2243          //Make it possible to call this function as a tag, e.g. in an article <txp:txp_die status="410" />
2244          if (is_array($msg))
2245              extract(lAtts(array('msg' => '', 'status' => '503', 'url' => ''),$msg));
2246  
2247          // Intentionally incomplete - just the ones we're likely to use
2248          $codes = array(
2249              '200' => 'OK',
2250              '301' => 'Moved Permanently',
2251              '302' => 'Found',
2252              '304' => 'Not Modified',
2253              '307' => 'Temporary Redirect',
2254              '401' => 'Unauthorized',
2255              '403' => 'Forbidden',
2256              '404' => 'Not Found',
2257              '410' => 'Gone',
2258              '414' => 'Request-URI Too Long',
2259              '500' => 'Internal Server Error',
2260              '501' => 'Not Implemented',
2261              '503' => 'Service Unavailable',
2262          );
2263  
2264          if ($status) {
2265              if (isset($codes[strval($status)]))
2266                  $status = strval($status) . ' ' . $codes[$status];
2267  
2268              txp_status_header($status);
2269          }
2270  
2271          $code = '';
2272          if ($status and $parts = @explode(' ', $status, 2)) {
2273              $code = @$parts[0];
2274          }
2275  
2276          callback_event('txp_die', $code);
2277  
2278          // redirect with status
2279          if ($url && in_array($code, array(301, 302, 307))) {
2280              ob_end_clean();
2281              header("Location: $url", true, $code);
2282              die('<html><head><meta http-equiv="refresh" content="0;URL='.txpspecialchars($url).'"></head><body></body></html>');
2283          }
2284  
2285          if (@$GLOBALS['connected'] && @txpinterface == 'public') {
2286              $out = safe_field('user_html','txp_page',"name='error_".doSlash($code)."'");
2287              if ($out === false)
2288                  $out = safe_field('user_html','txp_page',"name='error_default'");
2289          }
2290  
2291          if (!isset($out))
2292              $out = <<<eod
2293  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2294          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2295  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
2296  <head>
2297     <meta http-equiv="content-type" content="text/html; charset=utf-8" />
2298     <title>Textpattern Error: <txp:error_status /></title>
2299  </head>
2300  <body>
2301  <p align="center" style="margin-top:4em"><txp:error_message /></p>
2302  </body>
2303  </html>
2304  eod;
2305  
2306          header("Content-type: text/html; charset=utf-8");
2307  
2308          if (is_callable('parse')) {
2309  
2310              $GLOBALS['txp_error_message'] = $msg;
2311              $GLOBALS['txp_error_status'] = $status;
2312              $GLOBALS['txp_error_code'] = $code;
2313  
2314              set_error_handler("tagErrorHandler");
2315              die(parse($out));
2316          }
2317          else {
2318              $out = preg_replace(array('@<txp:error_status[^>]*/>@', '@<txp:error_message[^>]*/>@'),
2319                  array($status, $msg),
2320                  $out);
2321              die($out);
2322          }
2323      }
2324  
2325  // -------------------------------------------------------------
2326  	function join_qs($q)
2327      {
2328          $qs = array();
2329          foreach ($q as $k=>$v)
2330          {
2331              if (is_array($v))
2332              {
2333                  $v = join(',', $v);
2334              }
2335              if ($v)
2336              {
2337                  $qs[] = urlencode($k) . '=' . urlencode($v);
2338              }
2339          }
2340  
2341          $str = join('&amp;', $qs);
2342          return ($str ? '?'.$str : '');
2343      }
2344  
2345  // -------------------------------------------------------------
2346  
2347  	function pagelinkurl($parts, $inherit = array())
2348      {
2349          global $permlink_mode, $prefs;
2350  
2351          // $inherit can be used to add parameters to an existing url, e.g:
2352          // $url = pagelinkurl(array('pg'=>2), $pretext);
2353          $keys = array_merge($inherit, $parts);
2354  
2355          if (isset($prefs['custom_url_func'])
2356              and is_callable($prefs['custom_url_func'])
2357              and ($url = call_user_func($prefs['custom_url_func'], $keys, PAGELINKURL)) !== FALSE)
2358          {
2359              return $url;
2360          }
2361  
2362          // can't use this to link to an article
2363          if (isset($keys['id']))
2364          {
2365              unset($keys['id']);
2366          }
2367  
2368          if (isset($keys['s']) && $keys['s'] == 'default')
2369          {
2370              unset($keys['s']);
2371          }
2372  
2373          // 'article' context is implicit, no need to add it to the page URL
2374          if (isset($keys['context']) && $keys['context'] == 'article')
2375          {
2376              unset($keys['context']);
2377          }
2378  
2379          if ($permlink_mode == 'messy')
2380          {
2381              if (!empty($keys['context'])) {
2382                  $keys['context'] = gTxt($keys['context'].'_context');
2383              }
2384              return hu.'index.php'.join_qs($keys);
2385          }
2386  
2387          else
2388          {
2389              // all clean URL modes use the same schemes for list pages
2390              $url = '';
2391  
2392              if (!empty($keys['rss']))
2393              {
2394                  $url = hu.'rss/';
2395                  unset($keys['rss']);
2396                  return $url.join_qs($keys);
2397              }
2398  
2399              elseif (!empty($keys['atom']))
2400              {
2401                  $url = hu.'atom/';
2402                  unset($keys['atom']);
2403                  return $url.join_qs($keys);
2404              }
2405  
2406              elseif (!empty($keys['s']))
2407              {
2408                  if (!empty($keys['context'])) {
2409                      $keys['context'] = gTxt($keys['context'].'_context');
2410                  }
2411                  $url = hu.urlencode($keys['s']).'/';
2412                  unset($keys['s']);
2413                  return $url.join_qs($keys);
2414              }
2415  
2416              elseif (!empty($keys['author']))
2417              {
2418                  $ct = empty($keys['context']) ? '' : strtolower(urlencode(gTxt($keys['context'].'_context'))).'/';
2419                  $url = hu.strtolower(urlencode(gTxt('author'))).'/'.$ct.urlencode($keys['author']).'/';
2420                  unset($keys['author'], $keys['context']);
2421                  return $url.join_qs($keys);
2422              }
2423  
2424              elseif (!empty($keys['c']))
2425              {
2426                  $ct = empty($keys['context']) ? '' : strtolower(urlencode(gTxt($keys['context'].'_context'))).'/';
2427                  $url = hu.strtolower(urlencode(gTxt('category'))).'/'.$ct.urlencode($keys['c']).'/';
2428                  unset($keys['c'], $keys['context']);
2429                  return $url.join_qs($keys);
2430              }
2431  
2432              return hu.join_qs($keys);
2433          }
2434      }
2435  
2436  // -------------------------------------------------------------
2437  	function filedownloadurl($id, $filename='')
2438      {
2439          global $permlink_mode;
2440  
2441          $filename = urlencode($filename);
2442          #FIXME: work around yet another mod_deflate problem (double compression)
2443          # http://blogs.msdn.com/wndp/archive/2006/08/21/Content-Encoding-not-equal-Content-Type.aspx
2444          if (preg_match('/gz$/i', $filename))
2445              $filename .= a;
2446          return ($permlink_mode == 'messy') ?
2447              hu.'index.php?s=file_download'.a.'id='.$id :
2448              hu.gTxt('file_download').'/'.$id.($filename ? '/'.$filename : '');
2449      }
2450  
2451  // -------------------------------------------------------------
2452  	function imagesrcurl($id, $ext, $thumbnail = false)
2453      {
2454          global $img_dir;
2455          $thumbnail = ($thumbnail ? 't' : '');
2456          return ihu.$img_dir.'/'.$id.$thumbnail.$ext;
2457      }
2458  
2459  // -------------------------------------------------------------
2460  
2461  	function in_list($val, $list, $delim = ',')
2462      {
2463          $args = do_list($list, $delim);
2464  
2465          return in_array($val, $args);
2466      }
2467  
2468  // -------------------------------------------------------------
2469  
2470  	function do_list($list, $delim = ',')
2471      {
2472          return array_map('trim', explode($delim, $list));
2473      }
2474  
2475  // -------------------------------------------------------------
2476  	function doQuote($val)
2477      {
2478          return "'$val'";
2479      }
2480  
2481  // -------------------------------------------------------------
2482  	function quote_list($in)
2483      {
2484          $out = doSlash($in);
2485          return doArray($out, 'doQuote');
2486      }
2487  
2488  // -------------------------------------------------------------
2489  	function trace_add($msg)
2490      {
2491          global $production_status;
2492  
2493          if ($production_status === 'debug')
2494          {
2495              global $txptrace,$txptracelevel;
2496  
2497              $txptrace[] = str_repeat("\t", $txptracelevel).$msg;
2498          }
2499      }
2500  
2501  //-------------------------------------------------------------
2502  	function article_push() {
2503          global $thisarticle, $stack_article;
2504          $stack_article[] = @$thisarticle;
2505      }
2506  
2507  //-------------------------------------------------------------
2508  	function article_pop() {
2509          global $thisarticle, $stack_article;
2510          $thisarticle = array_pop($stack_article);
2511      }
2512  // -------------------------------------------------------------
2513  
2514  	function relative_path($path, $pfx=NULL)
2515      {
2516          if ($pfx === NULL)
2517              $pfx = dirname(txpath);
2518          return preg_replace('@^/'.preg_quote(ltrim($pfx, '/'), '@').'/?@', '', $path);
2519      }
2520  
2521  // -------------------------------------------------------------
2522  	function get_caller($num=1,$start=2)
2523      {
2524          $out = array();
2525          if (!is_callable('debug_backtrace'))
2526              return $out;
2527  
2528          $bt = debug_backtrace();
2529          for ($i=$start; $i< $num+$start; $i++) {
2530              if (!empty($bt[$i])) {
2531                  $t = '';
2532                  if (!empty($bt[$i]['file']))
2533                      $t .= relative_path($bt[$i]['file']);
2534                  if (!empty($bt[$i]['line']))
2535                      $t .= ':'.$bt[$i]['line'];
2536                  if ($t)
2537                      $t .= ' ';
2538                  if (!empty($bt[$i]['class']))
2539                      $t .= $bt[$i]['class'];
2540                  if (!empty($bt[$i]['type']))
2541                      $t .= $bt[$i]['type'];
2542                  if (!empty($bt[$i]['function'])) {
2543                      $t .= $bt[$i]['function'];
2544  
2545                  $t .= '()';
2546                  }
2547  
2548  
2549                  $out[] = $t;
2550              }
2551          }
2552          return $out;
2553      }
2554  
2555  //-------------------------------------------------------------
2556  // function name is misleading but remains for legacy reasons
2557  // this actually sets the locale
2558  	function getlocale($lang) {
2559          global $locale;
2560  
2561          if (empty($locale))
2562              $locale = @setlocale(LC_TIME, '0');
2563  
2564          // Locale identifiers vary from system to system.  The
2565          // following code will attempt to discover which identifiers
2566          // are available.  We'll need to expand these lists to
2567          // improve support.
2568          // ISO identifiers: http://www.w3.org/WAI/ER/IG/ert/iso639.htm
2569          // Windows: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt_language_strings.asp
2570          $guesses = array(
2571              'ar-dz' => array('ar_DZ.UTF-8', 'ar_DZ', 'ara', 'ar', 'arabic', 'ar_DZ.ISO_8859-6'),
2572              'bg-bg' => array('bg_BG.UTF-8', 'bg_BG', 'bg', 'bul', 'bulgarian', 'bg_BG.ISO8859-5'),
2573              'ca-es' => array('ca_ES.UTF-8', 'ca_ES', 'cat', 'ca', 'catalan', 'ca_ES.ISO_8859-1'),
2574              'cs-cz' => array('cs_CZ.UTF-8', 'cs_CZ', 'ces', 'cze', 'cs', 'csy', 'czech', 'cs_CZ.cs_CZ.ISO_8859-2'),
2575              'da-dk' => array('da_DK.UTF-8', 'da_DK'),
2576              'de-de' => array('de_DE.UTF-8', 'de_DE', 'de', 'deu', 'german', 'de_DE.ISO_8859-1'),
2577              'en-gb' => array('en_GB.UTF-8', 'en_GB', 'en_UK', 'eng', 'en', 'english-uk', 'english', 'en_GB.ISO_8859-1','C'),
2578              'en-us' => array('en_US.UTF-8', 'en_US', 'english-us', 'en_US.ISO_8859-1'),
2579              'es-es' => array('es_ES.UTF-8', 'es_ES', 'esp', 'spanish', 'es_ES.ISO_8859-1'),
2580              'et-ee' => array('et_EE.UTF-8', 'et_EE'),
2581              'el-gr' => array('el_GR.UTF-8', 'el_GR', 'el', 'gre', 'greek', 'el_GR.ISO_8859-7'),
2582              'fi-fi' => array('fi_FI.UTF-8', 'fi_FI', 'fin', 'fi', 'finnish', 'fi_FI.ISO_8859-1'),
2583              'fr-fr' => array('fr_FR.UTF-8', 'fr_FR', 'fra', 'fre', 'fr', 'french', 'fr_FR.ISO_8859-1'),
2584              'gl-gz' => array('gl_GZ.UTF-8', 'gl_GZ', 'glg', 'gl', '', ''),
2585              'he_il' => array('he_IL.UTF-8', 'he_IL', 'heb', 'he', 'hebrew', 'he_IL.ISO_8859-8'),
2586              'hr-hr' => array('hr_HR.UTF-8', 'hr_HR', 'hr'),
2587              'hu-hu' => array('hu_HU.UTF-8', 'hu_HU', 'hun', 'hu', 'hungarian', 'hu_HU.ISO8859-2'),
2588              'id-id' => array('id_ID.UTF-8', 'id_ID', 'id', 'ind', 'indonesian','id_ID.ISO_8859-1'),
2589              'is-is' => array('is_IS.UTF-8', 'is_IS'),
2590              'it-it' => array('it_IT.UTF-8', 'it_IT', 'it', 'ita', 'italian', 'it_IT.ISO_8859-1'),
2591              'ja-jp' => array('ja_JP.UTF-8', 'ja_JP', 'ja', 'jpn', 'japanese', 'ja_JP.ISO_8859-1'),
2592              'ko-kr' => array('ko_KR.UTF-8', 'ko_KR', 'ko', 'kor', 'korean'),
2593              'lv-lv' => array('lv_LV.UTF-8', 'lv_LV', 'lv', 'lav'),
2594              'nl-nl' => array('nl_NL.UTF-8', 'nl_NL', 'dut', 'nla', 'nl', 'nld', 'dutch', 'nl_NL.ISO_8859-1'),
2595              'no-no' => array('no_NO.UTF-8', 'no_NO', 'no', 'nor', 'norwegian', 'no_NO.ISO_8859-1'),
2596              'pl-pl' => array('pl_PL.UTF-8', 'pl_PL', 'pl', 'pol', 'polish', ''),
2597              'pt-br' => array('pt_BR.UTF-8', 'pt_BR', 'pt', 'ptb', 'portuguese-brazil', ''),
2598              'pt-pt' => array('pt_PT.UTF-8', 'pt_PT', 'por', 'portuguese', 'pt_PT.ISO_8859-1'),
2599              'ro-ro' => array('ro_RO.UTF-8', 'ro_RO', 'ron', 'rum', 'ro', 'romanian', 'ro_RO.ISO8859-2'),
2600              'ru-ru' => array('ru_RU.UTF-8', 'ru_RU', 'ru', 'rus', 'russian', 'ru_RU.ISO8859-5'),
2601              'sk-sk' => array('sk_SK.UTF-8', 'sk_SK', 'sk', 'slo', 'slk', 'sky', 'slovak', 'sk_SK.ISO_8859-1'),
2602              'sv-se' => array('sv_SE.UTF-8', 'sv_SE', 'sv', 'swe', 'sve', 'swedish', 'sv_SE.ISO_8859-1'),
2603              'th-th' => array('th_TH.UTF-8', 'th_TH', 'th', 'tha', 'thai', 'th_TH.ISO_8859-11'),
2604              'uk-ua' => array('uk_UA.UTF-8', 'uk_UA', 'uk', 'ukr'),
2605              'vi-vn' => array('vi_VN.UTF-8', 'vi_VN', 'vi', 'vie'),
2606              'zh-cn' => array('zh_CN.UTF-8', 'zh_CN'),
2607              'zh-tw' => array('zh_TW.UTF-8', 'zh_TW'),
2608          );
2609  
2610          if (!empty($guesses[$lang])) {
2611              $l = @setlocale(LC_TIME, $guesses[$lang]);
2612              if ($l !== false)
2613                  $locale = $l;
2614          }
2615          @setlocale(LC_TIME, $locale);
2616  
2617          return $locale;
2618      }
2619  
2620  //-------------------------------------------------------------
2621  	function assert_article() {
2622          global $thisarticle;
2623          if (empty($thisarticle))
2624              trigger_error(gTxt('error_article_context'));
2625      }
2626  
2627  //-------------------------------------------------------------
2628  	function assert_comment() {
2629          global $thiscomment;
2630          if (empty($thiscomment))
2631              trigger_error(gTxt('error_comment_context'));
2632      }
2633  
2634  //-------------------------------------------------------------
2635  	function assert_file() {
2636          global $thisfile;
2637          if (empty($thisfile))
2638              trigger_error(gTxt('error_file_context'));
2639      }
2640  
2641  //-------------------------------------------------------------
2642  	function assert_image() {
2643          global $thisimage;
2644          if (empty($thisimage))
2645              trigger_error(gTxt('error_image_context'));
2646      }
2647  
2648  //-------------------------------------------------------------
2649  	function assert_link() {
2650          global $thislink;
2651          if (empty($thislink))
2652              trigger_error(gTxt('error_link_context'));
2653      }
2654  
2655  //-------------------------------------------------------------
2656  	function assert_section() {
2657          global $thissection;
2658          if (empty($thissection))
2659              trigger_error(gTxt('error_section_context'));
2660      }
2661  
2662  //-------------------------------------------------------------
2663  	function assert_category() {
2664          global $thiscategory;
2665          if (empty($thiscategory))
2666              trigger_error(gTxt('error_category_context'));
2667      }
2668  
2669  //-------------------------------------------------------------
2670  	function assert_int($myvar) {
2671          if (is_numeric($myvar) and $myvar == intval($myvar)) {
2672              return (int) $myvar;
2673          }
2674          trigger_error("'".txpspecialchars((string)$myvar)."' is not an integer", E_USER_ERROR);
2675          return false;
2676      }
2677  
2678  //-------------------------------------------------------------
2679  	function assert_string($myvar) {
2680          if (is_string($myvar)) {
2681              return $myvar;
2682          }
2683          trigger_error("'".txpspecialchars((string)$myvar)."' is not a string", E_USER_ERROR);
2684          return false;
2685      }
2686  
2687  //-------------------------------------------------------------
2688  	function assert_array($myvar) {
2689          if (is_array($myvar)) {
2690              return $myvar;
2691          }
2692          trigger_error("'".txpspecialchars((string)$myvar)."' is not an array", E_USER_ERROR);
2693          return false;
2694      }
2695  
2696  //-------------------------------------------------------------
2697  	function replace_relative_urls($html, $permalink='') {
2698  
2699          global $siteurl;
2700  
2701          # urls like "/foo/bar" - relative to the domain
2702          if (serverSet('HTTP_HOST')) {
2703              $html = preg_replace('@(<a[^>]+href=")/@','$1'.PROTOCOL.serverSet('HTTP_HOST').'/',$html);
2704              $html = preg_replace('@(<img[^>]+src=")/@','$1'.PROTOCOL.serverSet('HTTP_HOST').'/',$html);
2705          }
2706          # "foo/bar" - relative to the textpattern root
2707          # leave "http:", "mailto:" et al. as absolute urls
2708          $html = preg_replace('@(<a[^>]+href=")(?!\w+:)@','$1'.PROTOCOL.$siteurl.'/$2',$html);
2709          $html = preg_replace('@(<img[^>]+src=")(?!\w+:)@','$1'.PROTOCOL.$siteurl.'/$2',$html);
2710  
2711          if ($permalink)
2712              $html = preg_replace("/href=\\\"#(.*)\"/","href=\"".$permalink."#\\1\"",$html);
2713          return ($html);
2714      }
2715  
2716  //-------------------------------------------------------------
2717  	function show_clean_test($pretext) {
2718          echo md5(@$pretext['req']).n;
2719          if (serverSet('SERVER_ADDR') == serverSet('REMOTE_ADDR'))
2720          {
2721              var_export($pretext);
2722          }
2723      }
2724  
2725  //-------------------------------------------------------------
2726  
2727  	function pager($total, $limit, $page) {
2728          $total = (int) $total;
2729          $limit = (int) $limit;
2730          $page = (int) $page;
2731  
2732          $num_pages = ceil($total / $limit);
2733  
2734          $page = min(max($page, 1), $num_pages);
2735  
2736          $offset = max(($page - 1) * $limit, 0);
2737  
2738          return array($page, $offset, $num_pages);
2739      }
2740  
2741  //-------------------------------------------------------------
2742  // word-wrap a string using a zero width space
2743  	function soft_wrap($text, $width, $break='&#8203;')
2744      {
2745          $wbr = chr(226).chr(128).chr(139);
2746          $words = explode(' ', $text);
2747          foreach($words as $wordnr => $word) {
2748              $word = preg_replace('|([,./\\>?!:;@-]+)(?=.)|', '$1 ', $word);
2749              $parts = explode(' ', $word);
2750              foreach($parts as $partnr => $part) {
2751                  $len = strlen(utf8_decode($part));
2752                  if (!$len) continue;
2753                  $parts[$partnr] = preg_replace('/(.{'.ceil($len/ceil($len/$width)).'})(?=.)/u', '$1'.$wbr, $part);
2754              }
2755              $words[$wordnr] = join($wbr, $parts);
2756          }
2757          return join(' ', $words);
2758      }
2759  
2760  //-------------------------------------------------------------
2761  	function strip_prefix($str, $pfx) {
2762          return preg_replace('/^'.preg_quote($pfx, '/').'/', '', $str);
2763      }
2764  
2765  //-------------------------------------------------------------
2766  // wrap an array of name => value tupels into an XML envelope,
2767  // supports one level of nested arrays at most.
2768  	function send_xml_response($response=array())
2769      {
2770          static $headers_sent = false;
2771  
2772          if (!$headers_sent) {
2773              ob_clean();
2774              header('Content-Type: text/xml; charset=utf-8');
2775              $out[] = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>';
2776              $headers_sent = true;
2777          }
2778  
2779          $default_response = array (
2780              'http-status' => '200 OK',
2781          );
2782  
2783          // backfill default response properties
2784          $response = $response + $default_response;
2785  
2786          txp_status_header($response['http-status']);
2787          $out[] = '<textpattern>';
2788          foreach ($response as $element => $value)
2789          {
2790              if (is_array($value))
2791              {
2792                  $out[] = t."<$element>".n;
2793                  foreach ($value as $e => $v)
2794                  {
2795                      // Character escaping in values; @see http://www.w3.org/TR/2000/WD-xml-c14n-20000119.html#charescaping
2796                      $v = str_replace(array("\t", "\n", "\r"), array("&#x9;", "&#xA;", "&#xD;"), htmlentities($v, ENT_QUOTES, 'UTF-8'));
2797                      $out[] = t.t."<$e value='$v' />".n;
2798                  }
2799                  $out[] = t."</$element>".n;
2800              }
2801              else
2802              {
2803                  $value = str_replace(array("\t", "\n", "\r"), array("&#x9;", "&#xA;", "&#xD;"), htmlentities($value, ENT_QUOTES, 'UTF-8'));
2804                  $out[] = t."<$element value='$value' />".n;
2805              }
2806          }
2807          $out[] = '</textpattern>';
2808          echo join(n, $out);
2809      }
2810  
2811  /**
2812   * Send a text/javascript response
2813   *
2814   * @param string $out
2815   * @since 4.4
2816   */
2817  	function send_script_response($out = '')
2818      {
2819          static $headers_sent = false;
2820          if (!$headers_sent) {
2821              ob_clean();
2822              header('Content-Type: text/javascript; charset=utf-8');
2823              txp_status_header('200 OK');
2824              $headers_sent = true;
2825          }
2826          echo ";\n".$out.";\n";
2827      }
2828  
2829  /**
2830   * Display a modal client message in response to an AJAX request and halt execution.
2831   *
2832   * @param array $message string|array: $message[0] is the message's text; $message[1] is the message's type (one of E_ERROR or E_WARNING, anything else meaning "success"; not used)
2833   * @since 4.5
2834   */
2835  function modal_halt($thing)
2836  {
2837      global $app_mode, $theme;
2838      if ($app_mode == 'async')
2839      {
2840          send_script_response($theme->announce_async($thing, true));
2841          die();
2842      }
2843  }
2844  
2845  // -------------------------------------------------------------
2846  // Perform regular housekeeping.
2847  // Might evolve into some kind of pseudo-cron later...
2848  	function janitor()
2849      {
2850          global $prefs;
2851  
2852          // update DST setting
2853          global $auto_dst, $timezone_key, $is_dst;
2854          if ($auto_dst && $timezone_key)
2855          {
2856              $is_dst = timezone::is_dst(time(), $timezone_key);
2857              if ($is_dst != $prefs['is_dst'])
2858              {
2859                  $prefs['is_dst'] = $is_dst;
2860                  set_pref('is_dst', $is_dst, 'publish', 2);
2861              }
2862          }
2863  
2864          // deprecation nags
2865          if (AJAXALLY_CHALLENGED)
2866          {
2867              trigger_error(gTxt('deprecated_configuration', array('{name}' => 'AJAXALLY_CHALLENGED')), E_USER_NOTICE);
2868          }
2869      }
2870  
2871  // -------------------------------------------------------------
2872  // Dealing with timezones.
2873      class timezone
2874      {
2875          private $_details;
2876          private $_offsets;
2877  
2878  		function __construct()
2879          {
2880              if (!timezone::is_supported())
2881              {
2882                  // Standard time zones as compiled by H.M. Nautical Almanac Office, June 2004
2883                  // http://aa.usno.navy.mil/faq/docs/world_tzones.html
2884                  $timezones = array(
2885                      -12, -11, -10, -9.5, -9, -8.5, -8, -7, -6, -5, -4, -3.5, -3, -2, -1,
2886                      0,
2887                      +1, +2, +3, +3.5, +4, +4.5, +5, +5.5, +6, +6.5, +7, +8, +9, +9.5, +10, +10.5, +11, +11.5, +12, +13, +14,
2888                  );
2889  
2890                  foreach ($timezones as $tz)
2891                  {
2892                      // Fake timezone id
2893                      $timezone_id = 'GMT'.sprintf('%+05.1f', $tz);
2894                      $sign = ($tz >= 0 ? '+' : '');
2895                      $label = sprintf("GMT %s%02d:%02d", $sign, $tz, abs($tz - (int)$tz) * 60);
2896                      $this->_details[$timezone_id]['continent'] = gTxt('timezone_gmt');
2897                      $this->_details[$timezone_id]['city'] = $label;
2898                      $this->_details[$timezone_id]['offset'] = $tz * 3600;
2899                      $this->_offsets[$tz * 3600] = $timezone_id;
2900                  }
2901              }
2902              else
2903              {
2904                  $continents = array('Africa', 'America', 'Antarctica', 'Arctic', 'Asia',
2905                      'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific');
2906  
2907                  $server_tz = @date_default_timezone_get();
2908                  $tzlist = timezone_abbreviations_list();
2909                  foreach ($tzlist as $abbr => $timezones)
2910                  {
2911                      foreach ($timezones as $tz)
2912                      {
2913                          $timezone_id = $tz['timezone_id'];
2914                          // $timezone_ids are not unique among abbreviations
2915                          if ($timezone_id && !isset($this->_details[$timezone_id]))
2916                          {
2917                              $parts = explode('/', $timezone_id);
2918                              if (in_array($parts[0], $continents))
2919                              {
2920                                  if (!empty($server_tz))
2921                                  {
2922                                      if (date_default_timezone_set($timezone_id))
2923                                      {
2924                                          $is_dst = date('I', time());
2925                                      }
2926                                  }
2927  
2928                                  $this->_details[$timezone_id]['continent'] = $parts[0];
2929                                  $this->_details[$timezone_id]['city'] = (isset($parts[1])) ? $parts[1] : '';
2930                                  $this->_details[$timezone_id]['subcity'] = (isset($parts[2])) ? $parts[2] : '';
2931                                  $this->_details[$timezone_id]['offset'] = date_offset_get(date_create()) - ($is_dst ? 3600 : 0);
2932                                  $this->_details[$timezone_id]['dst'] = $tz['dst'];
2933                                  $this->_details[$timezone_id]['abbr'] = strtoupper($abbr);
2934  
2935                                  // Guesstimate a timezone key for a given GMT offset
2936                                  $this->_offsets[$tz['offset']] = $timezone_id;
2937                              }
2938                          }
2939                      }
2940                  }
2941              }
2942  
2943              if (!empty($server_tz))
2944              {
2945                  date_default_timezone_set($server_tz);
2946              }
2947          }
2948  
2949          /**
2950           * Render HTML SELECT element for choosing a timezone
2951           * @param    string    $name    Element name
2952           * @param    string    $value    Selected timezone
2953           * @param    boolean    $blank_first Add empty first option
2954           * @param    boolean|string    $onchange n/a
2955           * @param    string    $select_id    HTML id attribute
2956           * @return    string    HTML markup
2957           */
2958  		function selectInput($name = '', $value = '', $blank_first = '', $onchange = '', $select_id = '')
2959          {
2960              if (!empty($this->_details))
2961              {
2962                  $thiscontinent = '';
2963                  $selected = false;
2964  
2965                  ksort($this->_details);
2966                  foreach ($this->_details as $timezone_id => $tz)
2967                  {
2968                      extract($tz);
2969                      if ($value == $timezone_id) $selected = true;
2970                      if ($continent !== $thiscontinent)
2971                      {
2972                          if ($thiscontinent !== '') $out[] = n.t.'</optgroup>';
2973                          $out[] = n.t.'<optgroup label="'.gTxt($continent).'">';
2974                          $thiscontinent = $continent;
2975                      }
2976  
2977                      $where = gTxt(str_replace('_', ' ', $city))
2978                                  .(!empty($subcity) ? '/'.gTxt(str_replace('_', ' ', $subcity)) : '').t
2979                                  /*."($abbr)"*/;
2980                      $out[] = n.t.t.'<option value="'.txpspecialchars($timezone_id).'"'.($value == $timezone_id ? ' selected="selected"' : '').'>'.$where.'</option>';
2981                  }
2982                  $out[] = n.t.'</optgroup>';
2983                  return n.'<select'.( $select_id ? ' id="'.$select_id.'"' : '' ).' name="'.$name.'"'.
2984                      ($onchange == 1 ? ' onchange="submit(this.form);"' : $onchange).
2985                      '>'.
2986                      ($blank_first ? n.t.'<option value=""'.($selected == false ? ' selected="selected"' : '').'></option>' : '').
2987                      join('', $out).
2988                      n.'</select>';
2989              }
2990              return '';
2991          }
2992  
2993          /**
2994           * Build a matrix of timezone details
2995           * @return    array    Array of timezone details indexed by timezone key
2996           */
2997  		function details()
2998          {
2999              return $this->_details;
3000          }
3001  
3002          /**
3003           * Find a timezone key matching a given GMT offset.
3004           * NB: More than one key might fit any given GMT offset,
3005           * thus the returned value is ambiguous and merely useful for presentation purposes.
3006           * @param    integer $gmtoffset
3007           * @return    string    timezone key
3008           */
3009  		function key($gmtoffset)
3010          {
3011              return isset($this->_offsets[$gmtoffset]) ? $this->_offsets[$gmtoffset] : '';
3012          }
3013  
3014           /**
3015           * Is DST in effect?
3016           * @param    integer $timestamp When?
3017           * @param    string     $timezone_key Where?
3018           * @return    boolean    Yes, they are saving time, actually.
3019           */
3020  		static function is_dst($timestamp, $timezone_key)
3021          {
3022              global $is_dst, $auto_dst;
3023  
3024              $out = $is_dst;
3025              if ($auto_dst && $timezone_key && timezone::is_supported())
3026              {
3027                  $server_tz = @date_default_timezone_get();
3028                  if ($server_tz)
3029                  {
3030                      // switch to client time zone
3031                      if (date_default_timezone_set($timezone_key))
3032                      {
3033                          $out = date('I', $timestamp);
3034                          // restore server time zone
3035                          date_default_timezone_set($server_tz);
3036                      }
3037                  }
3038              }
3039              return $out;
3040          }
3041  
3042          /**
3043           * Check for run-time timezone support
3044           * @return    boolean    Timezone feature is enabled
3045           */
3046  		static function is_supported()
3047          {
3048              return !defined('NO_TIMEZONE_SUPPORT');    // user-definable emergency brake
3049          }
3050      }
3051  
3052  //-------------------------------------------------------------
3053  	function install_textpack($textpack, $add_new_langs = false)
3054      {
3055          global $prefs;
3056  
3057          $textpack = explode(n, $textpack);
3058          if (empty($textpack)) return 0;
3059  
3060          // presume site language equals textpack language
3061          $language = get_pref('language', 'en-gb');
3062  
3063          $installed_langs = safe_column('lang', 'txp_lang', "1 = 1 group by lang");
3064          $doit = true;
3065  
3066          $done = 0;
3067          foreach ($textpack as $line)
3068          {
3069              $line = trim($line);
3070              // A line starting with #, not followed by @ is a simple comment
3071              if (preg_match('/^#[^@]/', $line, $m))
3072              {
3073                  continue;
3074              }
3075  
3076              // A line matching "#@language xx-xx" establishes the designated language for all subsequent lines
3077              if (preg_match('/^#@language\s+(.+)$/', $line, $m))
3078              {
3079                  $language = doSlash($m[1]);
3080                  // May this Textpack introduce texts for this language?
3081                  $doit = ($add_new_langs || in_array($language, $installed_langs));
3082                  continue;
3083              }
3084  
3085              // A line matching "#@event_name" establishes the event value for all subsequent lines
3086              if (preg_match('/^#@([a-zA-Z0-9_-]+)$/', $line, $m))
3087              {
3088                  $event = doSlash($m[1]);
3089                  continue;
3090              }
3091  
3092              // Data lines match a "name => value" pattern. Some white space allowed.
3093              if ($doit && preg_match('/^(\w+)\s*=>\s*(.+)$/', $line, $m))
3094              {
3095                  if (!empty($m[1]) && !empty($m[2]))
3096                  {
3097                      $name = doSlash($m[1]);
3098                      $value = doSlash($m[2]);
3099                      $where = "lang='$language' AND name='$name'";
3100                      // Store text; do *not* tamper with last modification date from RPC but use a well-known date in the past
3101                      if (safe_count('txp_lang', $where))
3102                      {
3103                          safe_update('txp_lang',    "lastmod='2005-08-14', data='$value', event='$event'", $where);
3104                      }
3105                      else
3106                      {
3107                          safe_insert('txp_lang',    "lastmod='2005-08-14', data='$value', event='$event', lang='$language', name='$name'");
3108                      }
3109                      ++$done;
3110                  }
3111              }
3112          }
3113          return $done;
3114      }
3115  
3116  /**
3117   * Generate a ciphered token.
3118   *
3119   * The token is reproducable, unique among sites and users, expires later.
3120   *
3121   * @return    string    The token.
3122   */
3123  //-------------------------------------------------------------
3124  	function form_token()
3125      {
3126          static $token;
3127          global $txp_user;
3128  
3129          // Generate a ciphered token from the current user's nonce (thus valid for login time plus 30 days)
3130          // and a pinch of salt from the blog UID.
3131          if (empty($token)) {
3132              $nonce = safe_field('nonce', 'txp_users', "name='".doSlash($txp_user)."'");
3133              $token = md5($nonce . get_pref('blog_uid'));
3134          }
3135          return $token;
3136      }
3137  
3138  /**
3139   * Assert system requirements
3140   */
3141  //-------------------------------------------------------------
3142  	function assert_system_requirements()
3143      {
3144          if (version_compare(REQUIRED_PHP_VERSION, PHP_VERSION) > 0) {
3145              txp_die('This server runs PHP version '.PHP_VERSION.'. Textpattern needs PHP version '. REQUIRED_PHP_VERSION. ' or better.');
3146          }
3147      }
3148  
3149  
3150  /**
3151   * Validate admin steps. Protect against CSRF attempts.
3152   *
3153   * @param    string    $step    Requested admin step.
3154   * @param    array    $steps    An array of valid steps with flag indicating CSRF needs, e.g. array('savething' => true, 'listthings' => false)
3155   * @return    boolean    $step is valid, proceed. Dies on CSRF attempt.
3156   */
3157  //-------------------------------------------------------------
3158  	function bouncer($step, $steps)
3159      {
3160          global $event;
3161  
3162          if (empty($step)) return true;
3163  
3164          // Validate step
3165          if (!array_key_exists($step, $steps)) {
3166              return false;
3167          }
3168  
3169          // Does this step require a token?
3170          if (!$steps[$step]) {
3171              return true;
3172          }
3173  
3174          // Validate token
3175          if (gps('_txp_token') == form_token()) {
3176              return true;
3177          }
3178  
3179          // This place ain't no good for you, son.
3180          die(gTxt('get_off_my_lawn', array('{event}' => $event, '{step}' => $step)));
3181      }
3182  
3183  /**
3184   * Test whether the client accepts a certain response format.
3185   *
3186   * Discards formats with a quality factor below 0.1
3187   *
3188   * @param   string  $format One of 'html', 'txt', 'js', 'css', 'json', 'xml', 'rdf', 'atom', 'rss'
3189   * @return  boolean $format TRUE if accepted
3190   * @since   4.5.0
3191   * @package Network
3192   */
3193  
3194  function http_accept_format($format)
3195  {
3196      static $formats = array(
3197          'html' => array('text/html', 'application/xhtml+xml', '*/*'),
3198          'txt'  => array('text/plain', '*/*'),
3199          'js'   => array('application/javascript', 'application/x-javascript', 'text/javascript', 'application/ecmascript', 'application/x-ecmascript', '*/*'),
3200          'css'  => array('text/css', '*/*'),
3201          'json' => array('application/json', 'application/x-json', '*/*'),
3202          'xml'  => array('text/xml', 'application/xml', 'application/x-xml', '*/*'),
3203          'rdf'  => array('application/rdf+xml', '*/*'),
3204          'atom' => array('application/atom+xml', '*/*'),
3205          'rss'  => array('application/rss+xml', '*/*'),
3206      );
3207      static $accepts = array();
3208      static $q = array();
3209  
3210      if (empty($accepts))
3211      {
3212          // Build cache of accepted formats.
3213          $accepts = preg_split('/\s*,\s*/', serverSet('HTTP_ACCEPT'), null, PREG_SPLIT_NO_EMPTY);
3214          foreach ($accepts as $i => &$a)
3215          {
3216              // Sniff out quality factors if present.
3217              if (preg_match('/(.*)\s*;\s*q=([.0-9]*)/', $a, $m))
3218              {
3219                  $a = $m[1];
3220                  $q[$a] = floatval($m[2]);
3221              }
3222              else
3223              {
3224                  $q[$a] = 1.0;
3225              }
3226              // Discard formats with quality factors below an arbitrary threshold
3227              // as jQuery adds a wildcard '*/*; q=0.01' to the 'Accepts' header for XHR requests.
3228              if ($q[$a] < 0.1)
3229              {
3230                  unset($q[$a]);
3231                  unset($accepts[$i]);
3232              }
3233          }
3234      }
3235      return isset($formats[$format]) ? count(array_intersect($formats[$format], $accepts)) > 0 : false;
3236  }
3237  
3238  /**
3239   * Translate article status names into numerical status codes
3240   *
3241   * @param string $name     Named status {'draft', 'hidden', 'pending', 'live', 'sticky'}
3242   * @return int             Numerical status [1..5]
3243   */
3244  function getStatusNum($name)
3245  {
3246      $labels = array('draft' => 1, 'hidden' => 2, 'pending' => 3, 'live' => 4, 'sticky' => 5);
3247      $status = strtolower($name);
3248      $num = empty($labels[$status]) ? 4 : $labels[$status];
3249      return $num;
3250  }
3251  
3252  ?>

title

Description

title

Description

title

Description

title

title

Body