<?php /*  PHP functions  written by and Copyright © 2009,2010,2011,2012 Joe Golembieski, SoftMoon WebWare

		This program is free software: you can redistribute it and/or modify
		it under the terms of the GNU General Public License as published by
		the Free Software Foundation, either version 3 of the License, or
		(at your option) any later version.
		The original copyright information must remain intact.

		This program is distributed in the hope that it will be useful,
		but WITHOUT ANY WARRANTY; without even the implied warranty of
		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
		GNU General Public License for more details.

		You should have received a copy of the GNU General Public License
		along with this program.  If not, see <http://www.gnu.org/licenses/>    /*/

// for PHP 5.2 (may work with ealier versions)    tab spacing=2  editor width=120  auto-word-wrap=no


if (!defined('DIR_SEP'))  {
  if (stripos(php_uname('s'), 'Win')===FALSE)    // Mac OS/LINUX/UNIX directory separator
    define('DIR_SEP', "/");
  else    // MS Windows directory separator
    define('DIR_SEP', "\\");  }


Function unslash($posted)  {
  if (is_array($posted))  foreach ($posted as &$v)  {$v=unslash($v);}
  else
  if (get_magic_quotes_gpc())  {
    $mqs=strtolower(ini_get('magic_quotes_sybase'));
    if (empty($mqs)  or  $mqs=='off')  $posted=stripslashes($posted);
    else  $posted=str_replace("''", "'", $posted);  }
  return $posted;  }

Function getUploadedFile($filefield, $index, $destination, $filetypes=FALSE, $echoIt=TRUE)  {
  if ( is_array($_FILES[$filefield])
  and $_FILES[$filefield]['error'][$index]['file']==0
  and $_FILES[$filefield]['size'][$index]['file']>128
  and $_FILES[$filefield]['tmp_name'][$index]['file']!=""
// and in_array($_FILES[$filefield]['type'][$index]['file'], array("image/png", "image/gif", "image/jpg")) //...good luck listing all possible mime types...
  and (!is_array($filetypes) or in_array(strtolower(strrchr($_FILES[$filefield]['name'][$index]['file'], ".")), $filetypes))
  and move_uploaded_file( $_FILES[$filefield]['tmp_name'][$index]['file'],
      $destination.=($UpL_name=basename($_FILES[$filefield]['name'][$index]['file'])) ) )
    {
      if ($echoIt)  echo "$filefield file: $UpL_name<br />\n";
      return $destination;
    }
  else return NULL;  }

Function file_path_exists($fn)  {
  if (file_exists($fn))  return $fn;
  $paths=explode(PATH_SEPARATOR, get_include_path());
  foreach ($paths as &$p)  {if (substr($p, strlen($p)-1)!==DIR_SEP)  $p.=DIR_SEP;}
  $i=0;  $m=count($paths);
  do {if (file_exists($paths[$i].$fn))  return $paths[$i].$fn;}
  while (++$i<$m);
  return FALSE;  }

Function include_modules($dir, $filetype='.php', $¿require=FALSE)  {
  if (substr($dir, strlen($dir)-1)!==DIR_SEP)  $dir.=DIR_SEP;
  if (!is_dir($path=$dir))  {
    $paths=explode(PATH_SEPARATOR, get_include_path());
    foreach ($paths as &$p)  {if (substr($p, strlen($p)-1)!==DIR_SEP)  $p.=DIR_SEP;}
    $i=0;  $m=count($paths);
    do {$path=$paths[$i++].$dir;}
    while (!is_dir($path)  and  $i<$m);  }
  $D=opendir($path);
  while ( $f=readdir($D) )  {
    if ($f=="." or $f=="..")  continue;
    if (is_dir($path.$f))  include_modules($path.$f.DIR_SEP, $filetype, $¿require);
    else
    if ( (is_string($filetype)  and  strrchr($f, '.')==$filetype)
    or  (is_array($filetype)  and  in_array(strrchr($f, '.'), $filetype)) )  {
      if ($¿require)  require_once $path.$f;
      else  include_once $path.$f;  }  }
  closedir($D);  }

Function lbreak_at($Llen, $terminator, $string, $EOLflag=TRUE, $LBr="\n", $atEnd=FALSE)  {
  $SLen=strlen($string);  $TLen=strlen($terminator);  $lastp=0;
  do  { if (($p=strpos($string, $terminator, $lastp))!==FALSE)  $p+=$TLen;  else  $p=$SLen;
    $build=substr($string, $lastp, $p-$lastp);  $lastp=$p;
    if (strlen($buildup)+strlen($build)<=$Llen)  $buildup.=$build;
    else {
      if ($EOLflag)  $rtrnString.=$buildup.$LBr;
      else  $rtrnString.=substr($buildup, 0, strlen($buildup)-$TLen).$LBr;
      $buildup=$build;  }  }
  while ($p<$SLen);
  return $rtrnString.$buildup.($atEnd ? $LBr : "");  }


Function array_is_similar($A1, $A2, &$Adif="")  {
  return (count($Adif=(@array_merge(array_diff($A1, $A2), array_diff($A2, $A1)))) < 1);  }

