PluginProbe
Import Social Events / 1.8.5
Import Social Events v1.8.5
1.9.1 1.9.0 trunk 1.0.0 1.0.1 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.2.0 1.3.0 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.6.0 1.6.1 1.6.10 1.6.11 1.6.12 All 61 releases
import-facebook-events / includes / lib / icalcreator / src / Util / StringFactory.php

StringFactory.php in Import Social Events 1.8.5, at includes/lib/icalcreator/src/Util/StringFactory.php

928 lines 31.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * iCalcreator, the PHP class package managing iCal (rfc2445/rfc5445) calendar information.
4 *
5 * copyright (c) 2007-2021 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
6 * Link https://kigkonsult.se
7 * Package iCalcreator
8 * Version 2.30.4
9 * License Subject matter of licence is the software iCalcreator.
10 * The above copyright, link, package and version notices,
11 * this licence notice and the invariant [rfc5545] PRODID result use
12 * as implemented and invoked in iCalcreator shall be included in
13 * all copies or substantial portions of the iCalcreator.
14 *
15 * iCalcreator is free software: you can redistribute it and/or modify
16 * it under the terms of the GNU Lesser General Public License as published
17 * by the Free Software Foundation, either version 3 of the License,
18 * or (at your option) any later version.
19 *
20 * iCalcreator is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU Lesser General Public License for more details.
24 *
25 * You should have received a copy of the GNU Lesser General Public License
26 * along with iCalcreator. If not, see <https://www.gnu.org/licenses/>.
27 *
28 * This file is a part of iCalcreator.
29 */
30 namespace Kigkonsult\Icalcreator\Util;
31
32 use Kigkonsult\Icalcreator\Vcalendar;
33 use UnexpectedValueException;
34 use function bin2hex;
35 use function count;
36 use function ctype_digit;
37 use function explode;
38 use function floor;
39 use function in_array;
40 use function openssl_random_pseudo_bytes;
41 use function ord;
42 use function rtrim;
43 use function sprintf;
44 use function str_ireplace;
45 use function str_replace;
46 use function strlen;
47 use function stripos;
48 use function strpos;
49 use function strrev;
50 use function strtolower;
51 use function strtoupper;
52 use function substr;
53 use function substr_count;
54 use function trim;
55
56 /**
57 * iCalcreator TEXT support class
58 *
59 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
60 * @since 2.30.4 - 2021-03-07
61 */
62 class StringFactory
63 {
64
65 /**
66 * @var string
67 */
68 public static $BS2 = '\\';
69 public static $QQ = '"';
70
71 /**
72 * Return concatenated calendar rows, one row for each property
73 *
74 * @param array $rows
75 * @return array
76 * @since 2.29.20 - 2020-01-31
77 */
78 public static function concatRows( $rows )
79 {
80 static $CHARs = [ ' ', "\t" ];
81 $output = [];
82 $cnt = count( $rows );
83 for( $i = 0; $i < $cnt; $i++ ) {
84 $line = rtrim( $rows[$i], Util::$CRLF );
85 $i1 = $i + 1;
86 while(( $i < $cnt ) && isset( $rows[$i1] ) &&
87 ! empty( $rows[$i1] ) &&
88 in_array( substr( $rows[$i1], 0, 1 ), $CHARs )) {
89 $i += 1;
90 $line .= rtrim( substr( $rows[$i], 1 ), Util::$CRLF );
91 $i1 = $i + 1;
92 } // end while
93 $output[] = $line;
94 } // end for
95 return $output;
96 }
97
98 /**
99 * @var string
100 */
101 private static $BEGIN_VCALENDAR = 'BEGIN:VCALENDAR';
102 private static $END_VCALENDAR = 'END:VCALENDAR';
103 private static $NLCHARS = '\n';
104
105 /**
106 * Return rows to parse from string or array
107 *
108 * Used by Vcalendar & RegulateTimezoneFactory
109 * @param string|array $unParsedText strict rfc2445 formatted, single property string or array of strings
110 * @return array
111 * @throws UnexpectedValueException
112 * @since 2.29.3 - 2019-08-29
113 */
114 public static function conformParseInput( $unParsedText = null )
115 {
116 static $ERR10 = 'Only %d rows in calendar content :%s';
117 $arrParse = false;
118 if( is_array( $unParsedText )) {
119 $rows = implode( self::$NLCHARS . Util::$CRLF, $unParsedText );
120 $arrParse = true;
121 }
122 else { // string
123 $rows = $unParsedText;
124 }
125 /* fix line folding */
126 $rows = self::convEolChar( $rows );
127 if( $arrParse ) {
128 foreach( $rows as $lix => $row ) {
129 $rows[$lix] = self::trimTrailNL( $row );
130 }
131 }
132 /* skip leading (empty/invalid) lines (and remove leading BOM chars etc) */
133 $rows = self::trimLeadingRows( $rows );
134 $cnt = count( $rows );
135 if( 3 > $cnt ) { /* err 10 */
136 throw new UnexpectedValueException(
137 sprintf( $ERR10, $cnt, PHP_EOL . implode( PHP_EOL, $rows ))
138 );
139 }
140 /* skip trailing empty lines and ensure an end row */
141 $rows = self::trimTrailingRows( $rows );
142 return $rows;
143 }
144
145 /**
146 * Return array to parse with leading (empty/invalid) lines removed (incl leading BOM chars etc)
147 *
148 * @param array $rows
149 * @return array
150 * @since 2.29.3 - 2019-08-29
151 */
152 private static function trimLeadingRows( $rows )
153 {
154 foreach( $rows as $lix => $row ) {
155 if( false !== stripos( $row, self::$BEGIN_VCALENDAR )) {
156 $rows[$lix] = self::$BEGIN_VCALENDAR;
157 break;
158 }
159 unset( $rows[$lix] );
160 } // end foreach
161 return $rows;
162 }
163
164 /**
165 * Return array to parse with trailing empty lines removed and ensured an end row
166 *
167 * @param array $rows
168 * @return array
169 * @since 2.29.3 - 2019-08-29
170 */
171 private static function trimTrailingRows( $rows )
172 {
173 $lix = array_keys( $rows );
174 $lix = end( $lix );
175 while( 3 < $lix ) {
176 $tst = trim( $rows[$lix] );
177 if(( self::$NLCHARS == $tst ) || empty( $tst )) {
178 unset( $rows[$lix] );
179 $lix--;
180 continue;
181 }
182 if( false === stripos( $rows[$lix], self::$END_VCALENDAR )) {
183 $rows[] = self::$END_VCALENDAR;
184 }
185 else {
186 $rows[$lix] = self::$END_VCALENDAR;
187 }
188 break;
189 } // end while
190 return $rows;
191 }
192
193 /**
194 * Return string with removed ical line folding
195 *
196 * Remove any line-endings that may include spaces or tabs
197 * and convert all line endings (iCal default '\r\n'),
198 * takes care of '\r\n', '\r' and '\n' and mixed '\r\n'+'\r', '\r\n'+'\n'
199 *
200 * @param string $text
201 * @return array
202 * @since 2.29.9 - 2019-03-30
203 */
204 public static function convEolChar( & $text )
205 {
206 static $BASEDELIM = null;
207 static $BASEDELIMs = null;
208 static $EMPTYROW = null;
209 static $FMT = '%1$s%2$75s%1$s';
210 static $CRLFs = [ "\r\n", "\n\r", "\n", "\r" ];
211 static $CRLFexts = [ "\r\n ", "\r\n\t" ];
212 /* fix dummy line separator etc */
213 if( empty( $BASEDELIM )) {
214 $BASEDELIM = self::getRandChars( 16 );
215 $BASEDELIMs = $BASEDELIM . $BASEDELIM;
216 $EMPTYROW = sprintf( $FMT, $BASEDELIM, Util::$SP0 );
217 }
218 /* fix eol chars */
219 $text = str_replace( $CRLFs, $BASEDELIM, $text );
220 /* fix empty lines */
221 $text = str_replace( $BASEDELIMs, $EMPTYROW, $text );
222 /* fix line folding */
223 $text = str_replace( $BASEDELIM, Util::$CRLF, $text );
224 $text = str_replace( $CRLFexts, null, $text );
225 /* split in component/property lines */
226 return explode( Util::$CRLF, $text );
227 }
228
229 /**
230 * Return formatted output for calendar component property
231 *
232 * @param string $label property name
233 * @param string $attributes property attributes
234 * @param string $content property content
235 * @return string
236 * @since 2.22.20 - 2017-01-30
237 */
238 public static function createElement(
239 $label,
240 $attributes = null,
241 $content = null
242 ) {
243 $output = strtoupper( $label );
244 if( ! empty( $attributes )) {
245 $output .= trim( $attributes );
246 }
247 $output .= Util::$COLON . trim( $content );
248 return self::size75( $output );
249 }
250
251 /**
252 * Return property name and (params+)value from (string) row
253 *
254 * @param string $row
255 * @return array propName and the trailing part of the row
256 * @since 2.29.11 - 2019-08-26
257 */
258 public static function getPropName( $row )
259 {
260 $sclnPos = strpos( $row, Util::$SEMIC );
261 $clnPos = strpos( $row, Util::$COLON );
262 switch( true ) {
263 case (( false === $sclnPos ) && ( false === $clnPos )) : // no params and no value
264 return [ $row, null ];
265 break;
266 case (( false !== $sclnPos ) && ( false === $clnPos )) : // param exist and NO value ??
267 $propName = self::before( Util::$SEMIC, $row );
268 break;
269 case (( false === $sclnPos ) && ( false !== $clnPos )) : // no params
270 $propName = self::before( Util::$COLON, $row );
271 break;
272 case ( $sclnPos < $clnPos ) : // param(s) with value ??
273 $propName = self::before( Util::$SEMIC, $row );
274 break;
275 default : // ie $sclnPos > $clnPos // no params
276 $propName = self::before( Util::$COLON, $row );
277 break;
278 } // end switch
279 return [ $propName, self::after( $propName, $row ) ];
280 }
281
282 /**
283 * Return a random (and unique) sequence of characters
284 *
285 * @param int $cnt
286 * @return string
287 * @since 2.27.3 - 2018-12-28
288 */
289 public static function getRandChars( $cnt )
290 {
291 $cnt = (int) floor( $cnt / 2 );
292 $x = 0;
293 do {
294 $randChars = bin2hex( openssl_random_pseudo_bytes( $cnt, $cStrong ));
295 $x += 1;
296 } while(( 3 > $x ) && ( false == $cStrong ));
297 return $randChars;
298 }
299
300 /**
301 * Return bool true if name is X-prefixed
302 *
303 * @param string $name
304 * @return bool
305 * @since 2.29.5 - 2019-08-30
306 */
307 public static function isXprefixed( $name )
308 {
309 static $X_ = 'X-';
310 return ( $X_ == strtoupper( substr( $name, 0, 2 ) ));
311 }
312
313 /**
314 * Return wrapped string with (byte oriented) line breaks at pos 75
315 *
316 * Lines of text SHOULD NOT be longer than 75 octets, excluding the line
317 * break. Long content lines SHOULD be split into a multiple line
318 * representations using a line "folding" technique. That is, a long
319 * line can be split between any two characters by inserting a CRLF
320 * immediately followed by a single linear white space character (i.e.,
321 * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
322 * of CRLF followed immediately by a single linear white space character
323 * is ignored (i.e., removed) when processing the content type.
324 *
325 * Edited 2007-08-26 by Anders Litzell, anders@litzell.se to fix bug where
326 * the reserved expression "\n" in the arg $string could be broken up by the
327 * folding of lines, causing ambiguity in the return string.
328 *
329 * @param string $string
330 * @return string
331 * @link http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
332 * @since 2.29.29 - 2020-09-11
333 */
334 public static function size75( $string )
335 {
336 static $LCN = 'n';
337 static $UCN = 'N';
338 static $SPBSLCN = ' \n';
339 static $SP1 = ' ';
340 $tmp = $string;
341 $len = strlen( $tmp );
342 $string = null;
343 $cCnt = $x = 0;
344 while( true ) {
345 $x1 = $x + 1;
346 if( $len < $x ) {
347 $string .= Util::$CRLF; // loop breakes here
348 break;
349 }
350 elseif(( 74 <= $cCnt ) &&
351 ( self::$BS2 == substr( $tmp, $x, 1 ) ) && // '\\'
352 (( $LCN == substr( $tmp, $x1, 1 )) ||
353 ( $UCN == substr( $tmp, $x1, 1 )))) {
354 $string .= Util::$CRLF . $SPBSLCN; // don't break lines inside '\n'
355 $x += 2;
356 if( $len < $x ) {
357 $string .= Util::$CRLF;
358 break; // or here...
359 }
360 $cCnt = 3;
361 }
362 elseif( 75 <= $cCnt ) {
363 // $string .= Util::$CRLF . $SP1;
364 $string .= Util::$CRLF;
365 if( $len == $x ) {
366 break; // or here..
367 }
368 $string .= $SP1;
369 $cCnt = 1;
370 }
371 $str1 = substr( $tmp, $x, 1 );
372 $byte = ord( $str1 );
373 $string .= $str1;
374 switch( true ) {
375 case(( $byte >= 0x20 ) && ( $byte <= 0x7F )) :
376 $cCnt += 1; // characters U-00000000 - U-0000007F (same as ASCII)
377 break; // add a one byte character
378 case(( $byte & 0xE0 ) == 0xC0 ) : // characters U-00000080 - U-000007FF, mask 110XXXXX
379 if( $len > ( $x + 1 )) {
380 $cCnt += 1;
381 $x += 1; // add second byte of a two bytes character
382 $string .= substr( $tmp, $x, 1 );
383 }
384 break;
385 case(( $byte & 0xF0 ) == 0xE0 ) : // characters U-00000800 - U-0000FFFF, mask 1110XXXX
386 if( $len > ( $x + 2 )) {
387 $cCnt += 1;
388 $x += 1;
389 $string .= substr( $tmp, $x1, 2 );
390 $x += 1; // add byte 2-3 of a three bytes character
391 }
392 break;
393 case(( $byte & 0xF8 ) == 0xF0 ) : // characters U-00010000 - U-001FFFFF, mask 11110XXX
394 if( $len > ( $x + 3 )) {
395 $cCnt += 1;
396 $x += 1;
397 $string .= substr( $tmp, $x1, 3 );
398 $x += 3; // add byte 2-4 of a four bytes character
399 }
400 break;
401 case(( $byte & 0xFC ) == 0xF8 ) : // characters U-00200000 - U-03FFFFFF, mask 111110XX
402 if( $len > ( $x + 4 )) {
403 $cCnt += 1;
404 $x += 1;
405 $string .= substr( $tmp, $x, 4 );
406 $x += 4; // add byte 2-5 of a five bytes character
407 }
408 break;
409 case(( $byte & 0xFE ) == 0xFC ) : // characters U-04000000 - U-7FFFFFFF, mask 1111110X
410 if( $len > ( $x + 5 )) {
411 $cCnt += 1;
412 $x += 1;
413 $string .= substr( $tmp, $x, 5 );
414 $x += 5; // add byte 2-6 of a six bytes character
415 }
416 break;
417 default: // add any other byte without counting up $cCnt
418 break;
419 } // end switch( true )
420 $x += 1; // next 'byte' to test
421 } // end while( true )
422 return $string;
423 }
424
425 /**
426 * Return property value and attributes
427 *
428 * Attributes are prefixed by ';', value by ':', BUT they may exists in attr/values (quoted?)
429 *
430 * @param string $line property content
431 * @param string $propName
432 * @return array [line, [*propAttr]]
433 * @todo fix 2-5 pos port number
434 * @since 2.30.3 - 2021-02-14
435 */
436 public static function splitContent( $line, $propName = null )
437 {
438 static $CSS = '://';
439 static $EQ = '=';
440 static $URIprops = [ Vcalendar::SOURCE, Vcalendar::URL, Vcalendar::TZURL ];
441 $clnPos = strpos( $line, Util::$COLON );
442 if(( false === $clnPos )) {
443 return [ $line, [] ]; // no params
444 }
445 if( 0 == $clnPos ) { // no params, most frequent
446 return [ substr( $line, 1 ) , [] ];
447 }
448 if( in_array( strtoupper( $propName ), $URIprops )) {
449 self::checkFixUriValue( $line );
450 }
451 if( self::checkSingleParam( $line )) { // one param
452 $param = self::between( Util::$SEMIC, Util::$COLON, $line );
453 return [
454 self::after( Util::$COLON, $line ),
455 [
456 self::before( $EQ, $param ) =>
457 trim( self::after( $EQ, $param ), self::$QQ )
458 ]
459 ];
460 } // end if
461 /* more than one param here (or a tricky one...) */
462 $attr = [];
463 $attrix = -1;
464 $WithinQuotes = false;
465 $len = strlen( $line );
466 $cix = 0;
467 while( $cix < $len ) {
468 $str1 = substr( $line, $cix, 1 );
469 $cix1 = $cix + 1;
470 if( ! $WithinQuotes &&
471 ( Util::$COLON == $str1 ) &&
472 ( $CSS != substr( $line, $cix, 3 )) && // '://'
473 ! self::colonIsPrefixedByProtocol( $line, $cix ) &&
474 ! self::hasPortNUmber( substr( $line, $cix1, 7 ))) {
475 $line = substr( $line, $cix1 );
476 break;
477 }
478 if( self::$QQ == $str1 ) { // '"'
479 $WithinQuotes = ! $WithinQuotes;
480 }
481 if( Util::$SEMIC == $str1 ) { // ';'
482 $attrix += 1;
483 $attr[$attrix] = null; // initiate
484 }
485 else {
486 $attr[$attrix] .= $str1;
487 }
488 $cix += 1;
489 } // end while...
490 /* make attributes in array format */
491 $propAttr = [];
492 foreach( $attr as $attribute ) {
493 $attrSplit = explode( $EQ, $attribute, 2 );
494 if( 1 < count( $attrSplit )) {
495 $propAttr[$attrSplit[0]] = $attrSplit[1];
496 }
497 }
498 return [ $line, $propAttr ];
499 }
500
501 /**
502 * Return true if single param only (and no colons in param values)
503 *
504 * 2nd most frequent
505 *
506 * @param $line
507 * @return bool
508 * @since 2.30.3 - 2021-02-14
509 */
510 private static function checkSingleParam( $line )
511 {
512 if( 0 !== strpos( $line, Util::$SEMIC )) {
513 return false;
514 }
515 return (( 1 == substr_count( $line, Util::$SEMIC )) &&
516 ( 1 == substr_count( $line, Util::$COLON )));
517 }
518
519 /**
520 * Replace opt value prefix 'VALUE=URI:message://' by ':' also (opt un-urldecoded) '<'|'>'|'@'
521
522 * orginating from any Apple device
523 *
524 * @param $line
525 * @since 2.30.4 - 2021-03-07
526 */
527 public static function checkFixUriValue( & $line )
528 {
529 static $VEQU = ';VALUE=URI';
530 static $MSGPFX1 = 'message:';
531 static $MSGPFX2 = 'message://';
532 static $PFCHARS1 = '%3C';
533 static $SFCHARS1 = '%3E';
534 static $PFCHARS2 = '<';
535 static $SFCHARS2 = '>';
536 static $SCHAR31 = '%40';
537 static $SCHAR32 = '@';
538 if( false !== stripos( $line, $VEQU )) {
539 $line = str_ireplace( $VEQU, Util::$SP0, $line );
540 }
541 if(( false !== stripos( $line, $MSGPFX1 )) && ( false === stripos( $line, $MSGPFX2 ))) {
542 $line = str_ireplace( $MSGPFX1, $MSGPFX2, $line );
543 }
544 if(( false !== strpos( $line, $PFCHARS1 )) && ( false !== strpos( $line, $SFCHARS1 ))) {
545 $line = str_replace( [ $PFCHARS1, $SFCHARS1 ], Util::$SP0, $line );
546 }
547 elseif(( false !== strpos( $line, $PFCHARS2 )) && ( false !== strpos( $line, $SFCHARS2 ))) {
548 $line = str_replace( [ $PFCHARS2, $SFCHARS2 ], Util::$SP0, $line );
549 }
550 if( false !== strpos( $line, $SCHAR31 )) {
551 $line = str_replace( $SCHAR31, $SCHAR32, $line );
552 }
553 }
554
555 /**
556 * Return bool true if colon-pos is prefixed by protocol
557 *
558 * @see https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml overkill !!
559 *
560 * @param string $line
561 * @param int $cix
562 * @return bool
563 * @accvess private
564 * @since 2.30.2 - 2021-02-04
565 */
566 private static function colonIsPrefixedByProtocol( $line, $cix )
567 {
568 static $MSTZ = [ 'utc-', 'utc+', 'gmt-', 'gmt+' ];
569 static $PROTO3 = [ 'cid:', 'sms:', 'tel:', 'urn:' ]; // 'fax:' removed
570 static $PROTO4 = [ 'crid:', 'news:', 'pres:', 'http:' ];
571 static $PROTO5 = [ 'https:' ];
572 static $PROTO6 = [ 'mailto:', 'telnet:' ];
573 static $PROTO7 = [ 'message:' ];
574 $line = strtolower( $line );
575 return (( in_array( substr( $line, $cix - 6, 4 ), $MSTZ )) || // ?? -6
576 ( in_array( substr( $line, $cix - 3, 4 ), $PROTO3 )) ||
577 ( in_array( substr( $line, $cix - 4, 5 ), $PROTO4 )) ||
578 ( in_array( substr( $line, $cix - 5, 6 ), $PROTO5 )) ||
579 ( in_array( substr( $line, $cix - 6, 7 ), $PROTO6 )) ||
580 ( in_array( substr( $line, $cix - 7, 8 ), $PROTO7 )));
581 }
582
583 /**
584 * Return bool true if leading chars in string is a port number (e.i. followed by '/')
585 *
586 * @param string $string
587 * @return bool
588 * @since 2.27.22 - 2020-09-01
589 */
590 private static function hasPortNUmber( $string )
591 {
592 $len = strlen( $string );
593 for( $x = 0; $x < $len; $x++ ) {
594 $str1 = substr( $string, $x, 1 );
595 if( ! ctype_digit( $str1 )) {
596 break;
597 }
598 if( Util::$SLASH == $str1 ) {
599 return true;
600 }
601 } // end for
602 return false;
603 }
604
605 /**
606 * Fix rfc5545. 3.3.11 Text, ESCAPED-CHAR
607 *
608 * @param string $string
609 * @return string
610 * @since 2.27.14 - 2019-02-20
611 */
612 public static function strrep( $string )
613 {
614 static $BSLCN = '\n';
615 static $SPECCHAR = [ 'n', 'N', 'r', ',', ';' ];
616 static $SQ = "'";
617 static $QBSLCR = "\r";
618 static $QBSLCN = "\n";
619 static $BSUCN = '\N';
620 $string = (string) $string;
621 $strLen = strlen( $string );
622 $pos = 0;
623 // replace single (solo-)backslash by double ones
624 while( $pos < $strLen ) {
625 if( false === ( $pos = strpos( $string, self::$BS2, $pos ))) {
626 break;
627 }
628 if( ! in_array( substr( $string, $pos, 1 ), $SPECCHAR )) {
629 $string = substr( $string, 0, $pos ) .
630 self::$BS2 . substr( $string, ( $pos + 1 ));
631 $pos += 1;
632 }
633 $pos += 1;
634 } // end while
635 // replace double quote by single ones
636 if( false !== strpos( $string, self::$QQ )) {
637 $string = str_replace( self::$QQ, $SQ, $string );
638 }
639 // replace comma by backslash+comma but skip any previously set of backslash+comma
640 // replace semicolon by backslash+semicolon but skip any previously set of backslash+semicolon
641 foreach( [ Util::$COMMA, Util::$SEMIC ] as $char ) {
642 $offset = 0;
643 while( false !== ( $pos = strpos( $string, $char, $offset ))) {
644 if(( 0 < $pos ) && ( self::$BS2 != substr( $string, ( $pos - 1 )))) {
645 $string = substr( $string, 0, $pos ) .
646 self::$BS2 . substr( $string, $pos );
647 }
648 $offset = $pos + 2;
649 } // end while
650 $string = str_replace(
651 self::$BS2 . self::$BS2 . $char,
652 self::$BS2 . $char,
653 $string
654 );
655 }
656 // replace "\r\n" by '\n'
657 if( false !== strpos( $string, Util::$CRLF )) {
658 $string = str_replace( Util::$CRLF, $BSLCN, $string );
659 }
660 // or replace "\r" by '\n'
661 elseif( false !== strpos( $string, $QBSLCR )) {
662 $string = str_replace( $QBSLCR, $BSLCN, $string );
663 }
664 // or replace '\N' by '\n'
665 elseif( false !== strpos( $string, $QBSLCN )) {
666 $string = str_replace( $QBSLCN, $BSLCN, $string );
667 }
668 // replace '\N' by '\n'
669 if( false !== strpos( $string, $BSUCN )) {
670 $string = str_replace( $BSUCN, $BSLCN, $string );
671 }
672 // replace "\r\n" by '\n'
673 $string = str_replace( Util::$CRLF, $BSLCN, $string );
674 return $string;
675 }
676
677 /**
678 * Special characters management input
679 *
680 * @param string $string
681 * @return string
682 * @since 2.22.2 - 2015-06-25
683 */
684 public static function strunrep( $string )
685 {
686 static $BS4 = '\\\\';
687 static $BSCOMMA = '\,';
688 static $BSSEMIC = '\;';
689 $string = str_replace( $BS4, self::$BS2, $string );
690 $string = str_replace( $BSCOMMA, Util::$COMMA, $string );
691 $string = str_replace( $BSSEMIC, Util::$SEMIC, $string );
692 return $string;
693 }
694
695 /**
696 * Return string with trimmed trailing \n
697 *
698 * @param string $value
699 * @return string
700 * @since 2.29.14 - 2019-09-03
701 */
702 public static function trimTrailNL( $value )
703 {
704 static $NL = '\n';
705 $value = (string) $value;
706 if( $NL == strtolower( substr( $value, -2 ))) {
707 $value = substr( $value, 0, ( strlen( $value ) - 2 ));
708 }
709 return $value;
710 }
711
712 /**
713 * @link https://php.net/manual/en/function.substr.php#112707
714 */
715
716 /**
717 * @var string
718 */
719 private static $SP0 = '';
720
721 /**
722 * Return bool true if needle is in haystack
723 *
724 * Case-sensitive search for needle in haystack
725 *
726 * @param $needle
727 * @param $haystack
728 * @return bool
729 */
730 public static function isIn( $needle, $haystack )
731 {
732 return ( false !== ( $pos = strpos( $haystack, $needle )));
733 }
734
735 /**
736 * Return substring after first found needle in haystack, '' on not found
737 *
738 * Case-sensitive search for needle in haystack
739 *
740 * @link https://php.net/manual/en/function.substr.php#112707
741 * @param string $needle
742 * @param string $haystack
743 * @return string
744 */
745 public static function after( $needle, $haystack )
746 {
747 if( ! self::isIn( $needle, $haystack )) {
748 return self::$SP0;
749 }
750 $pos = strpos( $haystack, $needle );
751 return substr( $haystack, $pos + strlen( $needle ));
752 }
753
754 /**
755 * Return substring after last found needle in haystack, '' on not found
756 *
757 * Case-sensitive search for needle in haystack
758 *
759 * @link https://php.net/manual/en/function.substr.php#112707
760 * @param string $needle
761 * @param string $haystack
762 * @return string
763 */
764 public static function afterLast( $needle, $haystack )
765 {
766 if( ! self::isIn( $needle, $haystack )) {
767 return self::$SP0;
768 }
769 $pos = self::strrevpos( $haystack, $needle );
770 return substr( $haystack, $pos + strlen( $needle ));
771 }
772
773 /**
774 * Return substring before first found needle in haystack, '' on not found
775 *
776 * Case-sensitive search for needle in haystack
777 *
778 * @link https://php.net/manual/en/function.substr.php#112707
779 * @param string $needle
780 * @param string $haystack
781 * @return string
782 */
783 public static function before( $needle, $haystack )
784 {
785 if( ! self::isIn( $needle, $haystack )) {
786 return self::$SP0;
787 }
788 return substr( $haystack, 0, strpos( $haystack, $needle ));
789 }
790
791 /**
792 * Return substring before last needle in haystack, '' on not found
793 *
794 * Case-sensitive search for needle in haystack
795 *
796 * @link https://php.net/manual/en/function.substr.php#112707
797 * @param string $needle
798 * @param string $haystack
799 * @return string
800 */
801 public static function beforeLast( $needle, $haystack )
802 {
803 if( ! self::isIn( $needle, $haystack )) {
804 return self::$SP0;
805 }
806 return substr( $haystack, 0, self::strrevpos( $haystack, $needle ));
807 }
808
809 /**
810 * Return substring between needles in haystack
811 *
812 * Case-sensitive search for needles in haystack
813 * If no needles found in haystack, '' is returned
814 * If only needle1 found, substring after is returned
815 * If only needle2 found, substring before is returned
816 *
817 * @link https://php.net/manual/en/function.substr.php#112707
818 * @param string $needle1
819 * @param string $needle2
820 * @param string $haystack
821 * @return string
822 */
823 public static function between( $needle1, $needle2, $haystack )
824 {
825 $exists1 = self::isIn( $needle1, $haystack );
826 $exists2 = self::isIn( $needle2, $haystack );
827 switch( true ) {
828 case ( ! $exists1 && ! $exists2 ) :
829 return self::$SP0;
830 break;
831 case ( $exists1 && ! $exists2 ) :
832 return self::after( $needle1, $haystack );
833 break;
834 case ( ! $exists1 && $exists2 ) :
835 return self::before( $needle2, $haystack );
836 break;
837 default :
838 return self::before( $needle2, self::after( $needle1, $haystack ));
839 break;
840 } // end switch
841 }
842
843 /**
844 * Return substring between last needles in haystack
845 *
846 * Case-sensitive search for needles in haystack
847 *
848 * @link https://php.net/manual/en/function.substr.php#112707
849 * @param string $needle1
850 * @param string $needle2
851 * @param string $haystack
852 * @return string
853 */
854 public static function betweenLast( $needle1, $needle2, $haystack )
855 {
856 return self::afterLast( $needle1, self::beforeLast( $needle2, $haystack ));
857 }
858
859 /**
860 * Return int for length from start to last needle in haystack, false on not found
861 *
862 * Case-sensitive search for needle in haystack
863 *
864 * @link https://php.net/manual/en/function.substr.php#112707
865 * @param string $haystack
866 * @param string $needle
867 * @return int
868 */
869 public static function strrevpos( $haystack, $needle )
870 {
871 return ( false !== ( $rev_pos = strpos( strrev( $haystack ), strrev( $needle ))))
872 ? ( strlen( $haystack ) - $rev_pos - strlen( $needle ))
873 : false;
874 }
875
876 /**
877 * Return bool true if haystack starts with needle, false on not found or to large
878 *
879 * Case-sensitive search for needle in haystack
880 *
881 * @param string $haystack
882 * @param string $needle
883 * @param string $len if found contains length of needle
884 * @return bool
885 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
886 * @since 2.29.11 - 2019-08-28
887 */
888 public static function startsWith( $haystack, $needle, & $len = null )
889 {
890 $len = null;
891 $needleLen = strlen( $needle );
892 if( $needleLen > strlen( $haystack )) {
893 return false;
894 }
895 if( 0 === strpos( $haystack, $needle )) {
896 $len = $needleLen;
897 return true;
898 }
899 return false;
900 }
901
902 /**
903 * Return bool true if haystack ends with needle, false on not found or to large
904 *
905 * Case-sensitive search for needle in haystack
906 *
907 * @param string $haystack
908 * @param string $needle
909 * @param string $len if found contains length of needle
910 * @return bool
911 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
912 * @since 2.29.23 - 2020-07-28
913 */
914 public static function endsWith( $haystack, $needle, & $len = null )
915 {
916 $len = null;
917 $needleLen = strlen( $needle );
918 if( $needleLen > strlen( $haystack )) {
919 return false;
920 }
921 if( $needle == substr( $haystack, ( 0 - $needleLen ))) {
922 $len = $needleLen;
923 return true;
924 }
925 return false;
926 }
927 }
928