| Current Path : /home/juvelize/www/libraries/ic_library/Vendor/Icalcreator/src/Util/ |
| Current File : /home/juvelize/www/libraries/ic_library/Vendor/Icalcreator/src/Util/StringFactory.php |
<?php
/**
* iCalcreator, the PHP class package managing iCal (rfc2445/rfc5445) calendar information.
*
* copyright (c) 2007-2021 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
* Link https://kigkonsult.se
* Package iCalcreator
* Version 2.30.4
* License Subject matter of licence is the software iCalcreator.
* The above copyright, link, package and version notices,
* this licence notice and the invariant [rfc5545] PRODID result use
* as implemented and invoked in iCalcreator shall be included in
* all copies or substantial portions of the iCalcreator.
*
* iCalcreator is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* iCalcreator 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with iCalcreator. If not, see <https://www.gnu.org/licenses/>.
*
* This file is a part of iCalcreator.
*/
namespace Kigkonsult\Icalcreator\Util;
use Kigkonsult\Icalcreator\Vcalendar;
use UnexpectedValueException;
use function bin2hex;
use function count;
use function ctype_digit;
use function explode;
use function floor;
use function in_array;
use function openssl_random_pseudo_bytes;
use function ord;
use function rtrim;
use function sprintf;
use function str_ireplace;
use function str_replace;
use function strlen;
use function stripos;
use function strpos;
use function strrev;
use function strtolower;
use function strtoupper;
use function substr;
use function substr_count;
use function trim;
/**
* iCalcreator TEXT support class
*
* @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
* @since 2.30.4 - 2021-03-07
*/
class StringFactory
{
/**
* @var string
*/
public static $BS2 = '\\';
public static $QQ = '"';
/**
* Return concatenated calendar rows, one row for each property
*
* @param array $rows
* @return array
* @since 2.29.20 - 2020-01-31
*/
public static function concatRows( $rows )
{
static $CHARs = [ ' ', "\t" ];
$output = [];
$cnt = count( $rows );
for( $i = 0; $i < $cnt; $i++ ) {
$line = rtrim( $rows[$i], Util::$CRLF );
$i1 = $i + 1;
while(( $i < $cnt ) && isset( $rows[$i1] ) &&
! empty( $rows[$i1] ) &&
in_array( substr( $rows[$i1], 0, 1 ), $CHARs )) {
$i += 1;
$line .= rtrim( substr( $rows[$i], 1 ), Util::$CRLF );
$i1 = $i + 1;
} // end while
$output[] = $line;
} // end for
return $output;
}
/**
* @var string
*/
private static $BEGIN_VCALENDAR = 'BEGIN:VCALENDAR';
private static $END_VCALENDAR = 'END:VCALENDAR';
private static $NLCHARS = '\n';
/**
* Return rows to parse from string or array
*
* Used by Vcalendar & RegulateTimezoneFactory
* @param string|array $unParsedText strict rfc2445 formatted, single property string or array of strings
* @return array
* @throws UnexpectedValueException
* @since 2.29.3 - 2019-08-29
*/
public static function conformParseInput( $unParsedText = null )
{
static $ERR10 = 'Only %d rows in calendar content :%s';
$arrParse = false;
if( is_array( $unParsedText )) {
$rows = implode( self::$NLCHARS . Util::$CRLF, $unParsedText );
$arrParse = true;
}
else { // string
$rows = $unParsedText;
}
/* fix line folding */
$rows = self::convEolChar( $rows );
if( $arrParse ) {
foreach( $rows as $lix => $row ) {
$rows[$lix] = self::trimTrailNL( $row );
}
}
/* skip leading (empty/invalid) lines (and remove leading BOM chars etc) */
$rows = self::trimLeadingRows( $rows );
$cnt = count( $rows );
if( 3 > $cnt ) { /* err 10 */
throw new UnexpectedValueException(
sprintf( $ERR10, $cnt, PHP_EOL . implode( PHP_EOL, $rows ))
);
}
/* skip trailing empty lines and ensure an end row */
$rows = self::trimTrailingRows( $rows );
return $rows;
}
/**
* Return array to parse with leading (empty/invalid) lines removed (incl leading BOM chars etc)
*
* @param array $rows
* @return array
* @since 2.29.3 - 2019-08-29
*/
private static function trimLeadingRows( $rows )
{
foreach( $rows as $lix => $row ) {
if( false !== stripos( $row, self::$BEGIN_VCALENDAR )) {
$rows[$lix] = self::$BEGIN_VCALENDAR;
break;
}
unset( $rows[$lix] );
} // end foreach
return $rows;
}
/**
* Return array to parse with trailing empty lines removed and ensured an end row
*
* @param array $rows
* @return array
* @since 2.29.3 - 2019-08-29
*/
private static function trimTrailingRows( $rows )
{
$lix = array_keys( $rows );
$lix = end( $lix );
while( 3 < $lix ) {
$tst = trim( $rows[$lix] );
if(( self::$NLCHARS == $tst ) || empty( $tst )) {
unset( $rows[$lix] );
$lix--;
continue;
}
if( false === stripos( $rows[$lix], self::$END_VCALENDAR )) {
$rows[] = self::$END_VCALENDAR;
}
else {
$rows[$lix] = self::$END_VCALENDAR;
}
break;
} // end while
return $rows;
}
/**
* Return string with removed ical line folding
*
* Remove any line-endings that may include spaces or tabs
* and convert all line endings (iCal default '\r\n'),
* takes care of '\r\n', '\r' and '\n' and mixed '\r\n'+'\r', '\r\n'+'\n'
*
* @param string $text
* @return array
* @since 2.29.9 - 2019-03-30
*/
public static function convEolChar( & $text )
{
static $BASEDELIM = null;
static $BASEDELIMs = null;
static $EMPTYROW = null;
static $FMT = '%1$s%2$75s%1$s';
static $CRLFs = [ "\r\n", "\n\r", "\n", "\r" ];
static $CRLFexts = [ "\r\n ", "\r\n\t" ];
/* fix dummy line separator etc */
if( empty( $BASEDELIM )) {
$BASEDELIM = self::getRandChars( 16 );
$BASEDELIMs = $BASEDELIM . $BASEDELIM;
$EMPTYROW = sprintf( $FMT, $BASEDELIM, Util::$SP0 );
}
/* fix eol chars */
$text = str_replace( $CRLFs, $BASEDELIM, $text );
/* fix empty lines */
$text = str_replace( $BASEDELIMs, $EMPTYROW, $text );
/* fix line folding */
$text = str_replace( $BASEDELIM, Util::$CRLF, $text );
$text = str_replace( $CRLFexts, null, $text );
/* split in component/property lines */
return explode( Util::$CRLF, $text );
}
/**
* Return formatted output for calendar component property
*
* @param string $label property name
* @param string $attributes property attributes
* @param string $content property content
* @return string
* @since 2.22.20 - 2017-01-30
*/
public static function createElement(
$label,
$attributes = null,
$content = null
) {
$output = strtoupper( $label );
if( ! empty( $attributes )) {
$output .= trim( $attributes );
}
$output .= Util::$COLON . trim( $content );
return self::size75( $output );
}
/**
* Return property name and (params+)value from (string) row
*
* @param string $row
* @return array propName and the trailing part of the row
* @since 2.29.11 - 2019-08-26
*/
public static function getPropName( $row )
{
$sclnPos = strpos( $row, Util::$SEMIC );
$clnPos = strpos( $row, Util::$COLON );
switch( true ) {
case (( false === $sclnPos ) && ( false === $clnPos )) : // no params and no value
return [ $row, null ];
break;
case (( false !== $sclnPos ) && ( false === $clnPos )) : // param exist and NO value ??
$propName = self::before( Util::$SEMIC, $row );
break;
case (( false === $sclnPos ) && ( false !== $clnPos )) : // no params
$propName = self::before( Util::$COLON, $row );
break;
case ( $sclnPos < $clnPos ) : // param(s) with value ??
$propName = self::before( Util::$SEMIC, $row );
break;
default : // ie $sclnPos > $clnPos // no params
$propName = self::before( Util::$COLON, $row );
break;
} // end switch
return [ $propName, self::after( $propName, $row ) ];
}
/**
* Return a random (and unique) sequence of characters
*
* @param int $cnt
* @return string
* @since 2.27.3 - 2018-12-28
*/
public static function getRandChars( $cnt )
{
$cnt = (int) floor( $cnt / 2 );
$x = 0;
do {
$randChars = bin2hex( openssl_random_pseudo_bytes( $cnt, $cStrong ));
$x += 1;
} while(( 3 > $x ) && ( false == $cStrong ));
return $randChars;
}
/**
* Return bool true if name is X-prefixed
*
* @param string $name
* @return bool
* @since 2.29.5 - 2019-08-30
*/
public static function isXprefixed( $name )
{
static $X_ = 'X-';
return ( $X_ == strtoupper( substr( $name, 0, 2 ) ));
}
/**
* Return wrapped string with (byte oriented) line breaks at pos 75
*
* Lines of text SHOULD NOT be longer than 75 octets, excluding the line
* break. Long content lines SHOULD be split into a multiple line
* representations using a line "folding" technique. That is, a long
* line can be split between any two characters by inserting a CRLF
* immediately followed by a single linear white space character (i.e.,
* SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
* of CRLF followed immediately by a single linear white space character
* is ignored (i.e., removed) when processing the content type.
*
* Edited 2007-08-26 by Anders Litzell, anders@litzell.se to fix bug where
* the reserved expression "\n" in the arg $string could be broken up by the
* folding of lines, causing ambiguity in the return string.
*
* @param string $string
* @return string
* @link http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
* @since 2.29.29 - 2020-09-11
*/
public static function size75( $string )
{
static $LCN = 'n';
static $UCN = 'N';
static $SPBSLCN = ' \n';
static $SP1 = ' ';
$tmp = $string;
$len = strlen( $tmp );
$string = null;
$cCnt = $x = 0;
while( true ) {
$x1 = $x + 1;
if( $len < $x ) {
$string .= Util::$CRLF; // loop breakes here
break;
}
elseif(( 74 <= $cCnt ) &&
( self::$BS2 == substr( $tmp, $x, 1 ) ) && // '\\'
(( $LCN == substr( $tmp, $x1, 1 )) ||
( $UCN == substr( $tmp, $x1, 1 )))) {
$string .= Util::$CRLF . $SPBSLCN; // don't break lines inside '\n'
$x += 2;
if( $len < $x ) {
$string .= Util::$CRLF;
break; // or here...
}
$cCnt = 3;
}
elseif( 75 <= $cCnt ) {
// $string .= Util::$CRLF . $SP1;
$string .= Util::$CRLF;
if( $len == $x ) {
break; // or here..
}
$string .= $SP1;
$cCnt = 1;
}
$str1 = substr( $tmp, $x, 1 );
$byte = ord( $str1 );
$string .= $str1;
switch( true ) {
case(( $byte >= 0x20 ) && ( $byte <= 0x7F )) :
$cCnt += 1; // characters U-00000000 - U-0000007F (same as ASCII)
break; // add a one byte character
case(( $byte & 0xE0 ) == 0xC0 ) : // characters U-00000080 - U-000007FF, mask 110XXXXX
if( $len > ( $x + 1 )) {
$cCnt += 1;
$x += 1; // add second byte of a two bytes character
$string .= substr( $tmp, $x, 1 );
}
break;
case(( $byte & 0xF0 ) == 0xE0 ) : // characters U-00000800 - U-0000FFFF, mask 1110XXXX
if( $len > ( $x + 2 )) {
$cCnt += 1;
$x += 1;
$string .= substr( $tmp, $x1, 2 );
$x += 1; // add byte 2-3 of a three bytes character
}
break;
case(( $byte & 0xF8 ) == 0xF0 ) : // characters U-00010000 - U-001FFFFF, mask 11110XXX
if( $len > ( $x + 3 )) {
$cCnt += 1;
$x += 1;
$string .= substr( $tmp, $x1, 3 );
$x += 3; // add byte 2-4 of a four bytes character
}
break;
case(( $byte & 0xFC ) == 0xF8 ) : // characters U-00200000 - U-03FFFFFF, mask 111110XX
if( $len > ( $x + 4 )) {
$cCnt += 1;
$x += 1;
$string .= substr( $tmp, $x, 4 );
$x += 4; // add byte 2-5 of a five bytes character
}
break;
case(( $byte & 0xFE ) == 0xFC ) : // characters U-04000000 - U-7FFFFFFF, mask 1111110X
if( $len > ( $x + 5 )) {
$cCnt += 1;
$x += 1;
$string .= substr( $tmp, $x, 5 );
$x += 5; // add byte 2-6 of a six bytes character
}
break;
default: // add any other byte without counting up $cCnt
break;
} // end switch( true )
$x += 1; // next 'byte' to test
} // end while( true )
return $string;
}
/**
* Return property value and attributes
*
* Attributes are prefixed by ';', value by ':', BUT they may exists in attr/values (quoted?)
*
* @param string $line property content
* @param string $propName
* @return array [line, [*propAttr]]
* @todo fix 2-5 pos port number
* @since 2.30.3 - 2021-02-14
*/
public static function splitContent( $line, $propName = null )
{
static $CSS = '://';
static $EQ = '=';
static $URIprops = [ Vcalendar::SOURCE, Vcalendar::URL, Vcalendar::TZURL ];
$clnPos = strpos( $line, Util::$COLON );
if(( false === $clnPos )) {
return [ $line, [] ]; // no params
}
if( 0 == $clnPos ) { // no params, most frequent
return [ substr( $line, 1 ) , [] ];
}
if( in_array( strtoupper( $propName ), $URIprops )) {
self::checkFixUriValue( $line );
}
if( self::checkSingleParam( $line )) { // one param
$param = self::between( Util::$SEMIC, Util::$COLON, $line );
return [
self::after( Util::$COLON, $line ),
[
self::before( $EQ, $param ) =>
trim( self::after( $EQ, $param ), self::$QQ )
]
];
} // end if
/* more than one param here (or a tricky one...) */
$attr = [];
$attrix = -1;
$WithinQuotes = false;
$len = strlen( $line );
$cix = 0;
while( $cix < $len ) {
$str1 = substr( $line, $cix, 1 );
$cix1 = $cix + 1;
if( ! $WithinQuotes &&
( Util::$COLON == $str1 ) &&
( $CSS != substr( $line, $cix, 3 )) && // '://'
! self::colonIsPrefixedByProtocol( $line, $cix ) &&
! self::hasPortNUmber( substr( $line, $cix1, 7 ))) {
$line = substr( $line, $cix1 );
break;
}
if( self::$QQ == $str1 ) { // '"'
$WithinQuotes = ! $WithinQuotes;
}
if( Util::$SEMIC == $str1 ) { // ';'
$attrix += 1;
$attr[$attrix] = null; // initiate
}
else {
$attr[$attrix] .= $str1;
}
$cix += 1;
} // end while...
/* make attributes in array format */
$propAttr = [];
foreach( $attr as $attribute ) {
$attrSplit = explode( $EQ, $attribute, 2 );
if( 1 < count( $attrSplit )) {
$propAttr[$attrSplit[0]] = $attrSplit[1];
}
}
return [ $line, $propAttr ];
}
/**
* Return true if single param only (and no colons in param values)
*
* 2nd most frequent
*
* @param $line
* @return bool
* @since 2.30.3 - 2021-02-14
*/
private static function checkSingleParam( $line )
{
if( 0 !== strpos( $line, Util::$SEMIC )) {
return false;
}
return (( 1 == substr_count( $line, Util::$SEMIC )) &&
( 1 == substr_count( $line, Util::$COLON )));
}
/**
* Replace opt value prefix 'VALUE=URI:message://' by ':' also (opt un-urldecoded) '<'|'>'|'@'
* orginating from any Apple device
*
* @param $line
* @since 2.30.4 - 2021-03-07
*/
public static function checkFixUriValue( & $line )
{
static $VEQU = ';VALUE=URI';
static $MSGPFX1 = 'message:';
static $MSGPFX2 = 'message://';
static $PFCHARS1 = '%3C';
static $SFCHARS1 = '%3E';
static $PFCHARS2 = '<';
static $SFCHARS2 = '>';
static $SCHAR31 = '%40';
static $SCHAR32 = '@';
if( false !== stripos( $line, $VEQU )) {
$line = str_ireplace( $VEQU, Util::$SP0, $line );
}
if(( false !== stripos( $line, $MSGPFX1 )) && ( false === stripos( $line, $MSGPFX2 ))) {
$line = str_ireplace( $MSGPFX1, $MSGPFX2, $line );
}
if(( false !== strpos( $line, $PFCHARS1 )) && ( false !== strpos( $line, $SFCHARS1 ))) {
$line = str_replace( [ $PFCHARS1, $SFCHARS1 ], Util::$SP0, $line );
}
elseif(( false !== strpos( $line, $PFCHARS2 )) && ( false !== strpos( $line, $SFCHARS2 ))) {
$line = str_replace( [ $PFCHARS2, $SFCHARS2 ], Util::$SP0, $line );
}
if( false !== strpos( $line, $SCHAR31 )) {
$line = str_replace( $SCHAR31, $SCHAR32, $line );
}
}
/**
* Return bool true if colon-pos is prefixed by protocol
*
* @see https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml overkill !!
*
* @param string $line
* @param int $cix
* @return bool
* @accvess private
* @since 2.30.2 - 2021-02-04
*/
private static function colonIsPrefixedByProtocol( $line, $cix )
{
static $MSTZ = [ 'utc-', 'utc+', 'gmt-', 'gmt+' ];
static $PROTO3 = [ 'cid:', 'sms:', 'tel:', 'urn:' ]; // 'fax:' removed
static $PROTO4 = [ 'crid:', 'news:', 'pres:', 'http:' ];
static $PROTO5 = [ 'https:' ];
static $PROTO6 = [ 'mailto:', 'telnet:' ];
static $PROTO7 = [ 'message:' ];
$line = strtolower( $line );
return (( in_array( substr( $line, $cix - 6, 4 ), $MSTZ )) || // ?? -6
( in_array( substr( $line, $cix - 3, 4 ), $PROTO3 )) ||
( in_array( substr( $line, $cix - 4, 5 ), $PROTO4 )) ||
( in_array( substr( $line, $cix - 5, 6 ), $PROTO5 )) ||
( in_array( substr( $line, $cix - 6, 7 ), $PROTO6 )) ||
( in_array( substr( $line, $cix - 7, 8 ), $PROTO7 )));
}
/**
* Return bool true if leading chars in string is a port number (e.i. followed by '/')
*
* @param string $string
* @return bool
* @since 2.27.22 - 2020-09-01
*/
private static function hasPortNUmber( $string )
{
$len = strlen( $string );
for( $x = 0; $x < $len; $x++ ) {
$str1 = substr( $string, $x, 1 );
if( ! ctype_digit( $str1 )) {
break;
}
if( Util::$SLASH == $str1 ) {
return true;
}
} // end for
return false;
}
/**
* Fix rfc5545. 3.3.11 Text, ESCAPED-CHAR
*
* @param string $string
* @return string
* @since 2.27.14 - 2019-02-20
*/
public static function strrep( $string )
{
static $BSLCN = '\n';
static $SPECCHAR = [ 'n', 'N', 'r', ',', ';' ];
static $SQ = "'";
static $QBSLCR = "\r";
static $QBSLCN = "\n";
static $BSUCN = '\N';
$string = (string) $string;
$strLen = strlen( $string );
$pos = 0;
// replace single (solo-)backslash by double ones
while( $pos < $strLen ) {
if( false === ( $pos = strpos( $string, self::$BS2, $pos ))) {
break;
}
if( ! in_array( substr( $string, $pos, 1 ), $SPECCHAR )) {
$string = substr( $string, 0, $pos ) .
self::$BS2 . substr( $string, ( $pos + 1 ));
$pos += 1;
}
$pos += 1;
} // end while
// replace double quote by single ones
if( false !== strpos( $string, self::$QQ )) {
$string = str_replace( self::$QQ, $SQ, $string );
}
// replace comma by backslash+comma but skip any previously set of backslash+comma
// replace semicolon by backslash+semicolon but skip any previously set of backslash+semicolon
foreach( [ Util::$COMMA, Util::$SEMIC ] as $char ) {
$offset = 0;
while( false !== ( $pos = strpos( $string, $char, $offset ))) {
if(( 0 < $pos ) && ( self::$BS2 != substr( $string, ( $pos - 1 )))) {
$string = substr( $string, 0, $pos ) .
self::$BS2 . substr( $string, $pos );
}
$offset = $pos + 2;
} // end while
$string = str_replace(
self::$BS2 . self::$BS2 . $char,
self::$BS2 . $char,
$string
);
}
// replace "\r\n" by '\n'
if( false !== strpos( $string, Util::$CRLF )) {
$string = str_replace( Util::$CRLF, $BSLCN, $string );
}
// or replace "\r" by '\n'
elseif( false !== strpos( $string, $QBSLCR )) {
$string = str_replace( $QBSLCR, $BSLCN, $string );
}
// or replace '\N' by '\n'
elseif( false !== strpos( $string, $QBSLCN )) {
$string = str_replace( $QBSLCN, $BSLCN, $string );
}
// replace '\N' by '\n'
if( false !== strpos( $string, $BSUCN )) {
$string = str_replace( $BSUCN, $BSLCN, $string );
}
// replace "\r\n" by '\n'
$string = str_replace( Util::$CRLF, $BSLCN, $string );
return $string;
}
/**
* Special characters management input
*
* @param string $string
* @return string
* @since 2.22.2 - 2015-06-25
*/
public static function strunrep( $string )
{
static $BS4 = '\\\\';
static $BSCOMMA = '\,';
static $BSSEMIC = '\;';
$string = str_replace( $BS4, self::$BS2, $string );
$string = str_replace( $BSCOMMA, Util::$COMMA, $string );
$string = str_replace( $BSSEMIC, Util::$SEMIC, $string );
return $string;
}
/**
* Return string with trimmed trailing \n
*
* @param string $value
* @return string
* @since 2.29.14 - 2019-09-03
*/
public static function trimTrailNL( $value )
{
static $NL = '\n';
$value = (string) $value;
if( $NL == strtolower( substr( $value, -2 ))) {
$value = substr( $value, 0, ( strlen( $value ) - 2 ));
}
return $value;
}
/**
* @link https://php.net/manual/en/function.substr.php#112707
*/
/**
* @var string
*/
private static $SP0 = '';
/**
* Return bool true if needle is in haystack
*
* Case-sensitive search for needle in haystack
*
* @param $needle
* @param $haystack
* @return bool
*/
public static function isIn( $needle, $haystack )
{
return ( false !== ( $pos = strpos( $haystack, $needle )));
}
/**
* Return substring after first found needle in haystack, '' on not found
*
* Case-sensitive search for needle in haystack
*
* @link https://php.net/manual/en/function.substr.php#112707
* @param string $needle
* @param string $haystack
* @return string
*/
public static function after( $needle, $haystack )
{
if( ! self::isIn( $needle, $haystack )) {
return self::$SP0;
}
$pos = strpos( $haystack, $needle );
return substr( $haystack, $pos + strlen( $needle ));
}
/**
* Return substring after last found needle in haystack, '' on not found
*
* Case-sensitive search for needle in haystack
*
* @link https://php.net/manual/en/function.substr.php#112707
* @param string $needle
* @param string $haystack
* @return string
*/
public static function afterLast( $needle, $haystack )
{
if( ! self::isIn( $needle, $haystack )) {
return self::$SP0;
}
$pos = self::strrevpos( $haystack, $needle );
return substr( $haystack, $pos + strlen( $needle ));
}
/**
* Return substring before first found needle in haystack, '' on not found
*
* Case-sensitive search for needle in haystack
*
* @link https://php.net/manual/en/function.substr.php#112707
* @param string $needle
* @param string $haystack
* @return string
*/
public static function before( $needle, $haystack )
{
if( ! self::isIn( $needle, $haystack )) {
return self::$SP0;
}
return substr( $haystack, 0, strpos( $haystack, $needle ));
}
/**
* Return substring before last needle in haystack, '' on not found
*
* Case-sensitive search for needle in haystack
*
* @link https://php.net/manual/en/function.substr.php#112707
* @param string $needle
* @param string $haystack
* @return string
*/
public static function beforeLast( $needle, $haystack )
{
if( ! self::isIn( $needle, $haystack )) {
return self::$SP0;
}
return substr( $haystack, 0, self::strrevpos( $haystack, $needle ));
}
/**
* Return substring between needles in haystack
*
* Case-sensitive search for needles in haystack
* If no needles found in haystack, '' is returned
* If only needle1 found, substring after is returned
* If only needle2 found, substring before is returned
*
* @link https://php.net/manual/en/function.substr.php#112707
* @param string $needle1
* @param string $needle2
* @param string $haystack
* @return string
*/
public static function between( $needle1, $needle2, $haystack )
{
$exists1 = self::isIn( $needle1, $haystack );
$exists2 = self::isIn( $needle2, $haystack );
switch( true ) {
case ( ! $exists1 && ! $exists2 ) :
return self::$SP0;
break;
case ( $exists1 && ! $exists2 ) :
return self::after( $needle1, $haystack );
break;
case ( ! $exists1 && $exists2 ) :
return self::before( $needle2, $haystack );
break;
default :
return self::before( $needle2, self::after( $needle1, $haystack ));
break;
} // end switch
}
/**
* Return substring between last needles in haystack
*
* Case-sensitive search for needles in haystack
*
* @link https://php.net/manual/en/function.substr.php#112707
* @param string $needle1
* @param string $needle2
* @param string $haystack
* @return string
*/
public static function betweenLast( $needle1, $needle2, $haystack )
{
return self::afterLast( $needle1, self::beforeLast( $needle2, $haystack ));
}
/**
* Return int for length from start to last needle in haystack, false on not found
*
* Case-sensitive search for needle in haystack
*
* @link https://php.net/manual/en/function.substr.php#112707
* @param string $haystack
* @param string $needle
* @return int
*/
public static function strrevpos( $haystack, $needle )
{
return ( false !== ( $rev_pos = strpos( strrev( $haystack ), strrev( $needle ))))
? ( strlen( $haystack ) - $rev_pos - strlen( $needle ))
: false;
}
/**
* Return bool true if haystack starts with needle, false on not found or to large
*
* Case-sensitive search for needle in haystack
*
* @param string $haystack
* @param string $needle
* @param string $len if found contains length of needle
* @return bool
* @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
* @since 2.29.11 - 2019-08-28
*/
public static function startsWith( $haystack, $needle, & $len = null )
{
$len = null;
$needleLen = strlen( $needle );
if( $needleLen > strlen( $haystack )) {
return false;
}
if( 0 === strpos( $haystack, $needle )) {
$len = $needleLen;
return true;
}
return false;
}
/**
* Return bool true if haystack ends with needle, false on not found or to large
*
* Case-sensitive search for needle in haystack
*
* @param string $haystack
* @param string $needle
* @param string $len if found contains length of needle
* @return bool
* @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
* @since 2.29.23 - 2020-07-28
*/
public static function endsWith( $haystack, $needle, & $len = null )
{
$len = null;
$needleLen = strlen( $needle );
if( $needleLen > strlen( $haystack )) {
return false;
}
if( $needle == substr( $haystack, ( 0 - $needleLen ))) {
$len = $needleLen;
return true;
}
return false;
}
}