<?php
/**
 * YANA library
 *
 * @package yana
 * @licence http://creativecommons.org/licenses/by-nc-sa/2.5/
 */

if (!defined('ENT_FULL')) {
    /**
     * Constant ENT_FULL
     *
     * This adds a new constant ENT_FULL to the php predefined constants
     * ENT_COMPAT=2, ENT_QUOTES=3, ENT_NOQUOTES=0
     *
     * Use of this constant with the member method encode, as in this example:
     * <code>$string->encode('entities', ENT_FULL)</code>, will force ALL
     * characters of the string to be encoded as html entities.
     *
     * {@internal {
     *
     * Note:
     * There is no '1' for a good reason. In PHP as in some other
     * languages int(1) is fairly equivalent to bool(true), while
     * string(""), int(0) are equivalent to bool(false).
     * So you might encounter unexpected results when using int(1)
     * in other than an int-context. Not using int(1) is a way
     * (or propably a soft kind of "hack") to evade this.
     *
     * Note:
     * Just in case there may be any additional constants in future
     * there is a good chance they will continue as 4, 5, ...
     * So setting proprietary value ENT_FULL to 10 should'nt collide
     * for now and the near future ;-)
     *
     * }}
     *
     * @var int ENT_FULL = 10
     */
    define('ENT_FULL', 10);
}

/**
 * «datatype» String_type
 *
 * This is a OO-wrapper for the scalar string type of
 * PHP. This implementation is compatible with PHP 4 and 5.
 *
 * Note: this class is still under developement.
 * Use as see fit but be aware of possible changes in the
 * future.
 *
 * {@internal Note: Unlike Java, in PHP you got no automatic
 * boxing/unboxing since there are no native OO-wrappers
 * for scalar types and array. I would appreciate to see
 * this high on the agenda for upcomming releases of PHP.
 * Also there is no way to overload a non-static function
 * with a static function of the same name.
 * All this is making live a lot more complicated.
 * But for now we will have to live on it - and do all
 * boxing/unboxing by hand.
 *
 * Note: sadly had to deactivate preg_replace parameter $count
 * again, for there is no way to get it working in PHP 4.
 * Instead the functions will return the $count value.
 * This is kind of unexpected behaviour but it's the best way
 * there is right now without breaking backwards compatibility.
 * }}
 *
 * @access      public
 * @package     yana
 * @subpackage  utilities
 * @name        string_type
 */
class String_type
{

    /**
     * @access private
     * @var string
     * @ignore
     */
    var $value = "";

    /**
     * create new instance
     *
     * Creates a new string wrapper.
     * The input must be convertable to a string
     * value.
     *
     * @param string $stringValue
     *
     * @name  string_type::__construct()
     */
    function String_type($value)
    {
        $value = String_type::getString($value);
        $this->value = $value;
    }

    /**
     * get string value
     *
     * Unboxing the object. This function
     * returns the scalar string value of the object.
     *
     * @access public
     * @return string
     *
     * @name   string_type::get()
     */
    function get()
    {
        settype($this->value, "string");
        return $this->value;
    }

    /**
     * set string value
     *
     * Assigns a new value to the object.
     * Returns the old value.
     *
     * @access public
     * @param  string $new_value
     * @return string
     *
     * @name   string_type::set()
     */
    function set($value)
    {
        $value = String_type::getString($value);
        $prev_value = $this->value;
        $this->value = $value;
        return $prev_value;
    }

    /**
     * Alias of: String_type::get()
     *
     * @see string_type::get()
     */
    function toString()
    {
        return $this->get();
    }

    /**
     * return value as int
     *
     * Converts the string value to an integer and
     * returns it. Returns bool(false) if the string
     * is not numeric.
     *
     * @access public
     * @return int|bool(false)
     *
     * @name   string_type::toint()
     */
    function toInt() {
        if (is_numeric($this->value)) {
            return intval($this->get());
        } else {
            return false;
        }
    }

    /**
     * return value as float
     *
     * Converts the string value to a float and
     * returns it. Returns bool(false) if the string
     * is not numeric.
     *
     * @access public
     * @return float|bool(false)
     *
     * @name   string_type::tofloat()
     */
    function toFloat()
    {
        if (is_numeric($this->value)) {
            return floattval($this->get());
        } else {
            return false;
        }
    }