Function array_not_similar($A1, $A2)  {return @array_merge(array_diff($A1, $A2), array_diff($A2, $A1));}

Function array_equils(&$A1, &$A2, $IdentFLAG=FALSE)  { $RtrnFLAG=TRUE;  if ($IdentFLAG!==TRUE)  $IdentFLAG=FALSE;
  foreach ($A1 as $key => $data)  {
    if (array_key_exists($key, $A2) and $A2[$key]===$data)  { $daF=TRUE;
      if (is_array($data))  $RtrnFLAG=($RtrnFLAG and $daF=array_equils($A1[$key], $A2[$key], $IdentFLAG));
      if ($IdentFLAG and $daF)  {$A1[$key]=NULL;  $A2[$key]=NULL;}  }
    else  $RtrnFLAG=FALSE;  }
  foreach ($A2 as $key => $data)  {
    if (array_key_exists($key, $A1) and $A1[$key]===$data)  { $daF=TRUE;
      if (is_array($data))  $RtrnFLAG=($RtrnFLAG and $daF=array_equils($A1[$key], $A2[$key], $IdentFLAG));
      if ($IdentFLAG and $daF)  {$A1[$key]=NULL;  $A2[$key]=NULL;}  }
    else  $RtrnFLAG=FALSE;  }
  return $RtrnFLAG;  }

Function casei_in_array($VALUE, $A)  { if (!is_string($VALUE) or !is_array($A))  return FALSE;
  $VALUE=strtolower($VALUE);  //you may want to allow other value types and force them to a string for flexibility
  foreach ($A as $value)  {
    if (is_string($value)  and  $VALUE===strtolower($value))  return $value;  }
  return FALSE;  }

Function array_ikey_exists($KEY, $A)  { if (!is_array($A) or !is_string($KEY) and !is_numeric($KEY))  return FALSE;
  $KEY=strtolower((string)$KEY);  //allowing numeric keys gives this function universal flexibility
  foreach ($A as $key => $d)  {if ($KEY===strtolower((string)$key))  return $key;}
  return FALSE;  }

Function preg_key_grep($A, $preg, $KeepKey=FALSE)  { $subA=array();
  $keys=preg_grep($preg, array_keys($A));
  if ($KeepKey)
    foreach ($keys as $k)  {$subA[$k]=$A[$k];}
  else
    foreach ($keys as $k)  {$subA[]=$A[$k];}
  return $subA;  }

Function is_iterateable($var) {return (is_array($var)  or  is_object($var) /*PHP>=5.3*/ and  !is_callable($var)/**/);}

Function array_multijunction($new_keys, $A1, $A2 /* , $A3.... , $¿allKeys=FALSE */)  { $junctA=array();
  $A=func_get_args();  $n=func_num_args();
  if ((is_bool($A[$n-1])) ?  $A[--$n]  :  FALSE)
    for ($i=1; $i<$n; $i++)  {
      foreach ($A[$i] as $key => $data)  {$junctA[$key][$new_keys[$i-1]]=$data;}  }
  else  {  $=INF;
    for ($i=1; $i<$n; $i++)  {if (($c=count($A[$i])) < $)  {$=$i;  $=$c;}}
    foreach ($A[$] as $key => $data)  {
      for ($i=1; $i<$n; $i++)  {if (!array_key_exists($key, $A[$i]))  continue 2;}
      for ($i=1; $i<$n; $i++)  {$junctA[$key][$new_keys[$i-1]]=$A[$i][$key];}  }  }
  return $junctA;  }

//*********** Array SubKey **************\\
Function array_subkey($A, $indxLevel, $IDkey, $KeepKey=FALSE)  {
  if ($indxLevel==1)  {
    if (!is_array($IDkey))  return $A[$IDkey];
    else  { $subA=array();
      if ($KeepKey)
        foreach ($IDkey as $key) {if (array_key_exists($key, $A))  $subA[$key]=$A[$key];}
      else
        foreach ($IDkey as $key) {if (array_key_exists($key, $A))  $subA[]=$A[$key];}  }  }
  else  { $subA=array();
    if ($KeepKey)
      foreach ($A as $indx => $data)  {if (is_array($data))  $subA[$indx]=array_subkey($data, $indxLevel-1, $IDkey);}
    else
      foreach ($A as $indx => $data)  {if (is_array($data))  $subA[]=array_subkey($data, $indxLevel-1, $IDkey);}  }
  return $subA;  }

// for PHP 5.0 to 5.3 (and up) -- make noted changes for earlier versions, but heed danger

Define("KEEP_TREE", 1);
Define("KEEP_KEY", 2);

