<?php

@( (bool)FALSE === strpos( $_SERVER['REQUEST_URI'], basename( __FILE__ ) ) ) OR exit( '!! ACCESS DENIED !!' );
error_reporting( _E_VALUE );

/* vim: set expandtab tabstop=4 shiftwidth=4: */

// ***** BEGIN LICENSE BLOCK ( DO NOT REMOVE ) *****

// +----------------------------------------------------------------------+
// | WebSanitizer For MPN 188_8                                           |
// |                                                                      |
// | -> Web Security Front End Input/Output Content Sanitizing            |
// | -> Request URI, Link, And Basic Form Validation                      |
// | -> Content Censoring                                                 |
// | -> Custom Html Formatting Replacements/Adjustments                   |
// | -> Automated Link And Smiley Conversions                             |
// | -> BBCode Tag Support                                                |
// +----------------------------------------------------------------------+
// | VERSION: 0.3 Alpha                                                   |
// +----------------------------------------------------------------------+
// | LICENSE: GPL 2.0 (see docs/LICENSE_GPL)                              |
// |                                                                      |
// | This file is part of WebSanitizer For MPN 188_8.                     |
// |                                                                      |
// | WebSanitizer For MPN 188_8 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 2       |
// | of the License, or (at your option) any later version.               |
// |                                                                      |
// | WebSanitizer For MPN 188_8 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 WebSanitizer For MPN 188_8; if not, write to the          |
// | Free Software Foundation, Inc.,                                      |
// | 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA               |
// +----------------------------------------------------------------------+
// | AUTHOR(s):                                                           |
// |                                                                      |
// | The Original Code is the WebSanitizer For MPN 188_8.                 |
// | The Initial Developer of the Original Code is Eric Haddix.           |
// |                                                                      |
// | Portions created by Eric Haddix or Kiril Threndor                    |
// | <support@websanitizer.illearth.net> are                              |
// | Copyright (C) 2003 Eric Haddix.  All Rights Reserved.                |
// |                                                                      |
// | Portions from htmlfilter.inc v. 1.1, created by                      |
// | Konstantin Riabitsev <icon@linux.duke.edu> are                       |
// | Copyright (C) 2002-2003 by Duke University.  All Rights Reserved.    |
// +----------------------------------------------------------------------+
// | CONTRIBUTOR(s):                                                      |
// |                                                                      |
// |                                                                      |
// +----------------------------------------------------------------------+
// | MODIFICATIONS:                                                       |
// |                                                                      |
// |                                                                      |
// +----------------------------------------------------------------------+
// | REQUIRED FILES: core_Sanitize.param.php                              |
// |                 core_AllowArrays.param.php ( Optional )              |
// +----------------------------------------------------------------------+
// | NOTES:                                                               |
// |   This Class is designed to work in conjunction with an appropriate  |
// |   subclass.  You will need to build your own subclass(es) or use     |
// |   the ones provided with this class.                                 |
// |                                                                      |
// |   The oringal htmlfilter source code can be found at:                |
// |   http://linux.duke.edu/projects/mini/htmlfilter/                    |
// +----------------------------------------------------------------------+
// | SUPPORT:                                                             |
// |   WEB:   http://myphpnuke.com/                                       |
// |   EMAIL: support@websanitizer.illearth.net                           |
// +----------------------------------------------------------------------+
// $Id: core_Sanitize.class.php,v 1.48.2.24 2004/08/24 19:15:30 kirilt Exp $

// ***** END LICENSE BLOCK ( DO NOT REMOVE ) *****


class Sanitizer
{
    /**
     * Default Parameter File ( relative to class root )
     *
     * If you need to make adjustments to the default sanitizing parameters
     * it is recommended you do it in the parameter file and not this class.
     * The class parameter defaults defined below should remain intact as
     * a default safeguard.
     *
     * Note: Do not use prefix slash if in a sub-directory of class root
     *
     * @var string
     */

    var $def_sanparam         = 'inc/core_Sanitize.param.php';

    /**
     * Process String Type ID
     *
     * This sets how to process the string
     * See parameter comments in method sanitize
     *
     * @var bool
     */

    var $proc_type            = 1;

    /**
     * BB Encoder Enabled
     *
     * @var bool
     */

    var $bbencoder_on         = FALSE;

    /**
     * Enable / Disable Smiley Encoding
     *
     * @var bool  ( Default FALSE )
     */

    var $encode_smileys       = FALSE;

    /**
     * Available Smiles Array
     *
     * @var array ( Default NULL )
     */

    var $smile_ary            = array();

    /**
     * Absolute Path To Directory Where Smiles Are Kept
     *
     * Example: http://mydomain.com/images/smiles/
     *
     * NOTE: Must Contain Trailing Slash
     *
     * @var string
     */

    var $smile_uri            = NULL;

    /**
     * Make Text "Links" Clickable
     *
     * @var bool ( Default FALSE )
     */

    var $make_clickable       = FALSE;

    /**
     * Enable / Disable Return Literal
     *
     * @var bool ( Default FALSE )
     */

    var $return_literal       = FALSE;

    /**
     * Holds Bool Value To Remove/Allow HTML Tags
     * Once Shifted off the $taglist array
     *
     * @var bool ( default TRUE removes ALL tags if taglist array is empty )
     */

    var $rmtags               = TRUE;

    /**
     * Holds Bool Value To Remove/Allow BBCode Tags
     * Once Shifted off the $bbtaglist array
     *
     * @var bool ( default TRUE removes ALL tags if bbtaglist array is empty )
     */

    var $bbrmtags             = TRUE;

    /**
     * Holds Bool Value To Replace Tags
     * Once Shifted off the $rpltag_list array
     *
     * @var bool ( default FALSE -> Replace None )
     */

    var $rpl_tags             = FALSE;

    /**
     * Enable / Disable tag closing
     *
     * Tags set in self_closing_tags array
     *
     * @var bool
     */

    var $force_tag_closing    = FALSE;

    /**
     * Protect Mode
     *
     * Indicates If In Protect Mode Or Not
     *
     * @var bool
     */

    var $protect_mode         = FALSE;

    /**
     * Body length of text being processed
     *
     * @var integer
     */

    var $body_length          = 0;

    /**
     * BBCode Delimiters
     *
     * @var array
     */

    var $bb_delim             = array( '[' , ']' );

    /**
     * Default Print Delimiters
     *
     * @var array
     */

    var $html_delim           = array( '<', '>' );

    /**
     * Allowed Opening Delimiter Array
     * Provides For Dynamic Tag Checking
     *
     * @var array
     */

    var $delim_open           = array( '<', '[' );

    /**
     * Allowed Closing Delimiter Array
     * Provides For Dynamic Tag Checking
     *
     * @var array
     */

    var $delim_close          = array( '>', ']' );

    /**
     * Default Opening Tag Delimiter
     *
     * @var string
     */

    var $open_delim           = '<';

    /**
     * Default Closing Tag Delimiter
     *
     * @var string
     */

    var $close_delim          = '>';

    /**
     * Protected Tag Name Holder
     *
     * @var array
     */

    var $ptag = NULL;

    /**
     * Track Opening Protected Tag Delimiter If In Protect Mode.
     *
     * @var array
     * $syntax array( $open_delim, $close_delim )
     * @default NULL (leave at default)
     */

    var $ptag_delims = NULL;

    /**
     * Strip Tags And Tag Content By Delimiter
     *
     * This arrays consists of opening delimters for
     * tags and their content you want unconditionally removed
     *
     * @var array
     */

    var $strip_tags           = array();

    /**
     * Allow / Disallow HTML Tag Array
     *
     * @var array
     */

    var $taglist              = array
                                (
                                      FALSE
                                    , 'html'
                                    , 'head'
                                    , 'meta'
                                    , 'link'
                                    , 'body'
                                    , 'frameset'
                                    , 'frame'
                                    , 'blink'
                                    , 'object'
                                    , 'font'
                                    , 'iframe'
                                    , 'layer'
                                    , 'ilayer'
                                    , 'form'
                                    , 'script'
                                    , 'plaintext'
                                );


	/**
	 * BBCode Runtime TagList
	 *
	 * @var	array
	 */

	var	$bbtaglist		      = array
                                (
                                     TRUE
                                   , 'code'
                                   , 'quote'
                                   , 'b'
                                   , 'i'
                                   , 'list'
                                   , 'li'
                                   , 'uri'
                                   , 'img'
                                   , 'addsig'
                                );

    /**
     * Optional List Of Tags To Use As Replacements
     * for tags in the $tag_list array
     *
     * To Be Implemented At A Later Time
     *
     * Default: Replace None
     *
     * @var array
     */

    var $rpltag_list          = array();

    /**
     * Store Valid Links From Body
     *
     * Enable/Disable Link Storage
     *
     * @var bool
     */

    var $track_links          = FALSE;

    /**
     * Validate Links From Body On Secondary Scans
     *
     * Enable/Disable Secodary Link Check
     *
     * Secondary link validation is more
     * restrictive than the primary sanitizing check
     *
     * @var bool
     */

    var $validate_links       = TRUE;

    /**
     * Restricted Characters In Request URI And Links
     *
     * String MUST be compatible with function preg_match()
     * It is highly recommended you leave this value at it's default
     * Related methods scan for restricted characters ONLY
     *
     * @var string ( regex compatible with preq_match() )
     * @default '/[\\\\:\/\<\>\.\*\(\)\[\]\{\}\$\^\!\s\%\|]/'
     * @access private
     * @note The default will cause an "access denied" in GET requests
     *       if they contain spaces.  If you want to allow spaces in GET
     *       requests, remove the "\s" from the regex string
     */

    var $_uri_restrict_char = '/[\\\\:\/\<\>\.\*\(\)\[\]\{\}\$\^\!\s\%\|]/';

    /**
     * Valid Links From Body
     *
     * @var array
     */

    var $valid_links          = array(
                                         'a'      => array()
                                       , 'img'    => array()
                                       , 'form'   => array()
                                       , 'script' => array()
                                       , 'applet' => array()
                                     );

    /**
     * Store Form Tag Names
     *
     * Store the names of the form elements
     *
     * @var bool
     */

    var $track_forms          = FALSE;

    /**
     * Array Of Allowed Form Tag Names
     *
     * Used to store form element names
     *
     * @var array
     */

    var $form_tags            = array(
                                          'form'     => array()
                                        , 'input'    => array( 'name' => array() )
                                        , 'select'   => array( 'name' => array() )
                                        , 'option'   => array( 'name' => array() )
                                        , 'textarea' => array( 'name' => array() )
                                        , 'button'   => array( 'name' => array() )
                                     );

    /**
     * Array Of Form Variables From The Above form_tag_list
     *
     * Used to store form element names
     *
     * @var array
     */

    var $form_variables       = array();

    /**
     * Array of Form ID's
     *
     * @var array
     */

    var $form_ids             = array();

    /**
     * Current Form ID Being Processed
     *
     * @var integer
     */

    var $form_id              = 0;

    /**
     * Form Counter
     *
     * Tracks The Current Form Number
     *
     * @var int
     */

    var $form_cnt             = 0;

    /**
     * Form Number
     *
     * Expected number of forms in a particular scan
     * This number when set should match the size of
     * the form ID array EXACTLY
     *
     * @var int
     */

    var $form_num             = 0;

    /**
     * Tags To Skip Additional Action On
     * During Scans Done On Free Content
     *
     * @var array
     */

    var $skip_action_tag      = array( 'a', 'uri', 'img' );

    /**
     * Protect Content Tag Array
     * Default: Protect None
     *
     * @var array
     */

    var $protect_content      = array( 'code', 'pre', 'lit' );

    /**
     * Tags that must have content along with the tag
     *
     * @var array
     */

    var $rm_tags_with_content = array
                                (
                                      'script'
                                    , 'style'
                                    , 'applet'
                                    , 'embed'
                                    , 'head'
                                    , 'frameset'
                                    , 'frame'
                                    , 'xml'
                                    , 'form'
                                );

    /**
     * Tags that should self close ( XHTML )
     *
     * @var array
     */

    var $self_closing_tags    = array
                                (
                                      'br'
                                    , 'hr'
                                    , 'input'
                                    , 'li'
                                    , 'addsig'
                                    , 'img'
                                );

    /**
     * Remove all attributes from allowed tags
     * listed in this array
     *
     * @var array
     */