    /**
     * return value as boolean
     *
     * Returns a boolean value depending on the value of the string.
     * <ul>
     *     <li>    string("false") returns bool(false)    </li>
     *     <li>    string("true")  returns bool(true)     </li>
     *     <li>    any other value returns a boolean value
     *             depending on the result of PHP's
     *             internal conversion mechanism,
     *             BUT also issues an E_USER_NOTICE for on
     *             an invalid string to bool conversion   </li>
     * </ul>
     *
     * Note: 
     * If you just want to check wether a string is empty
     * or not, use $string->equals("") instead.
     *
     * @access public
     * @return bool
     *
     * @name   string_type::tobool()
     */
    function toBool()
    {
        if ($this->compareIgnoreCase('false') === 0) {
            return false;
        } else if ($this->compareIgnoreCase('true') === 0) {
            return true;
        } else {
            trigger_error('Invalid string to bool conversion. If you want to check wether a string is empty or not, use $string->equals("") instead.', E_USER_NOTICE);
            $b = $this->value;
            return (bool) $b;
        }
    }

    /**
     * OO-Alias of: addslashes(), addcslashes()
     *
     * @access public
     * @param  string $charlist (optional)
     * @return string
     *
     * @name   string_type::addslashes()
     */
    function &addSlashes($charlist = "")
    {
        $charlist = String_type::getString($charlist);
        if (!empty($charlist)) {
            $this->value = addcslashes($value,$charlist);
        } else {
            $this->value = addcslashes($value);
        }
        return $this->value;
    }

    /**
     * OO-Alias of: stripslashes(), stripcslashes()
     *
     * @access public
     * @param  string $charlist (optional)
     * @return string
     *
     * @name   string_type::removeslashes()
     */
    function &removeSlashes()
    {
        $this->value = stripslashes($value);
        return $this->value;
    }

    /**
     * OO-Alias of: $string[$index]
     *
     * Returns bool(false) on error.
     * Issues an E_USER_ERROR if $index is of wrong type.
     * Issues an E_USER_NOTICE if $index is out of bounds.
     *
     * Note that indices are numbered starting with '0'.
     *
     * @access public
     * @param  int $index (optional)
     *
     * @name   string_type::charat()
     */
    function charAt($index)
    {
        /* If it is an object and has a "toInt()" method, try to unbox the object. */
        if (is_object($index) && method_exists($index, 'toint')) {
            $index = $index->toInt();
        }
        /* If unboxing fails, or the input is no integer at all, issue an E_USER_ERROR and abort. */
        if (!is_int($index)) {
            trigger_error("Wrong argument type. Type 'int' expected, found '".gettype($index)."' instead.", E_USER_ERROR);
            return false;
        }
        /* check if $index is in bounds */
        if ($this->_check_bounds($index) === false) {
            return false;
        } else {
            /* all fine, proceed */
            return $this->value[$index];
        }
    }

    /**
     * OO-Alias of: trim(), chop()
     *
     * @access public
     * @return string
     *
     * @name   string_type::trim()
     */
    function &trim()
    {
        $this->value = trim($value);
        return $this->value;
    }