Function array_subkey¤5•3($A, $indxLevel, $IDkey, $KeepTree=FALSE)  {  $KeepKey=($KeepTree & KEEP_KEY);
  if ($indxLevel<2)  {
    if (is_array($IDkey)  and  is_array($IDkey['filter']))  $IDkey=array_shift($IDkey['filter']);
    if ($IDkey===NULL)  return $A;
//  if (is_callable($IDkey))  {  // non-object-oriented-PHP  [DANGER!  string-keyname / function-name collision]
    if ( is_object($IDkey)
    and  /* $flag=method_exists($IDkey, 'filter')   PHP < 5.3
		or */  is_callable($IDkey)     /* PHP >= 5.3   [PREFERRED] */   )  {
      $indx=$KeepTree;  $x=($flag) ? $IDkey->filter($A, $indx) : $IDkey($A, $indx);
      return ($KeepKey  and  $x!==NULL  and  $indx!==NULL) ? array($indx => $x) : $x;  }
    else
    if (is_array($IDkey))  { $subA=array();
      if (is_array($IDkey['re-index']))  {$IDkey=$IDkey['re-index'];  $reindex=TRUE;}  //while only useful with "keep key", we process this here to allow the same filter to be used with and without the "keep key" option. Move it down one line for faster performance and strickter implementation.
      if ($KeepKey)
        foreach ($IDkey as $newindex => $key)  {
//        if (is_callable($IDkey))  {  // non-object-oriented-PHP   [DANGER!  string-keyname / function-name collision]
          if (is_object($key)
          and  /* PHP < 5.3   $flag=method_exists($key, 'filter')  */
          /*or   PHP>=5.3 */ is_callable($key) )  { $indx=$KeepTree;
            if (($x=($flag) ? $key->filter($A, $indx) : $key($A, $indx))!==NULL)  $subA[$indx]=$x;  continue;  }
          if (substr($key, 0, 1)=="/")   {$subA=$subA+preg_key_grep($A, $key, KEEP_KEY);  continue;}
          if (array_key_exists($key, $A))  $subA[($reindex) ? $newindex : $key]=$A[$key];  }
      else
        foreach ($IDkey as $key)  {
//        if (is_callable($IDkey))  {  // non-object-oriented-PHP   [DANGER!  string-keyname / function-name collision]
          if (is_object($key)
          and  /* PHP < 5.3   $flag=method_exists($key, 'filter')  */
          /*or   PHP>=5.3 */ is_callable($key) )  {
            if (($x=($flag) ? $key->filter($A) : $key($A))!==NULL)  $subA[]=$x;  continue;  }
          if (substr($key, 0, 1)=="/")   {$subA=array_merge($subA, preg_key_grep($A, $key));  continue;}
          if (array_key_exists($key, $A))  $subA[]=$A[$key];  }  }
    else
    if (substr($IDkey, 0, 1)=="/")  {$subA=preg_key_grep($A, $IDkey, $KeepKey);}
    else  return ($KeepKey  and  isset($A[$IDkey])) ? array($IDkey => $A[$IDkey]) : $A[$IDkey];  }
  else  {
    if (is_array($IDkey)  and  is_array($IDkey['filter'])  and  ($filter=array_shift($IDkey['filter']))!==NULL)  {
      $x=array_subkey($A, 1, $filter, $KeepKey);
      if (is_array($filter)  or  @substr($filter, 0, 1)=="/"  or  $KeepKey)  {
        $indxLevel++;  array_unshift($IDkey['filter'], NULL);  }
      if (is_iterateable($x)  and  $t=array_subkey($x, $indxLevel-1, $IDkey, $KeepTree))  $subA=$t;  }
    else  { $subA=array();
    if ($KeepTree)  {
      if ($KeepKey)
        foreach ($A as $indx => $data)  {
          if (is_iterateable($data)  and  NULL!==($t=array_subkey($data, $indxLevel-1, $IDkey, $KeepTree)))  $subA[$indx]=$t;  }
      else
        foreach ($A as $data)  {
          if (is_iterateable($data)  and  NULL!==($t=array_subkey($data, $indxLevel-1, $IDkey, $KeepTree)))  $subA[]=$t;  }  }
    else
        foreach ($A as $data)  {
          if (is_iterateable($data)  and  NULL!==($t=array_subkey($data, $indxLevel-1, $IDkey)))  {
            if (is_array($t)) $subA=array_merge($subA, $t);  else  $subA[]=$t;  }  }  }  }
  return ($subA  and  count($subA)) ? $subA : NULL;  }