    var $rm_all_att           = array
                                (
                                      'script'
                                    , 'style'
                                    , 'applet'
                                    , 'embed'
                                    , 'head'
                                    , 'frameset'
                                    , 'xml'
                                    , 'form'
                                );

    /**
     * Remove bad attributes names
     *
     * @var array
     */

    var $rm_attnames          = array
                                (
                                    '/.*/' => array
                                    (
                                          '/target/i'
                                        , '/^on.*/i'
                                        , '/^dynsrc/i'
                                        , '/^datasrc/i'
                                        , '/^data.*/i'
                                        , '/^lowsrc/i'
                                    )
                                );

    /**
     * Remove bad attribute values
     *
     * @var array
     */

    var $bad_attvals          = array
                                (
                                    '/.*/' => array
                                    (
                                        '/.*/' => array
                                        (
                                            array
                                            (
                                                  '/^([\'\"])\s*\S+\s*script\s*:*(.*)([\'\"])/si'
                                                , '/^([\'\"])\s*https*\s*:(.*)([\'\"])/si'
                                                , '/^([\'\"])\s*mocha\s*:*(.*)([\'\"])/si'
                                                , '/^([\'\"])\s*about\s*:(.*)([\'\"])/si'
                                            )

                                            ,array
                                            (
                                                  '\\1oddjob:\\2\\1'
                                                , '\\1uucp:\\2\\1'
                                                , '\\1amaretto:\\2\\1'
                                                , '\\1round:\\2\\1'
                                            )
                                        )

                                        ,'/^style/i' => array
                                        (
                                            array
                                            (
                                                  '/expression/i'
                                                , '/behaviou*r/i'
                                                , '/binding/i'
                                                , '/include-source/i'
                                                , '/url\s*\(\s*([\'\"]*)\s*https*:.*([\'\"]*)\s*\)/si'
                                                , '/url\s*\(\s*([\'\"]*)\s*\S+\s*script:.*([\'\"]*)\s*\)/si'
                                            )

                                            ,array
                                            (
                                                  'idiocy'
                                                , 'idiocy'
                                                , 'idiocy'
                                                , 'idiocy'
                                                , 'url(\\1http://securityfocus.com/\\1)'
                                                , 'url(\\1http://securityfocus.com/\\1)'
                                            )
                                        )
                                    )
                                );

    /**
     * Fix bad attribute values
     *
     * @var array
     */

    var $fix_bad_attr_vals    = array();

    /**
     * Allowed http:// Attribute Values
     * tag => attname array
     *
     * @var array
     */

    var $allow_http_attval    = array
                                (
                                      'a'   => array( 'href' )
                                    , 'uri' => array( 'href' )
                                    , 'img' => array( 'src' )
                                );

    /**
     * Add Attributes To Tags
     *
     * @var bool
     */

    var $add_attrib           = FALSE;

    /**
     * Array Of Attributes To Add
     *
    * @var array
     */

    var $add_attr_to_tag      = array
                                (
                                    'a' => array( 'target' => '"_blank"' )
                                );

    /**
     * Censor Body Text
     *
     * @var bool
     */

    var $censor               = TRUE;

  	/**
	 * Strip Slashes
	 *
	 * @var	bool  DEFAULT =	TRUE ( Stripem )
	 */

	var	$strip_slashes	      = TRUE;

	/**
	 * Add Position Comments In Free Content
	 *
	 * @var	bool  DEFAULT =	FALSE
	 */

	var	$add_pos_comm	      = FALSE;

	/**
	 * Position Comment Insert Increment
	 *
	 * This will insert a position comment every n
	 * characters at the closest match to n in free content
	 *
	 * @var	integer  DEFAULT = 500
	 */

	var	$add_pos_inc	      = 500;

	/**
	 * Tracks Previous Comment Insert Position
	 *
	 * @var	interger  DEFAULT = 0
	 */

	var	$add_pos_tracker      = 0;

	/**
	 * Check Restricted Domains.
	 *
	 * URL list is in file restricted_doms.csv
	 * To add a URL to the list, enter any valid part of a domain
	 * you want to restrict.  One URL per line starting on line 3.
	 *
	 * This checks content that runs through method validateContent.
	 *
	 * @var	bool  DEFAULT = TRUE
	 */

	var	$_chk_restricted_doms = TRUE;

	/**
	 * Check All Content For Restricted Domains.
	 *
	 * Same as above, except this will run scan all content, not just content
	 * that gets run through method validateContent.
	 *
	 * CAUTION: This could be expensive, use with caution.
	 *
	 * @var	bool  DEFAULT = FALSE
	 */

	var	$chkall_restricted_doms = TRUE;

	/**
	 * Restricted URL Array.
	 *
     * Array of restricted URL's extracted from ./classes/restricted_doms.csv
	 *
	 * @var	bool  DEFAULT = TRUE
	 */

	var	$restricted_doms = array();


/**
 * Class Constructor
 *
 * This is where the initial sanitizing parameters are set.
 * See method sanitize for a more complete rundown on the arrays
 *
 * @param  $allow_html   ( bool   -> allow HTML or un-conditionally deny )
 * @param  $param_arrays ( string -> path and filename to custom sanitizing parameter arrays )
 * @param  $allowed_tags ( array  -> allowed / disallowed tag array )
 * @param  $censor       ( bool   -> Enable / Disable Word Censoring )
 *
 * @return void.
 */

function Sanitizer( $allow_html = TRUE, $param_arrays = NULL, $allowed_tags = NULL, $censor = TRUE )
    {
        $num_args = func_num_args();
        $init = (bool)FALSE;

        if ( $num_args == 4 )
        {
            // Unconditionally Strip ALL Tags And Content Containing This Delimiter

            if ( ! $allow_html ) { $this -> strip_tags = array( '<' ); }

            // Core Sainitizing Parameter Arrays

            if ( ! empty( $param_arrays ) ) { $init = $param_arrays; }

            // Allowed / Disallowed Tags

            if ( ! empty( $allowed_tags ) ) { $this -> taglist = $allowed_tags; }

            // Censor Words

            if ( ! $censor ) { $this -> censor = (bool)FALSE; }
        }

        // Initialize The Class Variables

        if ( $this -> censor )
        {
            if ( $list = file( './censor_list.csv' ) )
            {
                foreach ( $list as $v )
                {
                    $list_tmp = explode( ',' , $v );

                    $tmp1 = trim( $list_tmp[0] );
                    $tmp2 = trim( $list_tmp[1] );

                    $this -> censor_list[]    = "/\b$tmp1\b/si";
                    $this -> censor_replace[] = "$tmp2";
                }

                unset( $list, $list_tmp, $tmp1, $tmp2 );
            }
        }

        // Get Restricted URL's Array If Required

        if ( $this -> _chk_restricted_doms )
        {
            if ( @is_file( _DP_CLASS_PAR . 'restricted_doms.csv' ) )
            {
                $this -> restricted_doms = @file( _DP_CLASS_PAR . 'restricted_doms.csv' );
                unset( $this -> restricted_doms[0], $this -> restricted_doms[1] );
                $this -> trimContent( $this -> restricted_doms );
            }
        }

		// Turn Off Strip Slashes If Magic Quotes Is Off

		if ( ! get_magic_quotes_gpc() )
		{
			$this -> strip_slashes = (bool)FALSE;
		}

        if ( empty( $init ) && defined( '_DPF_SANITY' ) )
        {
            $init = _DPF_SANITY;
        }

        if ( $init && require $init )
        {
            // Allowed / Disallowed Tags

            if ( empty( $this -> taglist ) )
            {
                $this -> taglist = $tag_list;
            }

            if ( is_bool( $this -> taglist[0] ) )
            {
                $this -> rmtags = array_shift( $this -> taglist );
            }

            // Normalize

            array_walk( $this -> taglist, array( $this, 'casenormalize' ) );

            // Optional Replacement Tag Array For Above Tags

            if ( ! empty( $rpltag_list ) )
            {
                $this -> rpltag_list = $rpltag_list;

                if ( is_bool( $this -> rpltag_list[0] ) )
                {
                    $this -> rpl_tags = array_shift( $this -> rpltag_list );
                }
            }

            // Remove Tags And Content Contained Within Unconditionally

            if ( ! empty( $rm_tags_with_content ) )
            {
                $this -> rm_tags_with_content = $rm_tags_with_content;
                array_walk( $this -> rm_tags_with_content, array( $this, 'casenormalize' ) );
            }

            // Fix Bad HTML ( self-closing tags )

            if ( ! empty( $self_closing_tags ) )
            {
                $this -> self_closing_tags = $self_closing_tags;
                array_walk( $this -> self_closing_tags, array( $this, 'casenormalize' ) );
            }

            // Disallowed Attributes

            if ( ! empty( $rm_all_att ) )
            {
                $this -> rm_all_att = $rm_all_att;
            }

            // Disallowed Attributes

            if ( ! empty( $rm_attnames ) )
            {
                $this -> rm_attnames = $rm_attnames;
            }

            // Bad Attribute Values

            if ( ! empty( $bad_attvals ) )
            {
                $this -> bad_attvals = $bad_attvals;
            }

            // Fix Bad Attribute Values

            if ( ! empty( $fix_bad_attr_vals ) )
            {
                $this -> fix_bad_attr_vals = $fix_bad_attr_vals;
            }

            // Allowed http:// Attribute Values tag => attname array

            if ( ! empty( $allow_http_attr_vals ) )
            {
                 $this -> allow_http_attval = $allow_http_attr_vals;
            }

            // Add Attributes To Tags

            if ( ! empty( $add_attr_to_tag ) )
            {
                $this -> add_attr_to_tag = $add_attr_to_tag;
            }
        }
        else
        {
            exit( '<h1>!! SECURITY COMPROMISED !!</h1>' );
        }

    } // End Constructor


/**
 * #################### ORIGINAL HELPER METHODS ####################
 *
 * The following three methods are helper functions that were
 * part of the original htmlfilter.inc, but were modified for the class.
 *
 */

    /**
     * This is a debugging method used throughout the code. To enable
     * debugging you have to change the class variable debug to TRUE
     * and uncomment the appropriate (or all) calls to this method.
     *
     * @param  $message ( string -> Message to output. )
     * @return void.
     */

function wsDump( $message )
    {
       if ( $this -> debug )
        {
            echo '<br />' . $message;
        }

    } // End Method wsDump