    /**
     * encrypt or transform a string (not revertable)
     *
     * Returns an encrypted version depending on
     * the type of encryption you choose.
     *
     * The input value is not case-sensitive.
     *
     * Note: The result of this function is alwas
     * irreversible. If you are looking for
     * reversible encryption methods see the "encode"
     * function.
     *
     * The following values are available.
     * <ul>
     *     <li>    crc32:    computes the crc32 checksum value of the string    </li>
     *     <li>    md5:      computes the md5 hash-string                       </li>
     *     <li>    sha1:     computes the sha1 hash-string                      </li>
     *     <li>    crypt:    uses crypt() function, result depending on $salt.
     *                       See PHP-Manual for details.                        </li>
     *     <li>    des:      uses DES encryption algorithm                      </li>
     *     <li>    blowfish: uses BLOWFISH encryption algorithm                 </li>
     *     <li>    soundex:  calculates the soundex hash: While this is not an
     *                       encryption algorithm, it is listed here, because it
     *                       is uses some sort of irreversible hashing and thus
     *                       won't fit to encoding()                            </li>
     *     <li>    metaphone:calculates the metaphone hash: While this is not an
     *                       encryption algorithm, it is listed here, for the
     *                       same reason as "soundex". Note: uses the argument
     *                       $salt as second argument for metaphone() if $salt
     *                       is a numeric value, that can be converted to int.  </li>
     * </ul>
     *
     * {@internal { Note:
     * Add other types of encryption as you see fit.
     * Remember to update not only the source, but
     * also the documentation on this function. }}
     *
     * @access public
     * @param  string $encryption (optional) see the list of valid inputs for details
     * @param  string $salt       (optional) only used for certain encryption types
     * @return string
     *
     * @name   string_type::encrypt()
     * @see    string_type::encode()
     */
    function &encrypt($encryption = "md5", $salt = "")
    {
        settype($this->value, "string");

        $encryption = new String_type($encryption);
        $encryption->toLowerCase();

        $salt = new String_type($salt);
        switch ($encryption->toString()) {
            case 'crc32':
                $this->value = crc32($this->value);
            break;
            case 'md5':
                if ($salt->length() > 8 && CRYPT_MD5 == 1) {
                    $this->value = crypt($this->value, '$1$'.$salt->substring(0,9));
                } else {
                    $this->value = md5($this->value);
                }
            break;
            case 'sha1':
                $this->value = sha1($this->value);
            break;
            case 'crypt':
                if ($salt->length() > 0) {
                    $this->value = crypt($this->value, $salt->toString());
                } else {
                    $this->value = crypt($this->value);
                }
            break;
            case 'des':
                if ($salt->length() == 0) {
                    $this->value = NULL;
                } else {
                    if (CRYPT_EXT_DES == 1 && $salt->length() > 8) {
                        $this->value = crypt($this->value, $salt->substring(0,9));
                    } else if (CRYPT_STD_DES == 1 && $salt->length() > 1) {
                        $this->value = crypt($this->value, $salt->substring(0,2));
                    } else {
                        $this->value = NULL;
                    }
                }
            break;
            case 'blowfish':
                if (CRYPT_BLOWFISH != 1 || $salt->length() < 12) {
                    $this->value = NULL;
                } else {
                    $this->value = crypt($this->value, '$2a$'.$salt->substring(0,12));
                }
            break;
            case 'soundex':
                $this->value = soundex($this->value);
            break;
            case 'metaphone':
                if ($salt->length() > 0 && is_numeric($salt->get())) {
                    $phones = $salt->get();
                    settype($phones, "int");
                    $this->value = metaphone($this->value, $phones);
                } else {
                    $this->value = metaphone($this->value);
                }
            break;
        }
        assert('is_scalar($this->value);');
        settype($this->value, 'string');
        return $this->value;
    }