Class RegulateText {

Public $HTML_tags;

Function __construct($tags)  {
  $this->HTML_tags= is_array($tags) ? $tags
  : ($tags ? (RegulateText::$HTML4_tags + RegulateText::$restricted_HTML4_tags + RegulateText::$HTML5_tags)
    : RegulateText::$HTML4_tags + RegulateText::$HTML5_tags);
}

// The selection of tags below is optimized for use with a webmaster's database,
// --NOT-- to process user POSTs from the World Wide Web for inclusion on a public page.
// You should pass in your own selection of acceptable tags and further validate them after
// filtering an unknown/anonymous user's input through these methods.

Static Public $HTML4_tags = array(
'a','abbr','acronym','address','applet','b','basefont','bdo','big','blockquote','br','button',
'caption','center','cite','code','col','colgroup','dd','del','dfn','dir','div','dl','dt','em','embed',
'fieldset','font','h1','h2','h3','h4','h5','h6','hr','i','iframe','img','ins','kbd','legend','li',
'menu','noembed','noscript','object','ol','p','param','pre','q','s','samp','script','small','span','strike',
'strong','sub','sup','table','tbody','td','tfoot','th','thead','title','tr','tt','u','ul','var');

Static Public $restricted_HTML4_tags = array(
'form', 'input', 'select', 'option', 'label', 'optgroup', 'textarea', 'area', 'map',
'html', 'head', 'style', 'link', 'meta', 'base', 'body', 'isindex', 'frame', 'frameset', 'noframes');

Static Public $HTML5_tags = array(
'article', 'aside', 'bdi', 'command', 'details', 'summary', 'figure', 'figcaption', 'footer', 'header',
'hgroup', 'mark', 'meter', 'nav', 'progress', 'ruby', 'rt', 'rp', 'section', 'time', 'wbr',
'audio', 'video', 'source', 'embed', 'track', 'canvas', 'datalist', 'keygen', 'output');

Static Protected $HTML_ENTS = array(
  34 => 'quot',
  38 => 'amp',
  39 => 'apos',  /*XHTML/XML only*/
  60 => 'lt',
  62 => 'gt',
  160 => 'nbsp',
  161 => 'iexcl',
  162 => 'cent',
  163 => 'pound',
  164 => 'curren',
  165 => 'yen',
  166 => 'brvbar',
  167 => 'sect',
  168 => 'uml',
  169 => 'copy',
  170 => 'ordf',
  171 => 'laquo',
  172 => 'not',
  173 => 'shy',
  174 => 'reg',
  175 => 'macr',
  176 => 'deg',
  177 => 'plusmn',
  178 => 'sup2',
  179 => 'sup3',
  180 => 'acute',
  181 => 'micro',
  182 => 'para',
  183 => 'middot',
  184 => 'cedil',
  185 => 'sup1',
  186 => 'ordm',
  187 => 'raquo',
  188 => 'frac14',
  189 => 'frac12',
  190 => 'frac34',
  191 => 'iquest',
  192 => 'Agrave',
  193 => 'Aacute',
  194 => 'Acirc',
  195 => 'Atilde',
  196 => 'Auml',
  197 => 'Aring',
  198 => 'AElig',
  199 => 'Ccedil',
  200 => 'Egrave',
  201 => 'Eacute',
  202 => 'Ecirc',
  203 => 'Euml',
  204 => 'Igrave',
  205 => 'Iacute',
  206 => 'Icirc',
  207 => 'Iuml',
  208 => 'ETH',
  209 => 'Ntilde',
  210 => 'Ograve',
  211 => 'Oacute',
  212 => 'Ocirc',
  213 => 'Otilde',
  214 => 'Ouml',
  215 => 'times',
  216 => 'Oslash',
  217 => 'Ugrave',
  218 => 'Uacute',
  219 => 'Ucirc',
  220 => 'Uuml',
  221 => 'Yacute',
  222 => 'THORN',
  223 => 'szlig',
  224 => 'agrave',
  225 => 'aacute',
  226 => 'acirc',
  227 => 'atilde',
  228 => 'auml',
  229 => 'aring',
  230 => 'aelig',
  231 => 'ccedil',
  232 => 'egrave',
  233 => 'eacute',
  234 => 'ecirc',
  235 => 'euml',
  236 => 'igrave',
  237 => 'iacute',
  238 => 'icirc',
  239 => 'iuml',
  240 => 'eth',
  241 => 'ntilde',
  242 => 'ograve',
  243 => 'oacute',
  244 => 'ocirc',
  245 => 'otilde',
  246 => 'ouml',
  247 => 'divide',
  248 => 'oslash',
  249 => 'ugrave',
  250 => 'uacute',
  251 => 'ucirc',
  252 => 'uuml',
  253 => 'yacute',
  254 => 'thorn',
  255 => 'yuml',
  338 => 'OElig',
  339 => 'oelig',
  352 => 'Scaron',
  353 => 'scaron',
  376 => 'Yuml',
  402 => 'fnof',
  710 => 'circ',
  732 => 'tilde',
  913 => 'Alpha',
  914 => 'Beta',
  915 => 'Gamma',
  916 => 'Delta',
  917 => 'Epsilon',
  918 => 'Zeta',
  919 => 'Eta',
  920 => 'Theta',
  921 => 'Iota',
  922 => 'Kappa',
  923 => 'Lambda',
  924 => 'Mu',
  925 => 'Nu',
  926 => 'Xi',
  927 => 'Omicron',
  928 => 'Pi',
  929 => 'Rho',
  931 => 'Sigma',
  932 => 'Tau',
  933 => 'Upsilon',
  934 => 'Phi',
  935 => 'Chi',
  936 => 'Psi',
  937 => 'Omega',
  945 => 'alpha',
  946 => 'beta',
  947 => 'gamma',
  948 => 'delta',
  949 => 'epsilon',
  950 => 'zeta',
  951 => 'eta',
  952 => 'theta',
  953 => 'iota',
  954 => 'kappa',
  955 => 'lambda',
  956 => 'mu',
  957 => 'nu',
  958 => 'xi',
  959 => 'omicron',
  960 => 'pi',
  961 => 'rho',
  962 => 'sigmaf',
  963 => 'sigma',
  964 => 'tau',
  965 => 'upsilon',
  966 => 'phi',
  967 => 'chi',
  968 => 'psi',
  969 => 'omega',
  977 => 'thetasym',
  978 => 'upsih',
  982 => 'piv',
  8194 => 'ensp',
  8195 => 'emsp',
  8201 => 'thinsp',
  8204 => 'zwnj',
  8205 => 'zwj',
  8206 => 'lrm',
  8207 => 'rlm',
  8211 => 'ndash',
  8212 => 'mdash',
  8216 => 'lsquo',
  8217 => 'rsquo',
  8218 => 'sbquo',
  8220 => 'ldquo',
  8221 => 'rdquo',
  8222 => 'bdquo',
  8224 => 'dagger',
  8225 => 'Dagger',
  8226 => 'bull',
  8230 => 'hellip', //mis-spelled in O'Reily® publications as &hellep;
  8240 => 'permil',
  8242 => 'prime',
  8243 => 'Prime',
  8249 => 'lsaquo', /*nonstandard*/
  8250 => 'rsaquo', /*nonstandard*/
  8254 => 'oline',
  8260 => 'frasl',
  8364 => 'euro',
  8465 => 'image',
  8472 => 'weierp',
  8476 => 'real',
  8482 => 'trade',
  8501 => 'alefsym',
  8592 => 'larr',
  8593 => 'uarr',
  8594 => 'rarr',
  8595 => 'darr',
  8596 => 'harr',
  8629 => 'crarr',
  8656 => 'lArr',
  8657 => 'uArr',
  8658 => 'rArr',
  8659 => 'dArr',
  8660 => 'hArr',
  8704 => 'forall',
  8706 => 'part',
  8707 => 'exist',
  8709 => 'empty',
  8711 => 'nabla',
  8712 => 'isin',
  8713 => 'notin',
  8715 => 'ni',
  8719 => 'prod',
  8721 => 'sum',
  8722 => 'minus',
  8727 => 'lowast',
  8730 => 'radic',
  8733 => 'prop',
  8734 => 'infin',
  8736 => 'ang',
  8743 => 'and',
  8744 => 'or',
  8745 => 'cap',
  8746 => 'cup',
  8747 => 'int',
  8756 => 'there4',
  8764 => 'sim',
  8773 => 'cong',
  8776 => 'asymp', //mis-spelled in O'Reily® publications as &ap;
  8800 => 'ne',
  8801 => 'equiv',
  8804 => 'le',
  8805 => 'ge',
  8834 => 'sub',
  8835 => 'sup',
  8836 => 'nsub',
  8838 => 'sube',
  8839 => 'supe',
  8853 => 'oplus',
  8855 => 'otimes',
  8869 => 'perp',
  8901 => 'sdot',
  8968 => 'lceil',
  8969 => 'rceil',
  8970 => 'lfloor',
  8971 => 'rfloor',
  9001 => 'lang',
  9002 => 'rang',
  9674 => 'loz',
  9824 => 'spades',
  9827 => 'clubs',
  9829 => 'hearts',
  9830 => 'diams');


//  this will encode all 'stray' (X)HTML entities as needed without effecting 'valid' existing markup.
//  this will also convert all upper-case XHTML tag names to lower-case (HTML tags will be left alone).
//  it will also apply nl2br "intelligently", and collapse spaces to your liking:

//  if  $nl_to_br=TRUE  the proper (X)HTML entity ( <br> or <br /> depending on the value of $xhtml)
//    will be added into the string BEFORE any line breaks, if not there already.
//  If $strip_CtrlChrs=TRUE and $retain_formatting=FALSE the line break character(s) will still be removed,
//    but the <br> or <br /> will be inserted.

//  if  $allowJS=FALSE  all JavaScript attributes within otherwise valid HTML tags will be deleted,
//    but the tag will remain valid.
//  Any JavaScript code containing an angle bracket <> will invalidate the HTML tag, even when $allowJS=FALSE,
//    and the entire tag, including the JavaScript even when $allowJS=FALSE, will be encoded to HTML entities.
//    Do any JavaScript comparisons and processing in an outside function.

//  $modulate_multispace  may also be:  TRUE, FALSE.
//    by default ('toEntities'), multiple spaces are converted into a &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; format;
//      and a space following a new-line is converted into a &nbsp; also. (see also $hardspace)
//    if TRUE, multiple spaces are collapsed into one single space.
//    if FALSE, multiple spaces are passed unchanged.

//  if  $xhtml=FALSE  a much less stringent tag validation is used, as in the example below.
//  i.e.       Jessie & Me are <font color=red>&iexcl;>excited<!</font>
//  becomes    Jessie &amp; Me are <font color=red>&iexcl;&gt;excited&lt;!</font>
//  Note this passed variable also controls any output from $nl_to_br=TRUE, tag name to lowercase, and $tabspacing.

//  $charset  see http://www.php.net/manual/en/function.htmlentities.php/

//  if  $strip_CtrlChrs=TRUE  all control characters (ASCII 0-31) will be deleted,
//   EXCEPT for line breaks and tabs when $retain_formatting=TRUE.
//   Note that if $strip_CtrlChrs=FALSE, line breaks and tabs will remain, even if $retain_formatting=FALSE.

//  if  $transform_nonstandard_Ents='numeric'  or  $transform_nonstandard_Ents='mnemonic' [etc....]
//   then the secondary function transform_nonstandard_entities() will be called on the string automatically
//   after converting all stray characters to Entities.  Note if you transform to 'mnemonic' and then
//   align to 'numeric', the latter will take precedence.  But with the inverse, if you transform to 'numeric'
//   and then align to 'mnemonic', the nonstandard entities will remain 'numeric'.

//  if  $alignEnts='numeric'  or  $alignEnts='mnemonic' [etc....] then the secondary function
//   XML_alignEntities() will be called on the string automatically as a final process after converting
//   all stray characters to Entities.  Note that $transform_nonstandard_Ents will also be passed along
//   for compatibility; see the comments for that function for more info.

//  $tabspacing  number of "spaces" to expand the tab character to.  The tab char is then discarded.
//    Note with $xthml=TRUE, the spaces have a &nbsp; &nbsp; &nbsp; format (see also $hardspace),
//    and tab spacing is always an even number.
//  If $tabspacing=(FALSE or 0), no tabs will be converted to spaces.

//  $hardspace  alternatively:  "&#160;" (same as &nbsp;)  or  "&#32;" (same as space)
//    "&#8194;" (same as &ensp; [enspace]) or  "&#8195;" (same as &emsp; [emspace]) or  "&#8201;" (same as &thinsp;)
//  When $modulate_multispace='toEntities' or when tabs expand,
//   $hardspace is the HTML entity that is repeated in sequence format.

//  $inaTag  is used internally when processing HTML tags, but you may also if your needs require.
//    if TRUE, quotes '' "" will -not- be converted into an html entity.  Also, when $modulate_multispace='toEntities',
//      only multiple spaces inside single or double quotes will be converted to the &nbsp; &nbsp; &nbsp; format;
//      and entities will not be "aligned" or "transformed".


//  Note this will NOT COMPLETELY validate your XHTML, just make sure it is well formed.

Public Function XHTMLencode( $S, // array(userOpt=>value, ...) ) /*note this alternative format for passing in options*/
  $nl_to_br=TRUE, $allowJS=TRUE, $modulate_multispace='toEntities', $xhtml=TRUE, $charset='ISO-8859-1',
  $retain_formatting=TRUE, $strip_CtrlChrs=TRUE, $alignEnts=FALSE, $transform_nonstandard_Ents=FALSE,
  $tabspacing=8, $hardspace='&nbsp;', $inaTag=FALSE )  {

// $allowJS=TRUE (see above) is optimized for use with a webmaster's database,
// --NOT-- to process user POSTs from the World Wide Web for inclusion on a public page.


  $Xchars = array(
  128 => array('&#8364;', '&euro;'),
  130 => array('&#8218;', '&sbquo;'),
  131 => array('&#402;',  '&fnof;'),
  132 => array('&#8222;', '&bdquo;'),
  133 => array('&#8230;', '&hellip;'), //mis-spelled in O'Reily® publications as &hellep;
  134 => array('&#8224;', '&dagger;'),
  135 => array('&#8225;', '&Dagger;'),
  136 => array('&#710;',  '&circ;'),
  137 => array('&#8240;', '&permil;'),
  138 => array('&#352;',  '&Scaron;'),
  139 => array('&#8249;', '&lsaquo;' /*nonstandard*/ ),
  140 => array('&#338;',  '&OElig;'),
  142 => array('&#381;',  '&#381;'),
  145 => array('&#8216;', '&lsquo;'),
  146 => array('&#8217;', '&rsquo;'),
  147 => array('&#8220;', '&ldquo;'),
  148 => array('&#8221;', '&rdquo;'),
  149 => array('&#8226;', '&bull;'),
  150 => array('&#8211;', '&ndash;'),
  151 => array('&#8212;', '&mdash;'),
  152 => array('&#732;',  '&tilde;'),
  153 => array('&#8482;', '&trade;'),
  154 => array('&#353;',  '&scaron;'),
  155 => array('&#8250;', '&rsaquo;' /*nonstandard*/ ),
  156 => array('&#339;',  '&oelig;'),
  158 => array('&#382;',  '&#382;'),
  159 => array('&#376;',  '&Yuml;') );


  if (func_num_args()==2  and  is_array($nl_to_br))  { $userArgs=$nl_to_br;  $nl_to_br=TRUE;
    $userOpts=array('nl_to_br', 'allowJS', 'modulate_multispace', 'xhtml', 'charset',
      'retain_formatting', 'strip_CtrlChrs', 'alignEnts', 'transform_nonstandard_Ents',
      'tabspacing', 'hardspace', 'inaTag');
    foreach ($userArgs as $var => $value)  {if (in_array($var, $userOpts))  $$var=$value;}  }

  $S=str_replace(array('&hellep;', '&ap;'), array('&hellip;', '&asymp;'), $S); //mis-spelled in O'Reily® publications

  $sl=strlen($S);  $build="";  $SPflag=FALSE;  $PREflag=TRUE;  $inQuotes=FALSE;
  $line_term=(strpos($S, chr(10))!==FALSE) ? 10 : 13;  //auto-support WINDOWS / UNIX / Linux / MAC line breaks
  switch (strtolower((string)$alignEnts))  {  // choose which entity form to use for non-standard entities (0x80 - 0x9F) (i.e. $Xchars)
  case 'numeric': {$repForm=0;  break;}
  case 'monikeric':
  case 'cognomic':
  case 'mnemonic':
  case 'alphabetic':
  case 'grammatic':
  case 'linguistic':
  case 'phonetic':
  case 'named':
  default: {$repForm=1;}  }

                          // if you WANT to allow tabs & linebreaks (and other ctrl chars) inside tag attribute VALUES
  $tagRegEx=($xhtml) ?                   //  delete below:     \x00-\x1F           \x00-\x1F
      '/^<(\/?)([a-z]{1,10})([ \t\n\r]+[a-z]{2,12}=[ \t\n\r]*("[^\x00-\x1F"<>]*"|\'[^\x00-\x1F\'<>]*\'))*( \/)?>/i'  //longest attribute: marginheight for iframe, onmouseover (etc) for all others; tags not included may have longer.
    : '/^<(\/?)([a-z]{1,10})([ \t\n\r]+[^<>]*)?>/i';       // allows tabs, linebreaks, etc, inside HTML tags  */
/*  : '/^<(\/?)([a-z]{1,10})([ ]+[^\x00-\x1F<>]*)?>/i';   // forbids tabs, linebreaks, etc, inside HTML tags  */

// Regular Expressions to find and remove JavaScript from HTML tags.
// When combined with the general xhtml tag filter, this will catch MOST JavaScript, but no guarantees!
  $jsRegEx=array( 'patterns' => array(
'/on(click|dblclick|keydown|keypress|keyup|mousedown|mousemove|mouseout|mouseover|mouseup|focus|blur)[ \t\n\r]*=[ \t\n\r]*([\'"`]).*\2/i',
'/(href|src|background)[ \t\n\r]*=[ \t\n\r]*([\'"`])javascript:.*\2/i',
'/(style)[ \t\n\r]*=[ \t\n\r]([\'"`]).*javascript.*\2*/',
'/([a-z]+)[ \t\n\r]*=[ \t\n\r]([\'"`])&{.*}\2*/' ),
                  'replacements' => array(
"",
"$1=''",
"$1=''",
"" ) );

  for ($i=0; $i<$sl; $i++)  { $cc=ord($sS=substr($S, $i));
    if ($nl_to_br  and  $cc==0x0D  and  $PREflag  and  preg_match('/<br( \/)?>$/', $build)==0)  {
      $build.=(($xhtml) ? "<br />" : "<br>");  $SPflag=FALSE;  }
    if ($modulate_multispace!==TRUE  and  $cc==0x09  and  $tabspacing  and  $PREflag  and  ($inQuotes  or  !$inaTag)) {
      $build.=( ($modulate_multispace==='toEntities') ?
        str_repeat((($SPflag) ? "$hardspace " : " $hardspace"), $tabspacing/2)
      : str_repeat(" ", $tabspacing) );
      continue;  }
    if ($PREflag  and  $strip_CtrlChrs  and  $cc<0x20  and  !($retain_formatting  and  ($cc==0x09  or  $cc==0x0D)))
      continue;  // pass over control characters
    if ($cc==0x20  and  $modulate_multispace  and  $SPflag  and  $PREflag)  {
      if ($modulate_multispace==='toEntities')  {
        if ($inQuotes  or  !$inaTag)  {$build.=$hardspace;  $SPflag=FALSE;}
        else  $build.=" ";  }
      continue;  }
    if ($SPflag=($cc==0x20  or  $cc==$line_term /*chr(10) / chr(13)*/))  {$build.=chr($cc);  continue;}
    if ($inaTag  and  ($cc==0x22  or  $cc==0x27))  {
      $build.=chr($cc);
      $inQuotes=($inQuotes===$cc) ? FALSE : (($inQuotes===FALSE) ? $cc : $inQuotes);
      continue;  }
    if ($xhtml  and  $cc>0x7F  and  $cc<0xA0)  {$build.=($Xchars[$cc]) ? $Xchars[$cc][$repForm] : " ";  continue;}
    if (preg_match('/^&(#?[a-z0-9]{2,8});/i', $sS, $susp)  // longest is &thetasym;
    and  (in_array($susp[1], self::$HTML_ENTS)  or  preg_match('/^#(x[0-9a-f]{2,4}|[0-9]{2,5})$/i', $susp[1])))  {
      $build.=$susp[0];  $i+=strlen($susp[0])-1;  $SPflag=FALSE;  continue;  }
    if ($xhtml  and  $inaTag  and  !$inQuotes  and  preg_match('/^[a-z]+/i', $sS, $toLC))  {
      $build.=strtolower($toLC[0]);  $i+=strlen($toLC[0])-1;  continue;  }
    if (preg_match($tagRegEx, $sS, $susp) // longest is <blockquote>
    and  in_array($susp[2]=strtolower($susp[2]), $this->HTML_tags))  {
        $sS=($xhtml) ?
          ($susp[1].$susp[2].substr($susp[0], $l=strlen($susp[1])+strlen($susp[2])+1, strlen($susp[0])-$l-1))
        : substr($susp[0], 1, strlen($susp[0])-2);
        $build.="<"
              .self::XHTMLencode(($allowJS) ? $sS : preg_replace($jsRegEx['patterns'], $jsRegEx['replacements'], $sS),
                FALSE,  // do not allow nl_to_br inside a tag
$allowJS,$modulate_multispace,$xhtml,$charset,$retain_formatting,$strip_CtrlChrs,$alignEnts,$transform_nonstandard_Ents,$tabspacing,$hardspace,
                TRUE)   // do not convert quotes/apostrophes inside a tag
              .">";
        $i+=strlen($susp[0])-1;  $SPflag=FALSE;
        if (($x=strpos($sS, 'pre'))!==FALSE  and  $x<2)  $PREflag=(boolean)$x;
        continue;  }
    $build.=htmlentities(substr($sS, 0, 1), ($xhtml) ? ENT_QUOTES : 0, $charset);  }

  if (!$inaTag  and  $transform_nonstandard_Ents)
    $build=self::transform_nonstandard_entities($build, $transform_nonstandard_Ents);
  return (!$inaTag  and  $alignEnts) ?
    self::XML_entitiesAlign($build, $alignEnts, $transform_nonstandard_Ents)
  : $build;  }


// This will convert all entities to either numeric format (when $alignTo='numeric'),
//  or (when $alignTo='mnemonic' etc...) to textual format if such exists for that entity.
// It will avoid converting nonstandard Entities to 'mnemonic' if you specify them separately as 'numeric'.
// If you pass an array in as $nonstandard_Ents (instead of the default string-based-flag format)
//  then the array will be used as the Entity Table (for use with custom XML docs), instead of the default $HTML_ENTS.

Static Public Function XML_entitiesAlign($S, $alignTo='numeric', $nonstandard_Ents='numeric')  {
  $Ents= (is_array($nonstandard_Ents)) ? $nonstandard_Ents : self::$HTML_ENTS;
  if (is_string($nonstandard_Ents))  $nonstandard_Ents=strtolower($nonstandard_Ents);
  $offset=0;
  switch (strtolower($alignTo))  {
  case 'numeric': {
    preg_match_all('/&([a-z1-9]{2,8});/i', $S, $susp, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE);   // '/&([a-z1-4]{2,8});/i' is strictly for HTML Entities, but restricts user-defined Entities using digits 5-9
    foreach ($susp[1] as $entity)  {
      if ($x=array_search($entity[0], $Ents))  { $x='#'.$x;
        $S=substr_replace($S, $x, $entity[1]+$offset, $sl=strlen($entity[0]));
        $offset+=strlen($x)-$sl;  }  }
  break;  }
  case 'monikeric':
  case 'cognomic':
  case 'mnemonic':
  case 'alphabetic':
  case 'grammatic':
  case 'linguistic':
  case 'phonetic':
  case 'named': {
    preg_match_all('/&#(x[0-9a-f]{2,4}|[0-9]{2,4});/i', $S, $susp, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
    foreach ($susp as $entity)  {
      $x= (stripos($entity[0][0], "x")) ? hexdec($entity[1][0]) : (int)$entity[1][0];
      if (($x==39  or  $x==8249  or  $x==8250)  and  $nonstandard_Ents==='numeric')  continue;
      if (isset($Ents[$x]))  {
        $S=substr_replace($S, $Ents[$x], $entity[1][1]+$offset-1, $sl=strlen($entity[1][0])+1);
        $offset+=strlen($Ents[$x])-$sl;  }  }
  break;  }  }
  return $S;  }


// This straightforward function simply takes your string and transforms the three "nonstandard" Entities
//  either to/from  numeric  to/from  mnemonic
// It will transform both decimal and hexadecimal numericals to mnemonic
//  but only from mnemonic to decimal.

Static Public Function transform_nonstandard_entities($S, $flag='numeric')  {
  $mnemonic=array('&apos;', '&apos;',  '&apos;', '&lsaquo;', '&lsaquo;', '&rsaquo;', '&rsaquo;');
  $numeric= array('&#39;',  '&#039;',  '&#x27;', '&#8249;',  '&#x2039;', '&#8250;',  '&#x203A;');
  switch (strtolower($flag))  {
  case 'numeric': {$S=str_replace($mnemonic, $numeric, $S);  break;}
  case 'monikeric':
  case 'cognomic':
  case 'mnemonic':
  case 'alphabetic':
  case 'grammatic':
  case 'linguistic':
  case 'phonetic':
  case 'named': {$S=str_replace($numeric, $mnemonic, $S);  break;}  }
  return $S;  }


Public Function get_entities()  {return self::$HTML_ENTS;}

  }  //close RegulateText Class
?>