    /**
     * This method skips any whitespace from the current position within
    * a string and to the next non-whitespace value.
     *
     * @param  $body   ( string -> Content string to process )
     * @param  $offset ( integer -> the offset within the string where we should start
     *                              looking for the next non-whitespace character. )
     *
     * @return integer ( the integer position within the $body where the next
     *                   non-whitespace char is located. )
     */

function skipspace( $body, $offset )
    {
        if ( 0 !== ( preg_match( '/^(\s*)/s', substr( $body, $offset ), $matches ) ) )
        {
            $count = strlen( $matches[1] );

            //$this -> wsDump( "Method skipspace: skipped $count chars\n" );

            $offset += $count;
        }

        return $offset;

    } // End Method skipspace

/**
 * A small helper method to use with array_walk. Modifies a by-ref
 * value and makes it lowercase.
 *
 * @param &$val ( string -> content to normalize )
 * @return void ( $val passed by reference )
 */

function casenormalize( &$val )
    {
        $val = strtolower( $val );

    } // End Method casenormalize


/**
 * #################### EXTENDED METHODS ####################
 *
 * These methods are an extension of htmlfilter.inc and are not
 * a part of the original code.
 *
 * One exception is method checkTagName which was built
 * for the WebSanitizer in order to reduce redundant code
 * in the orignal functions
 */


/**
 * Dump	Current	Values Of Class	Variables
 *
 * @param string ( name of the class you want variables dumped for )
 *
 * return void
 */

function dumpVariables( $classname )
	{
		echo"<br /><br />";

		foreach( get_class_vars( $classname	) AS $key => $v	)
		{
			var_dump( "<b>Variable {$key}:</b> ", $this	-> $key	);

			echo"<br /><br />";
		}

	} // End Method	dumpVariables

/**
 * Add Or Strip Slashes
 *
 * @param &$content ( string -> String To Slash ( add or strip )
 * @param $slash    ( bool -> Add = TRUE , DEFAULT: Strip = FALSE )
 *
 * @return void ( $content passed by reference )
 */

function slashIT( &$content, $slash = FALSE )
    {
        $FUNC = ( (bool)TRUE === $slash ) ? ( 'addslashes' ) : ( 'stripslashes' );

        if ( is_array( $content ) )
        {
            foreach( $content AS $key => $v )
            {
                if ( is_array( $v ) )
                {
                    $this -> slashIT( $v, $slash );
                }

                $content[ $key ] = $FUNC ( $v );
            }
        }
        else
        {
            $content = $FUNC ( $content );
        }

        return $content;

    } // End Method slashIT

/**
 * Undo HTML Special Character Conversion
 *
 * @param &$string ( string -> content to convert )
 *
 * @return void ( string passed by reference )
 */

function undoHtmlSpecialChars( &$string )
    {
        //$this -> wsDump( "Method undoHtmlSpecialChars: Undoing htmlspecialchars\n" );

        $ary = array
        (
              "&"  => "&amp;"
            , ">"  => "&gt;"
            , "<"  => "&lt;"
            , "\"" => "&quot;"
            , "'"  => "&apos;"
            , " "  => "&sp;"
        );

        $ary_undo =& $ary;

        $convert_check = (    (bool)FALSE == strpos( $string, "&amp;" )
                           || (bool)FALSE == strpos( $string, "&lt;" ) );


        if ( ! $convert_check )
        {
            $string = str_replace( array_values( $ary_undo ), array_keys( $ary_undo ), $string );
        }

        return;

    } // End Method undoHtmlSpecialChars

/**
 * Two Way Special Character Conversion
 *
 * Converts To Character Reference Or Entity Name
 *
 * @param &$string ( string  -> String To Convert Special Chars In
 * @param $type    ( integer -> Type Of Replacement To Perform ( either do (1) or undo (2) )
 * @param $use_ary ( integer -> Which Replacement Array To Use. Default is 2 )
 * @param $undo    ( array   -> Optional Array Of Characters to Undo Conversion On )
 *
 * @return void ( string passed by reference )
 */

function protectContent( &$string, $type, $use_ary = 2, $undo = NULL )
    {
        //$this -> wsDump( "Method protectContent: Converting Special Characters in a " . strlen( $string ) . " char string\n" );

        $convert_check  = (    (bool)FALSE === strpos( $string, "&#" )
                            && (bool)FALSE === strpos( $string, "&quot" ) );

        // Entity Value Reference Array

        $ent_ref_ary = array
        (
              "&"  => "&amp;"
            , "<"  => "&lt;"
            , ">"  => "&gt;"
            , "/"  => "&sol;"
            , "["  => "&lsqb;"
            , "]"  => "&rsqb;"
            , "("  => "&lpar;"
            , ")"  => "&rpar;"
            , "$"  => "&dollar;"
            , "\\" => "&bsol;"
            , "'"  => "&apos;"
            , "\"" => "&quot;"
            , "?"  => "&quest;"
            , " "  => "&sp;"
        );

        // Character Reference Value Reference Array

        $chr_ref_ary = array
        (
              '&'  => "&#038;"
            , "\t" => "&#009;"
            , "\n" => "&#010;"
            , "\r" => "&#013;"
            , "\s" => "&#032;"
            , ' '  => "&#032;"
            , '!'  => "&#033;"
            , '"'  => "&#034;"
            , '$'  => "&#036;"
            , '%'  => "&#037;"
            , "'"  => "&#039;"
            , '('  => "&#040;"
            , ')'  => "&#041;"
            , '*'  => "&#042;"
            , '+'  => "&#043;"
            , ','  => "&#044;"
            , '-'  => "&#045;"
            , '.'  => "&#046;"
            , '/'  => "&#047;"
            , ':'  => "&#058;"
            , '<'  => "&#060;"
            , '='  => "&#061;"
            , '>'  => "&#062;"
            , '?'  => "&#063;"
            , '@'  => "&#064;"
            , '['  => "&#091;"
            , "\\" => "&#092;"
            , ']'  => "&#093;"
            , '^'  => "&#094;"
            , '_'  => "&#095;"
            , '`'  => "&#096;"
            , '{'  => "&#123;"
            , '|'  => "&#124;"
            , '}'  => "&#125;"
            , '~'  => "&#126;"
        );

        // Options For Special Char Conversion

        switch( $use_ary )
        {
            // Convert To Characters Reference Value
            // This Array Should Only Be Used If Using
            // The $undo_ary ( Type 3 ) to Decode
            // It Is Intended For Posting Code
            // ( or any other type of text you want literally displayed )
            // To A Web Page

            case 1:

                $ary =& $chr_ref_ary;

                $ary_undo = array
                (
                      "\"" => "&#034;"
                    , "$"  => "&#036;"
                    , "<"  => "&lt;"
                    , ">"  => "&gt;"
                );

            break;

            // Limited Conversion To Characters Entity Name

            case 2:

                $ary =& $ent_ref_ary;

                $ary_undo =& $ary;

            break;

            // Limited Conversion To Characters Entity Name
            // This Matches The Array Of Chars In Method undoHtmlSpecialChar
            // It Is Duplicated Here If You Need Different Undo Options

            case 3:

                $ary = array
                (
                      "&"  => "&amp;"
                    , ">"  => "&gt;"
                    , "<"  => "&lt;"
                    , "\"" => "&quot;"
                    , "'"  => "&apos;"
                    , " "  => "&sp;"
                );

                $ary_undo =& $ary;

            break;

        } // End Array Options Switch

        // Reset $ary_undo if required

        if ( ! empty( $undo ) ) { $ary_undo =& $undo; }

        // Replacement Switch

        switch( $type )
        {
            // Convert $use_ary To Reference Values

            case 1:

                if ( ! $convert_check )
                {
                    $string = str_replace( array_values( $ary ), array_keys( $ary ), $string );
                }

                $string = str_replace( array_keys( $ary ), array_values( $ary ), $string );

            break;

            // Convert Case 1 Back To Original Chars Using $ary

            case 2:

                $string = str_replace( array_values( $ary ), array_keys( $ary ), $string );

            break;

            // Convert Case 1 Back To Original Chars Using $ary_undo

            case 3:

                $string = str_replace( array_values( $ary_undo ), array_keys( $ary_undo ), $string );

            break;

            // Convert Using PHP htmlspecialchars

            case 4:

                if ( $convert_check )
                {
                    $string = htmlspecialchars( $string );
                }

            break;

            // Convert Using PHP htmlentities

            case 5:

                $string = htmlentities( $string, ENT_QUOTES );

            break;

            // Convert Case 4 & 5 Back To Original Chars

            case 6:

                if ( function_exists( 'html_entity_decode' ) )
                {
                    $string = html_entity_decode( $string, ENT_QUOTES );
                }
                else
                {
                    $this -> undoHtmlSpecialChars( $string );
                }

            break;

            // Convert Using PHP htmlspecialchars
            // Without Checking For Previous Conversions

            case 7:

                $string = htmlspecialchars( $string );

            break;

            // Convert all types of sp[ecial characters back to original

            case 8:

                $srch = array_merge( $ent_ref_ary, $chr_ref_ary );
                $string = str_replace( $srch, array_keys( $srch ), $string );

            break;

        } // End Switch

        return;

    } // End Method protectContent

/**
 * Adjust Formatting For Display In Either
 * Textarea Or Screen Output
 *
 * @param &$string   ( string  -> String To Convert Special Chars In
 * @param $repl_type ( integer -> Type Of Adjustment To Perform. Default is 1 )
 * @param $use_ary   ( integer -> Which Replacement Array To Use. Default is 1 )
 * @param $opt_repl  ( array   -> Optional Array Of Replacement Characters )
 *
 * @return void ( $string passed by reference )
 */

function adjustFormat( &$string, $repl_type = 1, $use_ary = 1, $opt_repl = NULL )
    {
        // Reference Array

        $ref_ary = array
        (
              "<br>"
            , "<BR>"
            , "<br />"
            , "<BR />"
            , "&lt;br&gt;"
            , "&lt;BR&gt;"
            , "&lt;br /&gt;"
            , "&lt;BR /&gt;"
            , "&#060;br&#062;"
            , "&#060;BR&#062;"
            , "&#060;br /&#062;"
            , "&#060;BR /&#062;"
            , "&#013;&#010;"
            , "&#010;"
            , "&#013;&#013;"
            , "\r\n"
            , "\n"
            , "&#019;"
            , "\t"
            , "&#032;"
            , " "
        );

        switch( $use_ary )
        {
            // Replace All In Reference Array

            case 1:

                $srch_html =& $ref_ary;

            break;

            // Replace True HTML Only

            case 2:

                $srch_html = array_slice( $ref_ary, 0, 4 );

            break;

            // Replace Entity Values Only

            case 3:

                $srch_html = array_slice( $ref_ary, 4, 4 );

            break;

            // Replace Reference Values Only

            case 4:

                $srch_html = array_slice( $ref_ary, 8, 4 );

            break;

            // Replace Entities And Character References ONLY

            case 5:

                $srch_html = array_slice( $ref_ary, 4, 8 );

            break;

            // Replace Control Characters ONLY

            case 6:

                $srch_html = array_slice( $ref_ary, 12, 5 );

            break;

            // Replace Tabs

            case 7:

                $srch_html = array_slice( $ref_ary, 17, 2 );

            break;

            // Replace Spaces

            case 8:

                $srch_html = array_slice( $ref_ary, 19, 2 );

            break;

            // Special Case -> Final Storage Format For Non HTML Only Posts
            // ( Use Type 3 Replacement )

            case 9:

                $srch_html = array
                (
                      "\r" => '&#013;'
                    , "\n" => '&#010;'
                    , "\t" => '&#009;'
                    , "\s" => '&#032;'
                    , ' '  => '&#032;'
                    , '<'  => '&lt;'
                    , '>'  => '&gt;'
                );

            break;

            // Special Case -> Prep Case 9 Conversion For Display
            // ( Use Type 3 Replacement )

            case 10:

                $srch_html = array
                (
                      '&lt;'                           => '<'
                    , '&gt;'                           => '>'
                    , '&#032;&#032;&#032;&#032;'       => '&nbsp;&nbsp;&nbsp;&nbsp;'
                    , '&#009;'                         => '&nbsp;&nbsp;&nbsp;&nbsp;'
                    , '&#032;'                         => ' '
                    , '&#013;&#010;<br />&#013;&#010;' => '<br />'
                    , '&#010;&#010;<br />&#010;&#010;' => '<br />'
                    , '&#013;&#010;<br />'             => '<br />'
                    , '&#010;&#010;<br />'             => '<br />'
                    , '<br />&#013;&#010;'             => '<br />'
                    , '<br />&#010;&#010;'             => '<br />'
                    , '&#013;&#010;'                   => '<br />'
                    , '&#010;&#010;'                   => '<br />'
                    , '&#010;'                         => '<br />'
                    , '&#013;'                         => '<br />'
                );

            break;

            // Special Case -> BBCode Edit
            // ( Use Type 3 Replacement )

            case 11:

                $srch_html = array
                (
                      '&#032;' => ' '
                    , '&lt;'   => '<'
                    , '&gt;'   => '>'
                );

            break;

            // Special Case -> Final Storage Format For Pure HTML
            // ( Use Type 3 Replacement )

            case 12:

                $srch_html = array
                (
                      '&lt;'   => '<'
                    , '&gt;'   => '>'
                );

            break;

        } // End Search Array Switch

        // Type Of Conversion To Perform And Its Related Replacement

        switch ( $repl_type )
        {
            // Convert To \n

            case 1:

                $repl = "\n";

            break;

            // Convert To Empty String

            case 2:

                $repl = '';

            break;

           // Convert To <br /> And &nbsp; ( srch_html array case 9 + 10 only )

            case 3:

                $repl = array_values( $srch_html );

            break;

            // Convert To Reference Line Feed

            case 4:

                $repl = "&#010;";

            break;

            // Reverse A Type 3 Conversion

            case 5:

                $srch = array_values( $srch_html );
                $repl = array_keys( $srch_html );

            break;

        } // End Type Switch

        if ( ! empty ( $opt_repl ) ) { $repl =& $opt_repl; }

        if ( $repl_type != 5 )
        {
            $srch = ( $repl_type == 3 ) ? ( array_keys( $srch_html ) ) : ( $srch_html );
        }

        $string = str_replace( $srch, $repl, $string );

        return $string;

    } // End Method adjustFormat

/**
 * Strip All Quotes From Input String
 *
 * @param $string ( string -> content to strip quotes from )
 * @param $limit  ( string -> what type of quote stripping to perform )
 *
 * @return quoteless string
 */

function stripQuotes( $string, $limit = 'all' )
    {
        switch( $limit )
        {
            case 'single':

                $srch = array( "'", "&apos;", "\\'" );

            break;

            case 'double':

                $srch = array( '"', "&quot;", '\"' );

            break;

            DEFAULT:

                $srch = array( '"', "'", "&quot;", "&apos;", '\"', "\\'" );

            break;

        } // End Switch

        $string = str_replace( $srch , '' , $string );

        return $string;

    } // End Method stripQuotes

/**
 * Censor Words In A Text Body
 *
 * @param &$body ( string -> content to censor )
 *
 * return void ( $body passed by reference )
 */

function censorContent( &$body )
    {
        if ( ! empty( $this -> censor_list ) && ! empty( $this -> censor_replace ) )
        {
            $body = preg_replace(   $this -> censor_list
                                  , $this -> censor_replace
                                  , $body
                                );
        }

       return;

    } // End Method censorContent

/**
 * Replaces A Super Global Value With Sanitized Value
 *
 * @param $key    ( string -> array key to check )
 * @param $rValue ( string -> replacement value )
 *
 * @return void
 */

function replaceGPC_Globals( $key, $rValue )
    {
        global $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS;

        if ( ! empty( $_REQUEST[ $key ] ) ) { $_REQUEST[ $key ] = $rValue; }

        if ( ! empty( $_GET[ $key ] ) ) { $_GET[ $key ] = $rValue; }

        if ( ! empty( $_POST[ $key ] ) ) { $_POST[ $key ] = $rValue; }

        if ( ! empty( $_COOKIE[ $key ] ) ) { $_COOKIE[ $key ] = $rValue; }

        if ( ! empty( $HTTP_GET_VARS[ $key ] ) ) { $HTTP_GET_VARS[ $key ] = $rValue; }

        if ( ! empty( $HTTP_POST_VARS[ $key ] ) ) { $HTTP_POST_VARS[ $key ] = $rValue; }

        if ( ! empty( $HTTP_COOKIE_VARS[ $key ] ) ) { $HTTP_COOKIE_VARS[ $key ] = $rValue; }

        if ( ! empty( $GLOBALS[ $key ] ) ) { $GLOBALS[ $key ] = $rValue; }

        return;

    } // End Method replaceGPC_Globals

/**
 * Simple User Input Validation
 *
 * @param $input ( Mixed -> string or string array of input content )
 * @param $type  ( integer -> scan type to perform )
 *
 * @return bool
 */

function validateInput( $input, $type )
    {
        if ( ( empty( $input ) || $input == '' ) && $type != 3 )
        {
            return (bool)TRUE;
        }

        switch( $type )
        {
            // Check URI

            case 1:

                $uri = $this -> stripQuotes( trim( $input ) );
                $scan = (bool)FALSE;

                if ( $uri === 'request_uri' )
                {
                    $uri =& $_SERVER['REQUEST_URI'];
                }

                $uri = urldecode( $uri );
                $this -> protectContent( $uri, 8 );

                if ( (bool)FALSE !== ( @parse_url( $uri ) ) )
                {
                    if ( $this -> _chk_restricted_doms && ! empty( $this -> restricted_doms ) )
                    {
                        foreach( $this -> restricted_doms AS $v )
                        {
                            if ( (bool)FALSE !== stristr( $uri, $v ) )
                            {
                                return (bool)FALSE;
                            }
                        }
                    }

                    $scan = substr( strrchr( $uri, '?' ), 1 );

                    if ( (bool)FALSE != $scan )
                    {
                        $arg_parts = explode( '&' , $scan );

                        foreach( $arg_parts AS $v )
                        {
                            if ( 0 !== preg_match( $this -> _uri_restrict_char, $v ) )
                            {
                                return (bool)FALSE;
                            }
                        }

                        return (bool)TRUE;
                    }
                    else
                    {
                        return (bool)TRUE;
                    }
                }

            break;

            // Check Email

            case 2:

                $input = trim( urldecode( $input ) );

                // Regex Matching Pattern

                $regex  = '/^(([^<>;()[\]\\.,;:@"]+(\.[^<>()[\]\\.,;:@"]+)*)|(".+"))@((([a-z]([-a-z0-9]*[a-z0-9])?)';
                $regex .= '|(#[0-9]+)|(\[((([01]?[0-9]{0,2})|(2(([0-4][0-9])|(5[0-5]))))\.){3}(([01]?[0-9]{0,2})';
                $regex .= '|(2(([0-4][0-9])|(5[0-5]))))\]))\.)*(([a-z]([-a-z0-9]*[a-z0-9])?)|(#[0-9]+)';
                $regex .= '|(\[((([01]?[0-9]{0,2})|(2(([0-4][0-9])|(5[0-5]))))\.){3}(([01]?[0-9]{0,2})';
                $regex .= '|(2(([0-4][0-9])|(5[0-5]))))\]))$/';

                // Run Checks

                $chk[] = ( 0 === preg_match( $regex, $input ) );
                $chk[] = ( (bool)FALSE !== strpos( $input, '\\' ) );
                $tmp = explode( '@', $input );
                $chk[] = ( sizeof( $tmp ) != 2 );
                $chk[] = ( strlen( $tmp[0] ) > 64 );
                $chk[] = ( strlen( $tmp[1] ) > 255 );
                $tmp2 = explode( '.', $tmp[1] );
                $chk[] = ( sizeof( $tmp2 ) < 2 );

                foreach( $tmp2 AS $dompart )
                {
                    $chk[] = ( strlen( $dompart ) > 63 );
                }

                if ( ! in_array( (bool)TRUE, $chk ) )
                {
                    return (bool)TRUE;
                }

            break;

            // Check Empty

            case 3:

                if ( $input != '' )
                {
                    return (bool)TRUE;
                }

            break;

            // Check Alpha Only

            case 4:

                if ( 0 === preg_match( '/[^a-zA-Z]/', $input ) )
                {
                    return (bool)TRUE;
                }

            break;

            // Check Num Only

            case 5:

                if ( 0 === preg_match( '/[^0-9]/', $input ) )
                {
                    return (bool)TRUE;
                }

            break;

             // Check Alpha & Num Only

            case 6:

                if ( 0 === preg_match( '/[^a-zA-Z0-9]/', $input ) )
                {
                    return (bool)TRUE;
                }

            break;

            // Check Minimum / Maximum String Lengths

            case 7:

                if ( ! is_array( $input ) ) { return; }

                $chk = ( $input[0] == 'min' )
                     ? ( strlen( $input[2] ) >= $input[1] )
                     : ( strlen( $input[2] ) <= $input[1] );

                if ( $chk )
                {
                    return (bool)TRUE;
                }

            break;

            case 8: // Chek Domain Name

                $chk['dom_length'] = ( strlen( $input ) > 255 );
                $regex1 = '/[^a-z^A-Z^0-9^\-]/';  // REGEX Allowed Characters
                $regex2 = '/[0-9]/'; // REGEX For All Numbers Check
                $regex3 = '/^\-$/';

                $tmp = explode( '.', $input );
                $chk['dom_parts_count'] = ( sizeof( $tmp ) >= 2 );

                foreach( $tmp AS $dompart )
                {
                    $chk['dom_label_length'] = ( strlen( $dompart ) > 63 );
                    var_dump('REGEX5_DOM', preg_match( $regex5, $dompart, $matches ), $matches);echo '<br /><br />' . "\n\n";
                    var_dump('REGEX6_DOM', preg_match_all( $regex6, $dompart, $matches ), $matches);echo '<br /><br />' . "\n\n";

                    $chk[ 'dom_regex_label = ' . $dompart ] = ( 0 !== preg_match( $regex5, $dompart ) );
                }

            break;

        } // End Switch

        return (bool)FALSE;

    } // End Method validateInput

/**
 * Call Back Method For makeClickable Method
 *
 * Takes incoming regex match array and returns
 * a replacement string
 *
 * @param $matches ( array -> array of matches from method makeClickable )
 *
 * @return string ( replacement string )
 */

function printURL( $matches )
    {
        $delim = ( $this -> bbencoder_on ) ? ( $this -> bb_delim ) : ( $this -> html_delim );
        $tag   = ( $this -> bbencoder_on ) ? ( 'uri' ) : ( 'a' );
        $matches[0] = trim( $matches[0] );
        $matches[5] = ( isset( $matches[5] ) ) ? ( trim( $matches[5] ) ) : ( NULL );
        if ( isset( $matches[9] ) ) { $matches[9] = trim( $matches[9] ); }
        if ( isset( $matches[11] ) ) { $matches[11] = trim( $matches[11] ); }

        if ( isset( $matches[5] ) && $matches[0] == $matches[5] )
        {
            return $matches[0];
        }
        else
        {
            if ( (bool)FALSE !== strpos( $matches[0], '@' ) )
            {
                if ( ! $this -> validateInput( $matches[0], 2 ) )
                {
                    return '';
                }
                else
                {
                   return $delim[0] . $tag . ' href="mailto:' . $matches[0]. '"' . $delim[1] . $matches[0] . $delim[0] . '/' . $tag . $delim[1];
                }
            }

            if ( ! $this -> validateInput( $matches[0], 1 ) )
            {
                return '';
            }
            else
            {
                if ( isset( $matches[9] ) )
                {
                    if ( $matches[9] == 'http' || $matches[9] == 'https' )
                    {
                        return $delim[0] . $tag . ' href="' . $matches[0]. '"' . $delim[1] . $matches[0] . $delim[0] . '/' . $tag . $delim[1];
                    }
                    else if ( $matches[11] == 'ftp' )
                    {
                        if ( (bool)FALSE === strpos( $matches[0], '://') )
                        {
                            return $delim[0] . $tag . ' href="ftp://' . $matches[0]. '"' . $delim[1] . $matches[0] . $delim[0] . '/' . $tag . $delim[1];
                        }
                        else
                        {
                            return $delim[0] . $tag . ' href="' . $matches[0]. '"' . $delim[1] . $matches[0] . $delim[0] . '/' . $tag . $delim[1];
                        }
                    }
                    else
                    {
                        return $delim[0] . $tag . ' href="http://' . $matches[0]. '"' . $delim[1] . $matches[0] . $delim[0] . '/' . $tag . $delim[1];
                    }
                }
                else
                {
                    return $matches[0];
                }
            }
        }

    } // End Callback Method printURL

/**
 * Match And Replace Plain Text Links
 * With A Clickable Version
 *
 * @param &$string ( string -> content to check for and convert text links )
 *
 * @return void ( $string passed by reference )
 */

function makeClickable( &$string )
    {
        $delim = ( $this -> bbencoder_on ) ? ( $this -> bb_delim ) : ( $this -> html_delim );
        $tag   = ( $this -> bbencoder_on ) ? ( 'uri' ) : ( 'a' );

        $delim[0] = preg_quote( $delim[0] );
        $delim[1] = preg_quote( $delim[1] );

        $mail  = '(([a-z0-9_]|\\-|\\.)+@([^[:space:]]*)([[:alnum:]-]))';
        $http1 = '([^\n|\r|\t|\s](?![0-9])[[:alnum:]]+)\.\w{3,}?
([^[:space:]]*)([[:alnum:]#?\/&=])';
        $http2 = '([[:alnum:]]+):\/\/([^[:space:]]*)([[:alnum:]#?\/&=])';

        $reg1 = '%(' . $delim[0] . $tag . '\s[^' . $delim[1] . ']+' . $delim[1];
        $reg2 = '[^\s]+' . $delim[0] . '/' . $tag . $delim[1] . ')|(' . $delim[0] . '[^' . $delim[1] . ']+';
        $reg3 = '[^' . $delim[0] . ']+' . $delim[1] . ')|';
        $reg4 = '[^\s|\n|\r(?!\.|\,|\!|\?|_|\-|\'|\"|\:|\;|\))]+%i';

        $search = array
        (
            $reg1 . $mail  . $reg2 . $mail  . $reg3 . $mail  . $reg4
          , $reg1 . $http1 . $reg2 . $http1 . $reg3 . $http1 . $reg4
          , $reg1 . $http2 . $reg2 . $http2 . $reg3 . $http2 . $reg4
        );

        $string = preg_replace_callback( $search, array( &$this, 'printURL' ), $string );

        return $string;

    } // End Method makeClickable

/**
 * Link Plain Text Smileys
 *
 * @param &$string ( string -> content to check for and convert text smileys )
 *
 * @return void ( $string passed by reference )
 */

function linkSmiley( &$string )
    {
        $delim = ( $this -> bbencoder_on ) ? ( $this -> bb_delim ) : ( $this -> html_delim );

        if ( ! empty( $this -> smile_ary ) && ! empty( $this -> smile_uri ) )
        {
            foreach ( $this -> smile_ary AS $key => $v )
            {
              $repl = $delim[0] . 'img src="' . $this -> smile_uri . $v . '" alt="' . $key . '" /' . $delim[1];

               $string = str_replace( $key, $repl, $string );
            }
        }

        return $string;

    } // End Method linkSmiley

/**
 * Process Free Content
 * This is content not enclosed by tags
 *
 * @param
 *
 * return string ( processed content )
 */

function freeContent( $string, $lt, $curpos, $cleanup = 0, $skip_action = FALSE )
    {
        $free_content = substr( $string, $curpos, ( $lt - $curpos ) );

        if ( $cleanup == 1 ) { $this -> trimContent( $free_content, TRUE, NULL, 'ltrim' ); }

        if ( $cleanup == 2 ) { $this -> trimContent( $free_content, TRUE, NULL, 'rtrim' ); }

        if ( strlen( $free_content ) == 0 ) { return ''; }

        if ( ! $skip_action )
        {
            if ( ! $this -> return_literal )
            {
                if ( $this -> encode_smileys )
                {
                    //$this -> wsDump( "Method freeContent: SMILES LINKED.\n" );
                    $this -> linkSmiley( $free_content );
                }

                if ( $this -> make_clickable )
                {
                    $this -> makeClickable( $free_content );
                    //$this -> wsDump( "Method freeContent: TEXT LINKS CONVERTED TO HTML.\n" );
                }
            }

            if ( $this -> censor ) { $this -> censorContent( $free_content ); }
        }

        //$this -> wsDump( "Method freeContent: " . strlen( $free_content ) . " chars grabbed.  Appending free content to trusted.\n" );

        return $free_content;

    } // End Method freeContent

/**
 * Trim Content Of Whitespace And/OR User Defined Character List.
 *
 * Trims while space from beginning and end of a string.
 *
 * @param mixed ( $string -> string or array of strings to trim )
 * @param bool ( $tws -> trim whitespace )
 * @param mixed ( $charlist -> string or array of string characters to trim )
 * @param string ( $_FUNC -> trim function to use )
 * @return string ( $string passed by reference ADN returned )
 */

function trimContent( &$string, $tws = TRUE, $charlist = NULL, $_FUNC = 'trim' )
{
    if ( is_array( $string ) )
    {
        foreach( $string AS $key => $arystr )
        {
            $string[ $key ] = $this -> trimContent( $arystr, $tws, $charlist, $_FUNC );
        }

        return $string;
    }

    if ( ! is_string( $string ) || strlen( $string ) == 0 )
    {
        return $string;
    }

    if ( ! empty( $charlist ) )
    {
        if ( is_array( $charlist ) )
        {
            foreach( $charlist AS $v )
            {
                $string = $_FUNC( $string, $v );
            }
        }
        else
        {
            $string = $_FUNC( $string, $charlist );
        }
    }

    if ( $tws )
    {
        $string = $_FUNC( $string, "\x20" );
        $string = $_FUNC( $string, "\x08..\x0F" );
        $string = $_FUNC( $string );
    }

    return $string;

} // End Method trimContent

/**
 * This method will protect the content of tags
 * defined in the protect_tag array
 *
 * Returns Protected Content And New Scan Postion
 *
 * @param &$body      ( content body )
 * @param $pos        ( position to start searching at )
 * @param $tagin      ( current tagname )
 *
 * @return array( $protected, $newpos )
 *
 * @return error bool(FALSE)
 */

function getProtectedContent( $body, $pos, $tagin, $delim = FALSE )
    {
        $this -> protect_mode = (bool)TRUE;
        $this -> ptag = $tagin;
        $this -> ptag_delims = array( $this -> open_delim, $this -> close_delim );
        //$this -> wsDump( "Method getProtectedContent: <b>PROTECTION MODE ENABLED</b>\n" );

        // Initialize Variables

        $coor            = array();
        $attary          = NULL;
        $strstart        = NULL;
        $tagname         = (bool)FALSE;
        $skip_content    = (bool)FALSE;
        $protected       = NULL;
        $final_string    = '';
        $startpos        = $pos;
        $newpos          = $pos + 1;
        $parent_open     = 0;
        $cnt = 0;

        // Scan Body For Tags

        //$this -> wsDump( "Method getProtectedContent: <b>Scanning Body For Protected Tags</b>\n" );

        while ( (bool)FALSE !== ( $ptag = $this -> getnxtag( $body, $pos ) ) )
        {
            list( $tagname, $attary, $tagtype, $lt, $gt ) = $ptag;

            $pos = $gt + 1;

            if ( (bool)FALSE !== $tagname && $tagname != '' && in_array( $tagname, $this -> protect_content ) )
            {
                $coor[ $cnt ] = array( $tagname, $attary, $tagtype, $lt, $gt );

                if ( $tagname == $tagin )
                {
                    if ( $tagtype == 1 ) { $parent_open++; }
                    if ( $tagtype == 2 ) { $parent_open--; }
                }

                if ( $parent_open >= 1 && $cnt != 0 ) { unset( $coor[ $cnt ] ); }
            }

            if ( $parent_open === 0 ) { break; }

            $cnt++;
        }

        // Check $coor Array, If Empty Return

        if ( empty( $coor ) )
        {
            //$this -> wsDump( "Method getProtectedContent: No Tags Found. Protection Mode Disabled  !! RETURNING !!</b>\n" );

            $this -> protect_mode = (bool)FALSE;

            return (bool)FALSE;
        }

        //$this -> wsDump( "Method getProtectedContent: Tag Scan Complete. <b>Starting Protection</b>\n" );

        // Loop Through The $coor Array And Protect Content

        foreach( $coor AS $key => $v )
        {
            list( $tagname, $attary, $tagtype, $lt, $gt ) = $v;

            $this -> checkTagName( $tagname );

            // Check Tag Name, If Not Allowed, Continue

            if ( (bool)FALSE === $tagname )
            {
                $strstart = $gt + 1;
                continue;
            }

            // If First Pass, Print The Tagname And Restart The Loop

            if ( ! isset( $strstart ) )
            {
                //$this -> wsDump( "Method getProtectedContent: <b>PROTECTING CONTENT IN TAG SET</b>\n" );
                $protected = $this -> tagprint( $tagname, $attary, $tagtype, $delim );
                $strstart = $gt + 1;
                continue;
            }

            $string = substr( $body, $strstart, ( $lt - $strstart ) );

            //$this -> wsDump( "Method getProtectedContent: <b>Protecting Tag Set Content</b>\n" );

            // Convert Characters To Reference Values

            $this -> protectContent( $string, 1, 1 );

            $final_string .= $string;

            $final_string .= $this -> tagprint( $tagname, $attary, $tagtype, $delim );

            $strstart = $gt + 1;

        } // End Foreach Loop

        if ( $this -> censor ) { $this -> censorContent( $final_string ); }

        $protected .= $final_string;
        $newpos = $strstart;

        $this -> protect_mode = (bool)FALSE;
        $this -> ptag_open_delim = NULL;

        //$this -> wsDump( "<br /><br />Method getProtectedContent: <b>FINAL PROTECTED STRING</b> -> {$protected}<br /><br />\n" );

       //$this -> wsDump( "Method getProtectedContent: <b>PROTECTION MODE DISABLED</b>. Content Protection Complete.  Returning.\n" );

        return array( $protected, $newpos );

    } // End Method getProtectedContent

/**
 * Check Tag Name Against Allowed Taglist
 *
 * @param &$tagname   Current tagname
 *
 * @return void       $tagname is passed by reference
 */

function checkTagName( &$tagname )
    {
        if ( in_array( $this -> open_delim, $this -> bb_delim ) )
        {
            if ( ! $this -> bbencoder_on ) { return; }

            if ( $this -> taglist != $this -> bbtaglist )
            {
                 $taglist = $this -> bbtaglist;
                 $rmtags = $this -> bbrmtags;
            }
        }
        else
        {
            $taglist = $this -> taglist;
            $rmtags = $this -> rmtags;
        }

        if (    (bool)FALSE === $tagname
             ||
                ( 0 === preg_match( '/[^0-9 ]/', $tagname ) )
             ||
                (    (bool)FALSE == $rmtags
                  && in_array( $tagname, $taglist ) )
             ||
                (    (bool)TRUE == $rmtags
                  && ! in_array( $tagname, $taglist ) )
           )
        {
            if ( $this -> protect_mode )
            {
                //$this -> wsDump( "Method checkTagName: In Protect Mode.  Going To End Of Tag Set And Removing.\n" );
            }
            else
            {
                //$this -> wsDump( "Method checkTagName: <h4>!! REMOVING THIS TAG !!</h4>.\n" );
            }

            $tagname = (bool)FALSE;
        }

        return;

    } // End Method CheckTagName

/**
 * Close Open Tags
 *
 * @param &$trusted   Trusted Content
 * @param $open_tags  Array holding open tag information
 *
 * @return void       $trusted is passed by reference
 */

function closeTags( &$trusted, $open_tags )
    {
        foreach ( $open_tags AS $tagname => $opentimes )
        {
            while ( $opentimes > 0 )
            {
                //$this -> wsDump( "Method closeTags: <b>{$tagname} Left Open.</b> CLOSING BY FORCE.\n" );
                if ( $this -> rpl_tags )
                {
                    $tag = substr( $tagname, 1, -1 );

                    if ( isset( $this -> rpltag_list['close'][ $tag ] )
                         && $this -> rpltag_list['close'][ $tag ] != ''
                       )
                    {
                        $tagname = str_replace(   $tag
                                                , $this -> rpltag_list['close'][ $tag ]
                                                , $tagname
                                              );
                    }
                    else
                    {
                        $tagname = '';
                    }
                }

                if ( $tagname != '' ) { $trusted .= $tagname; }
                $opentimes--;
            }
        }

    } // End Method closeTags

/**
 * Grab Request Variable Names From Forms
 *
 * This method can also be used to replace form element
 * attributes and reorder the presentation of the form.
 *
 * This feature is intended for secondary
 * single pass scans
 *
 * @param  $tagname  Tagname
 * @param  $attary   Array Of Attribute Names And Values
 *
 * @return bool on error, id string on success
 */

function trackFormVariables( $tagname, $attary )
    {
        $tagged = (bool)TRUE;

        // Generate An ID For The Form

        if ( $tagname == 'form' )
        {
            $this -> form_cnt++;

            if ( isset( $this -> form_ids[ $this -> form_cnt ]['id'] ) )
            {
                $this -> form_id = $this -> form_ids[ $this -> form_cnt ]['id'];
            }

            if ( isset( $this -> form_ids[ $this -> form_cnt ]['name'] ) )
            {
                $_form_name =   $this -> form_ids[ $this -> form_cnt ]['name']
                              . '_'
                              . $this -> form_cnt;
            }

            // Set The Form Atrribute Variables.
            // If "action" && "method" are not defined,
            // remove all attributes from the form tag

            if ( isset( $attary['action'] ) && isset( $attary['method'] ) )
            {
                if ( empty( $this -> form_variables ) )
                {
                    $this -> form_variables[ $this -> form_id ] = $this -> form_tags;
                }

                $this -> form_variables[ $this -> form_id ]['form'] = array
                (
                    'action' => $this -> stripQuotes( $attary['action'] )
                  , 'method' => $this -> stripQuotes( $attary['method'] )
                );

                $this -> form_variables[ $this -> form_id ]['input'][ 'name' ][] = $_form_name;

                $tagged = 'input type="hidden" name="' . $_form_name . '" value="' . $this -> form_id . '" /';
            }
            else // Invalid Form Found, Remove All Attributes From Form Tag
            {
                //$this -> wsDump( "Method trackFormVariables: <b>Found Form Without Required Attributes.  RESETTING ATTARY</b>\n" );

                return (bool)FALSE;
            }
        }

        // Fill The Form Variable Name Array

        if ( $tagname != 'form' && isset( $attary['name'] ) && is_array( $this -> form_variables[ $this -> form_id ] ) )
        {
            $attvalue = $this -> stripQuotes( $attary['name'] );

            $this -> form_variables[ $this -> form_id ][ $tagname ][ 'name' ][] = $attvalue;
        }

        return $tagged;

    } // End Method

/**
 * This Method Searchs A Multi-Dimensional Array For A Match To Needle
 *
 * @param string ( $needle   -> needle to search for )
 * @param array  ( $multiary -> Multi Dimensional Array To Search )
 *
 * @return bool match
 */

function searchMultiArray( $needle, $multiary )
    {
    	$match = (bool)FALSE;

    	if ( in_array( $needle, $multiary ) )
    	{
    		$match = (bool)TRUE;
    	}
    	else
    	{
    		foreach( $multiary AS $key => $v )
    		{
    		    if ( is_array( $multiary[ $key ] ) )
    			{
    				if ( $this -> searchMultiArray( $needle, $multiary[ $key ] ) )
    				{
    					$match = (bool)TRUE;
    					break;
    				}
    			}
    		}
    	}

    	return $match;

    } // End Method


/**
 * #################### CORE METHODS ####################
 *
 * These methods are the core of the sanitizer and are all
 * part of htmlfilter.inc.
 *
 * Modifications were made to the original code with respect to
 * class requirements, integration of new features, and
 * efficiency / performance adjustments
 */


/**
 * This function looks for the next character within a string.  It's
 * really just a glorified "strpos", except it catches the failures
 * nicely.
 *
 * @param  $body   The string to look for needle in.
 * @param  $offset Start looking from this position.
 * @param  $needle The character/string to look for.
 *
 * @return         location of the next occurance of the needle, or
 *                 strlen($body) if needle wasn't found.
 */

function findnxstr( $body, $offset, $needle, $retstr = FALSE )
    {
        if ( $offset >= $this -> body_length )
        {
            return $this -> body_length;
        }

        if ( is_array( $needle ) )
        {
            /**
             * If Needle Is An Array, We Are Looking For A New Tag
             * Search The Allowed Open Delimiter Array for A Match
             * If one is found, set opening and closing delimiter
             */

            $char_pos = array();

            foreach( $needle AS $key => $char )
            {
                if ( (bool)FALSE !== ( $pos = strpos( $body, $char, $offset ) ) )
                {
                    $char_pos[] = $pos;
                }
                else
                {
                    $char_pos[] = $this -> body_length;
                }

                $trk_neddle[] = $char;
            }

            if ( ! empty( $char_pos ) )
            {
                $char_pos      = array_unique( $char_pos );
                $nearest_match = min( $char_pos );
                $ary_flip      = array_flip( $char_pos );
                $ary_key       = $ary_flip[ $nearest_match ];

                if ( isset( $this -> delim_open[ $ary_key ] ) )
                {
                    $this -> open_delim  = $this -> delim_open[ $ary_key ];
                    $this -> close_delim = $this -> delim_close[ $ary_key ];

                    //$this -> wsDump( "Method findnxstr: <b>" . $this -> delim_open[ $ary_key ] . "</b> found at pos <b>$nearest_match</b>\n" );
                }

                if ( $retstr )
                {
                    return array( $nearest_match, $trk_neddle[ $ary_key ] );
                }
                else
                {
                    return $nearest_match;
                }
            }
        }
        else // String Needle Was Passed To Method
        {
            if ( (bool)FALSE !== ( $pos = strpos( $body, $needle, $offset ) ) )
            {
                //$this -> wsDump( "Method findnxstr: <b>$needle</b> found at pos <b>$pos</b>\n" );
                return $pos;
            }
        }

        return $this -> body_length;

    } // End Method findnxstr

/**
 * This function takes a PCRE-style regexp and tries to match it
 * within the string.
 *
 * @param  $body   The string to look for needle in.
 * @param  $offset Start looking from here.
 * @param  $reg    A PCRE-style regex to match.
 *
 * @return         Returns a false if no matches found, or an array
 *                 with the following members:
 *                 - integer with the location of the match within $body
 *                 - string with whatever content between offset and the match
 *                 - string with whatever it is we matched
 */

function findnxreg( $body, $offset, $reg )
    {
        $matches = array();
        $retarr  = array();

        $preg_rule = '%^(.*?)(' . $reg . ')%s';

        preg_match( $preg_rule, substr( $body, $offset ), $matches );

        if ( empty( $matches ) )
        {
            //$this -> wsDump( "Method findnxreg: No matches found.\n" );
            $retarr = (bool)FALSE;
        }
        else
        {
            $retarr[0] = $offset + strlen( $matches[1] );
            $retarr[1] = $matches[1];
            $retarr[2] = $matches[2];

            //$this -> wsDump( "Method findnxreg: <b>$reg</b> found at pos <b>$offset</b> matching <b>" . $matches[2] . "</b>\n" );
        }

        return $retarr;

    } // End Method findnxreg

/**
 * This function looks for the next tag.
 *
 * @param  $body   String where to look for the next tag.
 * @param  $offset Start looking from here.
 *
 * @return         false if no more tags exist in the body, or
 *                 an array with the following members:
 *                 - string with the name of the tag
 *                 - array with attributes and their values
 *                 - integer with tag type (1, 2, or 3)
 *                 - integer where the tag starts (starting "<")
 *                 - integer where the tag ends (ending ">")
 *                 first three members will be false, if the tag is invalid.
 */

function getnxtag( $body, $offset )
    {
        $me = 'Method getnxtag';

        if ( $offset > $this -> body_length )
        {
            //$this -> wsDump( "$me: Past the end of body\n" );

            return (bool)FALSE;
        }

        if ( $this -> body_length == ( $lt = $this -> findnxstr( $body, $offset, $this -> delim_open ) ) )
        {
            //$this -> wsDump( "$me: No more tags found!\n" );

            return (bool)FALSE;
        }

        /**
        * We are here:
        * blah blah <tag attribute="value">
        * \---------^
        */

        //$this -> wsDump( "$me: Found <b>" . $this -> open_delim . "</b> at pos <b>$lt</b>\n" );

        $pos = $this -> skipspace( $body, ( $lt + 1 ) );
        $c_delim = preg_quote( $this -> close_delim );

        if ( $pos >= $this -> body_length )
        {
            //$this -> wsDump( "$me: End of body reached.\n" );

            return array( FALSE, FALSE, FALSE, $lt, $this -> body_length );
        }

        /**
        * There are 3 kinds of tags:
        * 1. Opening tag, e.g.:
        *    <a href="blah">
        * 2. Closing tag, e.g.:
        *    </a>
        * 3. XHTML-style content-less tag, e.g.:
        *    <img src="blah"/>
        */

        $tagtype = (bool)FALSE;
        $chkchar = array( '/', '!', "'", '"', "\\", '?', '=' );
        $nxchar = substr( $body, $pos, 1 );

        if ( in_array( $nxchar, $chkchar ) )
        {
            switch ( $nxchar )
            {
                /**
                 * A closing tag.
                 */

                case '/':

                    //$this -> wsDump( "$me: This is a closing tag (type 2)\n" );

                    $tagtype = 2;
                    $pos++;

                break;

                /**
                 * A comment or an SGML declaration.
                 */

                case '!':

                    if ( substr( $body, ( $pos + 1 ), 2 ) == '--' )
                    {
                        if ( (bool)FALSE === ( $gt = strpos( $body, '-->', $pos ) ) )
                        {
                            $gt = $this -> body_length;
                        }
                        else
                        {
                            //$this -> wsDump( "$me: A comment found. Stripping.\n" );

                            $pos--;
                            $gt = $this -> findnxstr( $body, $pos, $this -> close_delim );
                        }
                    }
                    else
                    {
                      $gt = $this -> findnxstr( $body, $pos--, $this -> close_delim );

                      //$this -> wsDump( "$me: An SGML declaration found. Stripping.\n" );
                    }

                    return array( FALSE, FALSE, FALSE, $pos, $gt );

                break;

                /**
                 * If Not In Protect Mode, Set Tagtype To 1
                 *
                 * If In Protect Mode, Skip the next char
                 * And Continue Processing
                 */

                default:

                    if ( ! $this -> protect_mode )
                    {
                        $tagtype = 1;
                    }
                    else
                    {
                        //$this -> wsDump( "$me: In Protect Mode, Going To Next Char.\n" );

                        return array( FALSE, FALSE, FALSE, $pos, $lt + 1 );
                    }

                break;

            } // End Switch
        }

        /**
         * If We Made It Through The Switch
         * And Tagtype Is Not Defined, Assume Type 1.
         * If It's Type 3, We'll Switch Values Later.
         */

        $tagtype = ( (bool)FALSE === $tagtype ) ? ( 1 ) : ( $tagtype );
        $tagname = '';

        /**
         * Look for next [\W-_], which will indicate the end of the tag name.
         */

        if ( (bool)FALSE === ( $regary = $this -> findnxreg( $body, $pos, '[^\w\-_]' ) ) )
        {
            //$this -> wsDump( "$me: End of body reached while analyzing tag name\n" );

            return array( FALSE, FALSE, FALSE, $lt, $this -> body_length );
        }

        list( $pos, $tagname, $match ) = $regary;

        $this -> casenormalize( $tagname );

        if ( $this -> protect_mode && ( $this -> open_delim != $this -> ptag_delims[0] || $tagname != $this -> ptag ) )
        {
            $this -> open_delim = $this -> ptag_delims[0];
            $this -> close_delim = $this -> ptag_delims[1];

            return array( FALSE, FALSE, FALSE, $pos, $lt + 1 );
        }

        /**
         * $match can be either of these:
         * '>'  indicating the end of the tag entirely.
         * '\s' indicating the end of the tag name.
         * '/'  indicating that this is type-3 xhtml tag.
         *
         * Whatever else we find there indicates an invalid tag.
         */

        switch ( $match )
        {
            /**
             * This is an xhtml-style tag with a closing / at the
             * end, like so: <img src="blah"/>. Check if it's followed
             * by the closing bracket. If not, then this tag is invalid
             */

            case '/':

                if ( substr( $body, $pos, 2 ) == ( '/' . $this -> close_delim ) )
                {
                    //$this -> wsDump( "$me: XHTML-style tag found. Setting tagtype to 3\n" );

                    $pos++;
                    $tagtype = 3;
                }
                else
                {
                    //$this -> wsDump( "$me: Found invalid character '/'. <b>!! TAG IS INVALID. RETURNING !!</b>\n" );

                    $gt = $this -> findnxstr( $body, $pos, $this -> close_delim );

                    return array( FALSE, FALSE, FALSE, $pos, $gt );
                }

            break;

            /**
             * Check For End Of Tag
             */

            case $this -> close_delim:

                //$this -> wsDump( "$me: End of tag found at <b> {$pos} </b>\n" );
                //$this -> wsDump( "<br />$me: Tagname is <b> {$tagname} </b><br />\n" );
                //$this -> wsDump( "$me: This tag has no attributes\n" );

                return array( $tagname, FALSE, $tagtype, $lt, $pos );

            break;

            /**
             * Check if it's whitespace
             */

            default:

                if ( 0 === preg_match( '/\s/', $match ) )
                {
                    /**
                     * This is an invalid tag! Look for the next closing ">".
                     */

                    //$this -> wsDump( "$me: <b>INVALID CHARACTERS</b> found in tag name: <b>$match</b>\n" );
                    if ( $this -> protect_mode )
                    {
                        $this -> open_delim = $this -> ptag_delims[0];
                        $this -> close_delim = $this -> ptag_delims[1];
                    }

                    $gt = $this -> findnxstr( $body, $lt, $this -> close_delim );

                    return array( FALSE, FALSE, FALSE, $lt, $gt );
                }

                //$this -> wsDump( "$me: Tagname is <b>$tagname</b>\n" );

            break;

        } // End Switch

        /**
         * At this point we're here:
         * <tagname  attribute='blah'>
         * \-------^
         *
         * If not processing attibutes,
         * return the tags attributes if any exist
         *
         */

        if ( $this -> proc_type > 4 )
        {
            $attary  = array();

            $pos = $this -> skipspace( $body, $pos );

            $att_end = ( $tagtype == 3 ) ? ( 1 ) : ( 0 );

            $att_length = ( $this -> findnxstr( $body, $pos, $this -> close_delim ) - $att_end ) - $pos;

            $attary = array( substr( $body, $pos, $att_length ) );

            $gt = $this -> findnxstr( $body, $lt, $this -> close_delim );

            //$this -> wsDump( "$me: Skipping Attribute Checks. <b>!! RETURNING !!</b>\n" );

            return array( $tagname, $attary, $tagtype, $lt, $gt );
        }
        else
        {
            $attary  = array();
            $attname = '';
            $atttype = (bool)FALSE;
        }

        /**
         * Now we loop in order to find all attributes
         */

        while ( $pos <= $this -> body_length )
        {
            $pos = $this -> skipspace( $body, $pos );

            /**
             * Check for Non-closed tag.
             */

            if ( $pos >= $this -> body_length )
            {
                //$this -> wsDump( "$me: End of body reached before end of tag. <b>!! DISCARDING !!</b>\n" );

                return array( FALSE, FALSE, FALSE, $lt, $pos );
            }

            /**
             * See if we arrived at a ">" or "/>", which means that we reached
             * the end of the tag.
             */

            $matches = array();

            $match_closing_delim = '%^(\s*)(' . $c_delim . '|' . $c_delim . ')%s';

            if ( 0 !== preg_match( $match_closing_delim, substr( $body, $pos ), $matches ) )
            {
                //$this -> wsDump( "$me: Arrived at the end of the tag.\n" );

                $pos += strlen( $matches[1] );

                if ( $matches[2] == ( '/' . $this -> close_delim ) )
                {
                    $tagtype = 3;
                    $pos++;
                }

                return array( $tagname, $attary, $tagtype, $lt, $pos );
            }

            /**
             * There are several types of attributes, with optional
             * [:space:] between members.
             * Type 1:
             *   attrname[:space:]=[:space:]'CDATA'
             * Type 2:
             *   attrname[:space:]=[:space:]"CDATA"
             * Type 3:
             *   attr[:space:]=[:space:]CDATA
             * Type 4:
             *   attrname
             *
             * We leave types 1 and 2 the same, type 3 we check for
             * '"' and convert to "&quot" if needed, then wrap in
             * double quotes. Type 4 we convert into:
             * attrname="yes".
             */

            if ( (bool)FALSE == ( $regary = $this -> findnxreg( $body, $pos, '[^\w\-_]' ) ) )
            {
                /**
                 * Looks like body ended before the end of tag.
                 */

                //$this -> wsDump( "$me: End of body found before end of tag. <b>!! RETURNING !!</b>\n" );

                return array( FALSE, FALSE, FALSE, $lt, $this -> body_length );
            }

            list( $pos, $attname, $match ) = $regary;

            $this -> casenormalize( $attname );

            //$this -> wsDump( "$me: Attribute <b>$attname</b> found\n");

            /**
             * We arrived at the end of attribute name. Several things possible
             * here:
             * '>'  means the end of the tag and this is attribute type 4
             * '/'  if followed by '>' means the same thing as above
             * '\s' means a lot of things -- look what it's followed by.
             *      anything else means the attribute is invalid.
             */

            switch ( $match )
            {
               /**
                * This is an xhtml-style tag with a closing / at the
                * end, like so: <img src="blah"/>. Check if it's followed
                * by the closing bracket. If not, then this tag is invalid
                */

                case '/':

                    if ( substr( $body, $pos, 2 ) == ( '/' . $this -> close_delim ) )
                    {
                        //$this -> wsDump( "$me: This is an xhtml-style tag.  Setting tagtype to 3.\n" );
                        $pos++;
                        $tagtype = 3;
                    }
                    else
                    {
                        //$this -> wsDump( "$me: Found invalid character '/'. <b>!! TAG IS INVALID. RETURNING !!</b>\n");
                        $gt = $this -> findnxstr( $body, $pos, $this -> close_delim );

                        return array( FALSE, FALSE, FALSE, $lt, $gt );
                    }

                break;

                case $this -> close_delim:

                    //$this -> wsDump( "$me: found type 4 attribute.\n" );
                    //$this -> wsDump( "$me: Additionally, end of tag found at $pos\n" );
                    //$this -> wsDump( "$me: Attname is <b>$attname</b>\n" );
                    //$this -> wsDump( "$me: Setting attvalue to 'yes'\n" );

                    $attary[ $attname ] = '"yes"';

                    return array( $tagname, $attary, $tagtype, $lt, $pos );

                break;

                /**
                 * Skip whitespace and see what we arrive at.
                 */

                default:

                    $pos = $this -> skipspace( $body, $pos );

                    $char = substr( $body, $pos, 1 );

                    $match_closing_delim_1 = '|[\w/' . $c_delim . ']|';

                    /**
                     * Two things are valid here:
                     * '=' means this is attribute type 1, 2, or 3.
                     * \w means this was attribute type 4.
                     * anything else we ignore and re-loop. End of tag and
                     * invalid stuff will be caught by our checks at the beginning
                     * of the loop.
                     */

                    if ( $char == '=' )
                    {
                        //$this -> wsDump( "$me: Attribute type 1, 2, or 3 found.\n" );

                        $pos++;
                        $pos = $this -> skipspace( $body, $pos );

                        /**
                         * Here are 3 possibilities:
                         * "'"  attribute type 1
                         * '"'  attribute type 2
                         * everything else is the content of tag type 3
                         */

                        $quot = substr( $body, $pos, 1 );

                        if ( $quot == "'" || $quot == '"' )
                        {
                            if ( (bool)FALSE == $regary = $this -> findnxreg( $body, ( $pos + 1 ), $quot ) )
                            {
                               //$this -> wsDump( "$me: end of body reached before end of val. !! RETURNING !!\n" );

                                return array( FALSE, FALSE, FALSE, $lt, $this -> body_length );
                            }

                            list( $pos, $attval, $match ) = $regary;

                            $pos++;
                            $attary[ $attname ] = $quot . $attval . $quot;
                        }
                        else
                        {
                            $match_closing_delim_1 = '[\s' . $c_delim . ']';

                            if ( (bool)FALSE == ( $regary = $this -> findnxreg( $body, $pos, $match_closing_delim_1 ) ) )
                            {
                                //$this -> wsDump( "$me: end of body reached before end of val. <b>!! RETURNING !!</b>\n");
                                return array( FALSE, FALSE, FALSE, $lt, $this -> body_length );
                            }

                            list( $pos, $attval, $match ) = $regary;

                            /**
                             * If it's ">" it will be caught at the top.
                             */

                            if ( (bool)FALSE !== ( $qtcnt = preg_match_all( '/\"\/\'/s', $attval, $qt ) ) )
                            {
                                $attval = $this -> stripQuotes( stripslashes( $attval ) );

                                $attary[ $attname ] = '"' . $attval . '"';
                            }
                        }
                    }
                    else if ( 0 !== preg_match( $match_closing_delim, $char ) || isset( $this -> fix_bad_attr_vals[ $tagname ] ) )
                    {
                        /**
                         * That was attribute type 4.
                         */

                        //$this -> wsDump( "$me: attribute type 4 found.  Setting value to 'yes\n" );
                        $attary[ $attname ] = '"yes"';
                    }
                    else
                    {
                        /**
                         * An illegal character. Find next '>' and return.
                         */

                        //$this -> wsDump( "$me: illegal character <b>" . htmlspecialchars( $char ) . "</b> found. <b>!! RETURNING  !!</b>\n");

                        $gt = $this -> findnxstr( $body, $pos, $this -> close_delim );

                        return array( FALSE, FALSE, FALSE, $lt, $gt );
                    }

                break;

            } // End Switch

        } // End While/Do

        /**
         * The fact that we got here indicates that the tag end was never
         * found. Return invalid tag indication so it gets stripped.
         */

        //$this -> wsDump( "$me: No tag end found\n" );

        return array( FALSE, FALSE, FALSE, $lt, $this -> body_length );

    } // End Method getnxtag

/**
 * This function checks attribute values for entity-encoded values
 * and returns them translated into 8-bit strings so we can run
 * checks on them.
 *
 * @param  $attvalue A string to run entity check against.
 * @return           Translated value.
 */

function deent( $attvalue )
    {
        /**
         * This check is more or less a wrapper around stripslashes. Apparently
         * Explorer is stupid enough to just remove the backslashes and then
         * execute the content of the attribute as if nothing happened.
         * Who does that?
         */

        if ( (bool)FALSE !== strstr( $attvalue, '\\' ) )
        {
            $attvalue = stripslashes( $attvalue );

            //$this -> wsDump( "Method deent: After Unbackslashing: {$attvalue}\n" );
        }

        /**
         * See if we have to run the checks first. All entities must start
         * with "&".
         */

        if ( (bool)FALSE === strpos( $attvalue, '&' ) )
        {
            return $attvalue;
        }

        /**
         * Check named entities first.
         */

        //$this -> wsDump( "Method deent: translating named entities\n" );

        $trans = get_html_translation_table( HTML_ENTITIES );

        /**
         * Leave &quot; in and remove it
         * from the attribute if found
         * as it can mess us up.
         */

        $trans = array_flip( $trans );

        unset( $trans['&quot;'] );

        while ( list( $ent, $val ) = each( $trans ) )
        {
            $attvalue = preg_replace( '/' . $ent . '*/si', $val, $attvalue );
        }

        /**
         * Now translate numbered entities from 1 to 255 if needed.
         */

        if ( (bool)FALSE !== strpos( $attvalue, '#' ) )
        {
            //$this -> wsDump( "Method deent: translating numbered entities\n" );

            $omit = array( 34, 39 );

            for ( $asc = 256; $asc >= 0; $asc-- )
            {
                if ( ! in_array( $asc, $omit ) )
                {
                    $chr = chr( $asc );
                    $octrule = '/\&#0*' . $asc . ';*/si';
                    $hexrule = '/\&#x0*' . dechex($asc) . ';*/si';

                    $attvalue = preg_replace( $octrule, $chr, $attvalue );
                    $attvalue = preg_replace( $hexrule, $chr, $attvalue );
                }
            }
        }

        /**
         * Kill any tabs, newlines, or carriage returns. Our friends the
         * makers of the browser with 95% market value decided that it'd
         * be funny to make "java[tab]script" be just as good as "javascript".
         */

        if ( strcspn( $attvalue, "\t\r\n" ) != strlen( $attvalue ) )
        {
            //$this -> wsDump( "Method deent: found tabs, newlines, or carriage returns. Killing.\n" );

            $attvalue = str_replace(   array( "\t", "\r", "\n" )
                                     , array( '', '', '' )
                                     , $attvalue
                                   );
        }

        //$this -> wsDump( "Method deent: translated into: $attvalue\n" );

        return $attvalue;

    } // End Method deent

/**
 * This function runs various checks against the attributes.
 *
 * @param  $tagname         String with the name of the tag.
 * @param  $attary          Array with all tag attributes.
 * @return                  Array with modified attributes.
 */

function fixatts( $tagname, $attary, $tagtype )
    {
        $me = 'Method fixatts';

        //$this -> wsDump( "$me: Fixing attributes\n" );

        $slashchars = array( '\a', '\b', '\f', '\n', '\r', '\t', '\v', '\x', '\\\\' );

        while ( list( $attname, $attvalue ) = each( $attary ) )
        {
            /**
             * See if this attribute should be removed.
             */

            if ( ! empty( $this -> rm_attnames ) )
            {
                foreach ( $this -> rm_attnames AS $matchtag => $matchattrs )
                {
                    if ( 0 !== preg_match( $matchtag, $tagname ) )
                    {
                        foreach ( $matchattrs AS $matchattr )
                        {
                            if ( 0 !== preg_match( $matchattr, $attname ) )
                            {
                                //$this -> wsDump( "$me: Attribute <b>$attname</b> defined as bad. <b>!! REMOVING !!</b>\n" );

                                unset( $attary[ $attname ] );
                                continue;
                            }
                        }
                    }
                }
            }

            /**
             * Remove any backslashes. See if there are any first.
             */

            if ( (bool)FALSE !== strstr( $attvalue, '\\' ) )
            {
                //$this -> wsDump( "$me: backslashed characters found, compensating\n" );

                preg_match_all( '/\\\./', $attvalue, $matches );

                $slashed = $matches[0];

                foreach ( $slashed AS $slashchar )
                {
                    if ( ! in_array( $slashchar, $slashchars ) )
                    {
                        //$this -> wsDump( "$me: REMOVING BACKSLASHES from <b>$slashchar</b>\n" );

                        $char = substr( $slashchar, 1, 1 );

                        $attvalue = preg_replace( '/\\' . $slashchar . '/', $char, $attvalue );
                    }
                }
            }

            /**
             * Remove any entities.
             * If An Entity Is Found To Be Invalid, Remove All Attributes
             * By Re-initializing The $attary and breaking the loop
             */

            if ( (bool)FALSE === ( $attvalue = $this -> deent( $attvalue ) ) )
            {
                $attary = array();
                break;
            }

            /**
             * Now let's run checks on the attvalues.
             * I don't expect anyone to comprehend this. If you do,
             * get in touch with me so I can drive to where you live and
             * shake your hand personally. :)
             */

            $allow_http = ( isset( $this -> allow_http_attval[ $tagname ] ) && in_array( $attname, $this -> allow_http_attval[ $tagname ] ) );

            /**
             * Check if attribute value is a link, if it is
             * attemp to parse it.
             *
             * If syntax like http://somewhere.com?myvar=http://yoursite.com/sensitivefile
             * is discovered, the parse attempt fails and returns bool FALSE
             *
             * If deent returns false, the entire attribute array is discarded for this tag
             */

            if ( $this -> validate_links && $allow_http )
            {
                if ( ! $this -> validateInput( $attvalue, 1 ) )
                {
                    $attary = array();
                    break;
                }
            }

            if ( $allow_http && $this -> track_links )
            {
                if ( (bool)FALSE === strpos( $attvalue, 'mailto:' ) && (bool)FALSE === strpos( $attvalue, 'news:' ) )
                {
                    $this -> valid_links[ $tagname ][ $attname ][] = $attvalue;
                }
            }

            /**
             * If allowing an http type attribute value,
             * check it's position before allowing skip checking.
             * If it's position is not 0, scan the attribute
             */

            if ( $allow_http && ( $attname == 'href' || $attname == 'src' ) && 1 !== strpos( $attvalue, 'http://' ) && 1 !== strpos( $attvalue, './' ) )
            {
                $allow_http = (bool)FALSE;
            }

            if ( ! $allow_http && ! empty( $this -> bad_attvals ) )
            {
                foreach ( $this -> bad_attvals as $matchtag => $matchattrs )
                {
                    if ( 0 !== preg_match( $matchtag, $tagname ) )
                    {
                        foreach ( $matchattrs AS $matchattr => $valary )
                        {
                            if ( 0 !== preg_match( $matchattr, $attname ) )
                            {
                                /**
                                 * There are two arrays in valary.
                                 * First is matches.
                                 * Second one is replacements
                                */

                                list( $valmatch, $valrepl ) = $valary;

                                $newvalue = preg_replace( $valmatch, $valrepl, $attvalue );

                                if ( $newvalue != $attvalue )
                                {
                                    //$this -> wsDump( "$me: attvalue is now <b>$newvalue</b>\n" );

                                    $attary[ $attname ] = $newvalue;
                                }
                            }
                        }
                    }
                }
            }

        } // End While/Do

        /**
         * See if we need to append any attributes to this tag.
         */

        if ( $this -> add_attrib
             && $tagtype != 2
             && (bool)FALSE !== $tagname
             && isset( $this -> add_attr_to_tag[ $tagname ] )
           )
        {
            foreach ( $this -> add_attr_to_tag AS $matchtag => $addattary )
            {
                $matchtag = strtolower( $matchtag );
                $chk      = (bool)FALSE;

                $chkary = ( isset( $addattary['addif'] ) && is_array( $addattary['addif'] ) )
                        ? ( array_shift( $addattary ) )
                        : ( NULL );

               /* if ( is_array( $chkary ) && ! empty( $chkary ) )
                {
                    $chkname = array_keys( $chkary );
                }*/

                if ( ! empty( $chkary ) )
                {
                    foreach( $chkary AS $attr => $val )
                    {
                        if ( is_array( $val ) && isset( $attary[ $attr ] ) && in_array( $attary[ $attr ], $val ) )
                        {
                            $chk = (bool)TRUE;
                        }
                        else if ( is_string( $val ) && isset( $attary[ $attr ] ) && $attary[ $attr ] == $val )
                        {
                            $chk = (bool)TRUE;
                        }

                        if ( $chk ) { break; }
                    }
                }

                if ( $tagname == $matchtag )
                {
                    if ( ! empty( $attary ) )
                    {
                        foreach( $attary AS $name => $val )
                        {
                            foreach( $addattary AS $addname => $addval )
                            {
                                if ( ! isset( $attary[ $addname ] ) && $chk )
                                {
                                    $attary[ $addname ] = $addval;
                                }
                            }
                        }
                    }
                    else
                    {
                        $attary = array_merge( $attary, $addattary );
                    }

                    //$this -> wsDump( "$me: Added attributes to this tag\n" );
                }
            }
        }

        return $attary;

    } // End Method fixatts

/**
 * This function returns the final tag out of the tag name, an array
 * of attributes, and the type of the tag. This function is called by
 * sanitize internally.
 *
 * @param  $tagname     The name of the tag.
 * @param  $attary      the array of attributes and their values
 * @param  $tagtype     The type of the tag (see in comments).
 * @param  $delim       Alternate Set Of Delimiters To Use
 * @param  $tagtrack    If Adding To Tracking Array, Just Return Delimters & Tagname
 *
 * @return              A string with the final tag representation.
 */

function tagprint( $tagname, $attary, $tagtype, $delim = FALSE )
    {
        $open_tagname = $close_tagname = $tagname;

        if ( (bool)FALSE === $delim )
        {
            $delim[0] = $this -> open_delim;
            $delim[1] = $this -> close_delim;
        }

        // Replace Tag Name If Required

        if ( $this -> rpl_tags )
        {
            if ( $tagtype != 2 && isset( $this -> rpltag_list['open'][ $tagname ] ) )
            {
                //$this -> wsDump( "<b>Method tagprint: Replacing Tag: {$tagname}</b>\n" );

                $open_tagname = $this -> rpltag_list['open'][ $tagname ];
            }

            if ( $tagtype == 2 && isset( $this -> rpltag_list['close'][ $tagname ] ) )
            {
                $close_tagname = $this -> rpltag_list['close'][ $tagname ];
            }
        }

        // Build The Tag

        if ( $tagtype == 2 )
        {
            $fulltag = $delim[0] . '/' . $close_tagname . $delim[1];
        }
        else
        {
            $fulltag = $delim[0] . $open_tagname;
            $_tagtrack = NULL;

            $chk_type = ( $this -> proc_type != 5 ) ? ( ! in_array( $tagname, $this -> rm_all_att ) ) : ( (bool)TRUE );

            // If Form Tracking is enabled, Track The Form Element Names

            if ( $this -> track_forms && ! empty( $attary ) && isset( $this -> form_tags[ $tagname ] ) )
            {
                //$this -> wsDump( "<b>Method tagprint: Tracking Form Variables</b>\n" );

                $_tagtrack = $this -> trackFormVariables( $tagname, $attary );

                if ( (bool)FALSE === $_tagtrack ) { unset( $attary ); }
            }

            if ( ! empty( $attary ) && is_array( $attary ) && $chk_type )
            {
                foreach( $attary AS $attname => $attvalue )
                {
                    if ( is_string( $attname ) )
                   {
                        $fulltag .= " $attname=$attvalue";
                    }
                    else
                    {
                        $fulltag .= " $attvalue";
                    }
                }
            }

            if ( $tagtype == 3 && $this -> proc_type != 5 )
            {
                $fulltag .= ' /';
            }

            $fulltag .= $delim[1];

            if ( $this -> track_forms && $tagname == 'form' && isset( $_tagtrack ) && is_string( $_tagtrack ) )
            {
                $fulltag .= "\n" . $delim[0] . $_tagtrack . $delim[1] . "\n";
            }
        }

        //$this -> wsDump( "Method tagprint: <b>" . htmlspecialchars( $fulltag ) . "</b>\n" );

        return $fulltag;

    } // End Method tagprint

/**
 * This Method Holds The Meat Of The Tag Processing
 * of attributes, and the type of the tag.
 * This function is used internally by the methods.
 *
 * @param  &$skip_content     The name of the tag.
 * @param  &$tagname          The name of the tag.
 * @param  &$tagtype          The type of the tag (see in comments).
 * @param  &$open_tags        Array Of Open Tags
 * @param  &$attary           The array of attributes and their values
 * @param  &$tagtrack         The Closing Tagname In The Event Force Tag Closing Is Required
 *
 * @return void ( All Arguments Passed By Reference )
 */

function processTag(   &$skip_content
                     , &$tagname
                     , &$tagtype
                     , &$open_tags
                     , &$attary
                     , &$tagtrack
                   )
    {
        $me = 'Method processTag:';

        //$this -> wsDump( "$me: tagname is <b>{$tagname}</b>\n");

        if ( $tagtype == 2 )
        {
            //$this -> wsDump( "$me: This is a closing tag\n" );

            if ( $skip_content === $tagname )
            {
                /**
                 * Got to the end of tag we needed to remove.
                 */

                //$this -> wsDump( "$me: Finished removing tag with content\n" );

                $tagname = (bool)FALSE;
                $skip_content = (bool)FALSE;
            }
            else
            {
                if ( (bool)FALSE === $skip_content )
                {
                    if ( isset( $tagtrack ) && isset( $open_tags[ $tagtrack ] ) && $open_tags[ $tagtrack ] > 0 )
                    {
                        //$this -> wsDump( "$me: popping <b>$tagname</b> from open_tags\n" );

                        $open_tags[ $tagtrack ] = $open_tags[ $tagtrack ] - 1;
                    }
                    else
                    {
                        //$this -> wsDump( "$me: <b>$tagname</b> was never opened. <b>!! REMOVING !!</b>\n" );

                        $tagname = (bool)FALSE;
                    }
                }
                else
                {
                    //$this -> wsDump( "$me: Skipping this tag\n" );
               }
            }
        }
        else
        {
            /**
             * rm_tags_with_content if required
             */

            if ( (bool)FALSE !== $skip_content )
            {
                //$this -> wsDump( "$me: Skipping this tag\n" );
            }
            else
            {
                /**
                 * See if this is a self-closing type and change
                 * tagtype appropriately.
                 */

                if ( $tagtype == 1 && in_array( $tagname, $this -> self_closing_tags ) )
                {
                    //$this -> wsDump( "$me: Self-closing tag. Changing tagtype.\n" );
                    $tagtype = 3;
                }

                /**
                 * Check processing type and proceed accordingly
                 */

                if ( $this -> proc_type == 5 )
                {
                    //$this -> wsDump( "$me: Only Removing Disallowed Tags.\n" );

                    $this -> checkTagName( $tagname );
                }
                else
                {
                    /**
                     * See if we should skip this tag and any content
                     * inside it.
                     */

                    if ( $this -> proc_type != 3 && $tagtype == 1 && in_array( $tagname, $this -> rm_tags_with_content ) )
                    {
                        //$this -> wsDump( "$me: removing this tag with content\n" );

                        $skip_content = $tagname;
                    }
                    else
                    {
                        $this -> checkTagName( $tagname );
                    }

                    /**
                     * This is where we run other checks.
                     */

                    if ( $this -> proc_type != 4 && is_array( $attary ) )
                    {
                        $attary = $this -> fixatts( $tagname, $attary, $tagtype );
                    }
                }
            }
        }

        if ( (bool)FALSE !== $tagname && (bool)FALSE === $skip_content )
        {
            if ( $tagtype == 1 )
            {
                // Increment Open Tag Array If Required

                //$this -> wsDump( "$me: adding <b>" . htmlspecialchars( $tagname ) . "</b> to open_tags\n" );

                if ( isset( $open_tags[ $tagtrack ] ) )
                {
                    $open_tags[ $tagtrack ] = $open_tags[ $tagtrack ] + 1;
                }
                else
                {
                    $open_tags[ $tagtrack ] = 1;
                }
            }
        }

        return;

    } // End Method

} // End Class

?>