    /**
     * encode a string (revertable)
     *
     * Note: charset applies only to encoding = "entities"
     *
     * Returns an encoded version depending on
     * the type of encoding you choose.
     *
     * The input value is not case-sensitive.
     *
     * Note: The results of this function can be reversed using
     * the "decode()" function with the same values.
     * If you are looking for checksums and hashing-methods
     * see the "encrypt" function.
     *
     * The following values are available.
     * <ul>
     *     <li>    unicode:  uses utf8_encode(), aliases: "utf", "utf8"         </li>
     *     <li>    base64:   uses base64_encode()                               </li>
     *     <li>    url:      uses urlencode()                                   </li>
     *     <li>    rawurl:   uses rawurlencode()                                </li>
     *     <li>    entities: uses htmlentities(), uses $style argument          </li>
     *     <li>    rot13:    does a ROT13 transformation                        </li>
     *     <li>    quote:    quotes meta signs using quotemeta()                </li>
     *     <li>    regexp:   uses preg_quote(), alias: "regular expression"     </li>
     * </ul>
     *
     * {@internal {
     *
     * Note:
     * Add other types of encodings as you see fit.
     * Remember to add a corresponding decode method
     * in function "decode()" as well.
     * Remember to update not only the source, but
     * also the documentation on this function.
     *
     * }}
     *
     * @access public
     * @param  string $encoding  see the list of valid inputs for details
     * @param  string $style    (optional)
     * @param  string $charset  (optional)
     * @return string
     *
     * @name   string_type::encode()
     * @see    string_type::encrypt()
     * @see    string_type::decode()
     */
    function encode($encoding, $style = ENT_COMPAT, $charset = "")
    {
        settype($this->value, "string");

        $encoding = new String_type($encoding);
        $encoding->toLowerCase();

        settype($style, 'int');
        $charset  = new String_type($charset);

        switch ($encoding->toString()) {
            case 'unicode': case 'utf': case 'utf8':
                $this->value = utf8_encode($this->value);
            break;
            case 'base64':
                $this->value = base64_encode($this->value);
            break;
            case 'url':
                $this->value = urlencode($this->value);
            break;
            case 'rawurl':
                $this->value = rawurlencode($this->value);
            break;
            case 'rot13':
                $this->value = str_rot13($this->value);
            break;
            case 'entities':
             if (($style==ENT_COMPAT || $style==ENT_QUOTES || $style==ENT_NOQUOTES)) {
                 if (!empty($charset)) {
                     $this->value = htmlentities($this->value, $style, $charset);
                 } else {
                     $this->value = htmlentities($this->value, $style);
                 }
             } else if ($style==ENT_FULL) {
                 $temp = "";
                 for ($i=0;$i<strlen($this->value);$i++)
                 {
                     $temp .= "&#".ord($this->value[$i]).";";
                 }
                 $this->value = $temp;
                 unset($temp);
             } else {
                 $this->value = htmlentities($this->value);
             }
            break;
            case 'quote':
                $this->value = quotemeta($this->value);
            break;
            case 'regexp': case 'regular expression':
                $this->value = preg_quote($this->value);
            break;
            default:
                trigger_error("The value of the \$encoding parameter (argument 1) is invalid: '".$encoding->toString()."'.",E_USER_WARNING);
            break;
        }
        assert('is_scalar($this->value);');
        settype($this->value, 'string');
        return $this->value;
    }

    /**
     * decode a string (revertable)
     *
     * Note: charset applies only to encoding = "entities"
     *
     * This function is the opposite of "encode()".
     * See "encode()" for details on the available types of
     * encoding.
     *
     * @access public
     * @param  string $encoding
     * @param  int    $style   (optional)
     * @param  string $charset (optional)
     * @return string
     *
     * @name string_type::decode()
     * @see  string_type::encrypt()
     * @see  string_type::encode()
     */
    function &decode($encoding, $style = ENT_COMPAT, $charset = "")
    {
        settype($this->value, "string");

        $encoding = new String_type($encoding);
        $encoding->toLowerCase();

        settype($style, 'int');
        $charset  = new String_type($charset);

        switch ($encoding->toString()) {
            case 'unicode': case 'utf': case 'utf8':
                $this->value = utf8_decode($this->value);
            break;
            case 'base64':
                $this->value = base64_decode($this->value);
            break;
            case 'url':
                $this->value = urldecode($this->value);
            break;
            case 'rawurl':
                $this->value = rawurldecode($this->value);
            break;
            case 'rot13':
                $this->value = str_rot13($this->value);
            break;
            case 'entities':
             if (($style==ENT_COMPAT || $style==ENT_QUOTES || $style==ENT_NOQUOTES)) {
                 if ($charset->toString() != "") {
                     $this->value = html_entity_decode($this->value, $style, $charset->toString());
                 } else {
                     $this->value = html_entity_decode($this->value, $style);
                 }
             } else {
                 $this->value = html_entity_decode($this->value);
             }
            break;
            case 'rot13':
                $this->value = str_rot13($this->value);
            break;
            default:
                trigger_error("The value of the \$encoding parameter (argument 1) is invalid: '".$encoding->toString()."'.",E_USER_WARNING);
            break;
        }
        assert('is_scalar($this->value);');
        settype($this->value, 'string');
        return $this->value;
    }

    /**
     * return a lower-cased version of the string
     *
     * @access public
     * @return string
     *
     * @name   string_type::tolowercase()
     * @see    string_type::touppercase()
     */
    function &toLowerCase()
    {
        $this->value = strtolower($this->value);
        return $this->value;
    }

    /**
     * return a upper-cased version of the string
     *
     * @access public
     * @return string
     *
     * @name   string_type::touppercase()
     * @see    string_type::tolowercase()
     */
    function &toUpperCase()
    {
        $this->value = strtoupper($this->value);
        return $this->value;
    }

    /**
     * extract a substring
     *
     * Returns a substring beginning at character-offset $start with
     * $length characters.
     * See PHP-Manual "string functions" "substr()" for details.
     *
     * @access public
     * @param  int $start
     * @param  int $length (optional)
     * @return string
     *
     * @name   string_type::substring()
     */
    function &substring($start, $length = 0)
    {
        settype($start,  "int");
        settype($length, "int");
        assert('abs($length) <= $this->length();');

        if ($length != 0) {
            return substr($this->value, $start, $length);
        } else {
            return substr($this->value, $start);
        }
        return $this->value;
    }

    /**
     * test two strings for equality
     *
     * Returns
     * <ul>
     *     <li>    bool(true)  if and only if this string === $something    </li>
     *     <li>    bool(false) on every other case                          </li>
     * </ul>
     *
     * Note: this is unlike the PHP code ($string == $something) where you
     * might accidently run into comparision in e.g. a boolean context.
     *
     * @access public
     * @param  mixed $something
     * @return boolean
     *
     * @name   string_type::equals()
     */
    function equals($something)
    {
        return $this->value === $something;
    }

    /**
     * compare two strings
     *
     * Returns
     * <ul>
     *     <li>    int(-1)  if this string < $anotherString      </li>
     *     <li>    int(+0)  if this string === $anotherString    </li>
     *     <li>    int(+1)  if this string > $anotherString      </li>
     * </ul>
     *
     * Note: This function is case-sensitive.
     *
     * @access public
     * @param  string $anotherString
     * @return int(+1)|int(0)|int(-1)
     *
     * @name   string_type::compareto()
     * @see    string_type::comparetoignorecase()
     */
    function compareTo($anotherString)
    {
        $anotherString = String_type::getString($anotherString);
        return strcmp($this->value, $something);
    }

    /**
     * compare two strings (ignore case)
     *
     * Returns
     * <ul>
     *     <li>    int(-1)  if this string < $anotherString      </li>
     *     <li>    int(+0)  if this string === $anotherString    </li>
     *     <li>    int(+1)  if this string > $anotherString      </li>
     * </ul>
     *
     * Note: This function is NOT case-sensitive.
     *
     * @access public
     * @param  string $anotherString
     * @return int(+1)|int(0)|int(-1)
     *
     * @name   string_type::comparetoignorecase()
     * @see    string_type::compareto()
     */
    function compareToIgnoreCase($anotherString)
    {
        $anotherString = String_type::getString($anotherString);
        return strcasecmp($this->value, $something);
    }

    /**
     * match string against regular expression
     *
     * Matches this string against a given Perl-compatible regular expression.
     * Returns an array containing the FIRST set of matches or bool(false) if
     * the regular expression did not match at all.
     *
     * @access public
     * @param  string $regularExpression
     * @return array|bool(false)
     *
     * @name   string_type::match()
     * @see    string_type::matchall()
     */
    function match($regularExpression)
    {
        $regularExpression = String_type::getString($regularExpression);
        $numberOfHits = preg_match($regularExpression, $this->value, $matches);
        if (is_int($numberOfHits) && $numberOfHits > 0) {
            assert('is_array($matches);');
            return $matches;
        } else {
            return false;
        }
    }

    /**
     * match string against regular expression (return all results)
     *
     * Matches this string against a given Perl-compatible regular expression.
     * Returns an array containing ALL the matches or bool(false) if
     * the regular expression did not match at all.
     *
     * @access public
     * @param  string $regularExpression
     * @return array|bool(false)
     *
     * @name   string_type::matchall()
     * @see    string_type::match()
     */
    function matchAll($regularExpression)
    {
        $regularExpression = String_type::getString($regularExpression);
        $numberOfHits = preg_match_all($regularExpression, $this->value, $matches);
        if (is_int($numberOfHits) && $numberOfHits > 0) {
            assert('is_array($matches);');
            return $matches;
        } else {
            return false;
        }
    }

    /**
     * replace a needle with a substitute
     *
     * @access public
     * @param  string $needle
     * @param  string $substitute (optional)
     * @return int    number of times $needle is replaced
     *
     * @name   string_type::replace()
     * @see    string_type::replaceregexp()
     */
    function replace($needle, $substitute = ""/* no way to make this work for PHP 4 :-( , &$count = null */)
    {   
        $count = 0;
        $needle = String_type::getString($needle);
        $substitute = String_type::getString($substitute);

        if (/* func_num_args() > 2 */ true) {
            if (version_compare("5.0.0", PHP_VERSION) === 1) {
                /* This simulates the behaviour of the $count parameter
                 * that was introduced in PHP 5.0.0 for PHP 4.
                 */
                $count = substr_count($needle, $this->value);
                assert('is_int($count);');
                $this->value = str_replace($needle, $substitute, $this->value);
            } else {
                /* As of PHP version 5.0.0 and beyond the $count
                 * parameter may be used.
                 */
                $this->value = str_replace($needle, $substitute, $this->value, $count);
                assert('is_int($count);');
            }
        } else {
            $this->value = str_replace($needle, $substitute, $this->value);
        }

        return $count;
    }

    /**
     * replace a substring by using a regular expression
     *
     * @access public
     * @param  string $needle
     * @param  string $substitute (optional)
     * @param  int    $limit      (optional) must be a positive integer > 0, defaults to -1 (no limit)
     * @return int    number of times $needle is replaced
     * 
     * @name   string_type::replaceregexp()
     * @see    string_type::replace()
     */ 
    function replaceRegExp($regularExpression, $substitute = "", $limit = -1/* no way to make this work for PHP 4 :-( , &$count = null */)
    {
        $count = 0;
        $regularExpression = String_type::getString($regularExpression);
        $substitute = String_type::getString($substitute);
        settype($limit, "int");

        /**
         * Limit must be a positive integer > 0.
         * All other values default to -1 (= no limit).
         */
        if ($limit < 1) {
            $limit = -1;
        }

        if (/* func_num_args() > 3 */ true) {
            /* initialize count variable */
            $count = 0;
            if (version_compare("5.1.0", PHP_VERSION) === 1) {
                /* This simulates the behaviour of the $count parameter
                 * that was introduced in PHP 5.1.0 for PHP prior to this version.
                 */
                assert('!isset($dummy);');
                $count = preg_match_all($regularExpression, $this->value, $dummy);
                /* Since preg_match_all returns bool(false) on error it is wise to ensure
                 * the result has the expected data-type.
                 */
                if ($count === false) {
                    $count = 0;
                }
                if ($limit > 0 && $count > $limit) {
                    $count = $limit;
                }
                unset($dummy);
                assert('is_int($count); /* preg_match_all() returned an unexpected result */');
                $this->value = preg_replace($regularExpression, $substitute, $this->value, $limit);
                assert('is_string($this->value); /* preg_replace() returned an unexpected result */');
            } else {
                /* As of PHP version 5.1.0 and beyond the $count
                 * parameter may be used.
                 */
                $this->value = preg_replace($regularExpression, $substitute, $this->value, $limit, $count);
                assert('is_string($this->value); /* preg_replace() returned an unexpected result */');
                assert('is_int($count); /* preg_replace() did not return an integer for parameter $count */');
            }
        } else {
            $this->value = preg_replace($regularExpression, $substitute, $this->value, $limit);
        }

        return $count;
    }

    /**
     * get the length of the string
     *
     * @access public
     * @return int
     * 
     * @name   string_type::length()
     */
    function length() 
    {
        return strlen($this->value);
    }

    /**
     * convert string to an array
     *
     * @access public
     * @param  string $separator
     * @param  int    $limit (optional)
     * @return array
     * 
     * @name   string_type::split()
     * @see    string_type::splitregexp()
     */
    function split($separator, $limit = 0)
    {
        settype($limit, "int");
        $separator = String_type::getString($separator);

        if ($limit > 0) {
            return explode($separator, $this->value, $limit);
        } else {
            return explode($separator, $this->value);
        }
    }

    /**
     * convert string to an array by using regular expression to find a speratator
     *
     * @access public
     * @param  string $separator
     * @param  int    $limit (optional)
     * @return array
     * 
     * @name   string_type::splitregexp()
     * @see    string_type::split()
     */
    function splitRegExp($separator, $limit = 0)
    {
        settype($limit, "int");
        $separator = String_type::getString($separator);

        if ($limit > 0) {
            return preg_split($separator, $this->value, $limit);
        } else {
            return preg_split($separator, $this->value);
        }
    }

    /**
     * get position of first occurence of a needle inside the string
     *
     * Returns character-offset of first occurence of $needle within this string.
     * Indices starting with int(0). 
     *
     * Returns Java-style int(-1) if $needle is not found, NOT Php-style bool(false).
     * This is because int(0) and bool(false) might get mixed by accident.
     *
     * So while if <code>(strpos($string, $needle) == 0))</code> will return true,
     * even if $needle is not found, the test <code>if ($string->indexOf($needle) == 0)</code>
     * will return false if $needle is not found and true if and only if $string
     * starts with the string $needle.
     *
     * @access public
     * @param  string $needle
     * @param  int    $offset (optional)
     * @return int
     * 
     * @name   string_type::indexof()
     */
    function indexOf($needle, $offset = 0)
    {
        settype($offset, "int");
        $needle = String_type::getString($needle);

        if ($offset > 0) {
            $result = strpos($this->value, $needle, $offset);
        } else {
            $result = strpos($this->value, $needle);
        }
        if ($result === false) {
            return -1;
        } else {
            return $result;
        }
    }

    /**
     * wrap a long text
     *
     * Note: THIS FUNCTION IS NOT BINARY-SAFE!
     *
     * @access public
     * @param  int    $width (optional)
     * @param  string $break (optional)
     * @param  bool   $cut   (optional)
     * @return string
     *
     * @name   string_type::wrap()
     */
    function &wrap($width = 0, $break = "", $cut = false)
    {
        settype($width, "int");
        $break = String_type::getString($break);
        settype($cut, "boolean");

        if ($cut === true) {
            $this->value = wordwrap($this->value,$width,$break,$cut);
        } else if (!empty($break)) {
            $this->value = wordwrap($this->value,$width,$break);
        } else if ($width > 0) {
            $this->value = wordwrap($this->value,$width);
        } else {
            $this->value = wordwrap($this->value);
        }
        return $this->value;
    }

    /**
     * shuffle the string's characters
     *
     * @access public
     * @return string
     *
     * @name   string_type::shuffle()
     */
    function &shuffle()
    {
        $this->value = str_shuffle($this->value);
        return $this->value;
    }

    /**
     * reverse the string value
     *
     * @access public
     * @return string
     *
     * @name   string_type::reverse()
     */
    function &reverse()
    {
        $this->value = strrev($this->value);
        return $this->value;
    }
    
    /**
     * copy / clone the string
     *
     * creates a copy of this object
     *
     * @access public
     * @return String_type
     *
     * @name string_type::copy()
     */
    function copy()
    {
        return new String_type($this->value);
    }

    /**
     * getString
     *
     * @access protected
     * @static
     * @param  mixed $something
     * @return string
     *
     * @ignore
     */
    function getString($something)
    {

        if (is_object($something)) {
            if (method_exists($something, 'toString')) {
                $something = $something->toString();
            } else {
                $something = "";
            }
        } else if (is_array($something)) {
            $something = implode('', $something);
        }

        settype($something, "string");
        return $something;
    }

    /**
     * convert to html entities
     *
     * @access public
     * @static
     * @param  string $input
     * @return string
     *
     * @name   string_type::htmlentities()
     * @see    string_type::encode()
     */
    function htmlEntities($input)
    {
        assert('is_string($input);');
        $out = new String_type($input);
        $out->encode('entities', ENT_FULL);
        return $out->toString();
    }
    
    /**
     * _check_bounds
     *
     * string array bounds check
     *
     * @access private
     * @param  int $index
     * @return bool
     *
     * @ignore
     */
    function _check_bounds($index)
    {
        /* If the input is no integer at all, issue an E_USER_ERROR and abort. */
        if (!is_int($index)) {
            return false;
        } else if ($index < 0 || $index >= $this->length()) {
            trigger_error("String index '".$index."' out of bounds.", E_USER_NOTICE);
            return false;
        } else {
            return true;
        }
    }
}
?>