Strings.php
16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
<?php
namespace AIOSEO\Plugin\Common\Traits\Helpers;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Contains string specific helper methods.
*
* @since 4.0.13
*/
trait Strings {
/**
* Convert to snake case.
*
* @since 4.0.0
*
* @param string $string The string to convert.
* @return string The converted string.
*/
public function toSnakeCase( $string ) {
$string[0] = strtolower( $string[0] );
return preg_replace_callback( '/([A-Z])/', function ( $value ) {
return '_' . strtolower( $value[1] );
}, $string );
}
/**
* Convert to camel case.
*
* @since 4.0.0
*
* @param string $string The string to convert.
* @param bool $capitalize Whether or not to capitalize the first letter.
* @return string The converted string.
*/
public function toCamelCase( $string, $capitalize = false ) {
$string[0] = strtolower( $string[0] );
if ( $capitalize ) {
$string[0] = strtoupper( $string[0] );
}
return preg_replace_callback( '/_([a-z0-9])/', function ( $value ) {
return strtoupper( $value[1] );
}, $string );
}
/**
* Converts kebab case to camel case.
*
* @since 4.0.0
*
* @param string $string The string to convert.
* @param bool $capitalize Whether or not to capitalize the first letter.
* @return string The converted string.
*/
public function dashesToCamelCase( $string, $capitalizeFirstCharacter = false ) {
$string = str_replace( ' ', '', ucwords( str_replace( '-', ' ', $string ) ) );
if ( ! $capitalizeFirstCharacter ) {
$string[0] = strtolower( $string[0] );
}
return $string;
}
/**
* Truncates a given string.
*
* @since 4.0.0
*
* @param string $string The string.
* @param int $maxCharacters The max. amount of characters.
* @param boolean $shouldHaveEllipsis Whether the string should have a trailing ellipsis (defaults to true).
* @return string $string The string.
*/
public function truncate( $string, $maxCharacters, $shouldHaveEllipsis = true ) {
$length = strlen( $string );
$excessLength = $length - $maxCharacters;
if ( 0 < $excessLength ) {
// If the string is longer than 65535 characters, we first need to shorten it due to the character limit of the regex pattern quantifier.
if ( 65535 < $length ) {
$string = substr( $string, 0, 65534 );
}
$string = preg_replace( "#[^\pZ\pP]*.{{$excessLength}}$#", '', $string );
if ( $shouldHaveEllipsis ) {
$string = $string . ' ...';
}
}
return $string;
}
/**
* Escapes special regex characters.
*
* @since 4.0.5
*
* @param string $string The string.
* @param string $delimiter The delimiter character.
* @return string The escaped string.
*/
public function escapeRegex( $string, $delimiter = '/' ) {
static $escapeRegex = [];
if ( isset( $escapeRegex[ $string ] ) ) {
return $escapeRegex[ $string ];
}
$escapeRegex[ $string ] = preg_quote( $string, $delimiter );
return $escapeRegex[ $string ];
}
/**
* Escapes special regex characters inside the replacement string.
*
* @since 4.0.7
*
* @param string $string The string.
* @return string The escaped string.
*/
public function escapeRegexReplacement( $string ) {
static $escapeRegexReplacement = [];
if ( isset( $escapeRegexReplacement[ $string ] ) ) {
return $escapeRegexReplacement[ $string ];
}
$escapeRegexReplacement[ $string ] = str_replace( '$', '\$', $string );
return $escapeRegexReplacement[ $string ];
}
/**
* preg_replace but with the replacement escaped.
*
* @since 4.0.10
*
* @param string $pattern The pattern to search for.
* @param string $replacement The replacement string.
* @param string $subject The subject to search in.
* @return string The subject with matches replaced.
*/
public function pregReplace( $pattern, $replacement, $subject ) {
if ( ! $subject ) {
return $subject;
}
$key = $pattern . $replacement . $subject;
static $pregReplace = [];
if ( isset( $pregReplace[ $key ] ) ) {
return $pregReplace[ $key ];
}
// TODO: In the future, we should consider escaping the search pattern as well.
// We can use the following pattern for this - (?<!\\)([\/.^$*+?|()[{}\]]{1})
// The pattern above will only escape special characters if they're not escaped yet, which makes it compatible with all our patterns that are already escaped.
// The caveat is that we'd need to first trim off slash delimiters and add them back later - otherwise they'd be escaped as well.
$replacement = $this->escapeRegexReplacement( $replacement );
$pregReplace[ $key ] = preg_replace( $pattern, $replacement, $subject );
return $pregReplace[ $key ];
}
/**
* Returns string after converting it to lowercase.
*
* @since 4.0.13
*
* @param string $string The original string.
* @return string The string converted to lowercase.
*/
public function toLowerCase( $string ) {
static $lowerCased = [];
if ( isset( $lowerCased[ $string ] ) ) {
return $lowerCased[ $string ];
}
$lowerCased[ $string ] = function_exists( 'mb_strtolower' ) ? mb_strtolower( $string, $this->getCharset() ) : strtolower( $string );
return $lowerCased[ $string ];
}
/**
* Returns the index of a substring in a string.
*
* @since 4.1.6
*
* @param string $stack The stack.
* @param string $needle The needle.
* @param int $offset The offset.
* @return int|bool The index where the string starts or false if it does not exist.
*/
public function stringIndex( $stack, $needle, $offset = 0 ) {
$key = $stack . $needle . $offset;
static $stringIndex = [];
if ( isset( $stringIndex[ $key ] ) ) {
return $stringIndex[ $key ];
}
$stringIndex[ $key ] = function_exists( 'mb_strpos' ) ? mb_strpos( $stack, $needle, $offset, $this->getCharset() ) : strpos( $stack, $needle, $offset );
return $stringIndex[ $key ];
}
/**
* Checks if the given string contains the given substring.
*
* @since 4.1.0.2
*
* @param string $stack The stack.
* @param string $needle The needle.
* @param int $offset The offset.
* @return bool Whether the substring occurs in the main string.
*/
public function stringContains( $stack, $needle, $offset = 0 ) {
$key = $stack . $needle . $offset;
static $stringContains = [];
if ( isset( $stringContains[ $key ] ) ) {
return $stringContains[ $key ];
}
$stringContains[ $key ] = false !== $this->stringIndex( $stack, $needle, $offset );
return $stringContains[ $key ];
}
/**
* Check if a string is JSON encoded or not.
*
* @since 4.1.2
*
* @param mixed $string The string to check.
* @return bool True if it is JSON or false if not.
*/
public function isJsonString( $string ) {
if ( ! is_string( $string ) ) {
return false;
}
json_decode( $string );
// Return a boolean whether or not the last error matches.
return json_last_error() === JSON_ERROR_NONE;
}
/**
* Strips punctuation from a given string.
*
* @since 4.0.0
*
* @param string $string The string.
* @param array $charactersToKeep The characters that can't be stripped (optional).
* @return string The string without punctuation.
*/
public function stripPunctuation( $string, $charactersToKeep = [] ) {
$characterRegexPattern = '';
if ( ! empty( $charactersToKeep ) ) {
$characterString = implode( '', $charactersToKeep );
$characterRegexPattern = "(?![$characterString])";
}
$string = aioseo()->helpers->decodeHtmlEntities( $string );
$string = preg_replace( "/{$characterRegexPattern}[\p{P}\d+]/u", '', $string );
$string = aioseo()->helpers->encodeOutputHtml( $string );
// Trim both internal and external whitespace.
return preg_replace( '/\s\s+/u', ' ', trim( $string ) );
}
/**
* Returns the string after it is encoded with htmlspecialchars().
*
* @since 4.0.0
*
* @param string $string The string to encode.
* @return string The encoded string.
*/
public function encodeOutputHtml( $string ) {
return htmlspecialchars( $string, ENT_COMPAT | ENT_HTML401, $this->getCharset(), false );
}
/**
* Returns the string after all HTML entities have been decoded.
*
* @since 4.0.0
*
* @param string $string The string to decode.
* @return string The decoded string.
*/
public function decodeHtmlEntities( $string ) {
static $decodeHtmlEntities = [];
if ( isset( $decodeHtmlEntities[ $string ] ) ) {
return $decodeHtmlEntities[ $string ];
}
// We must manually decode non-breaking spaces since html_entity_decode doesn't do this.
$string = $this->pregReplace( '/ /', ' ', $string );
$decodeHtmlEntities[ $string ] = html_entity_decode( (string) $string, ENT_QUOTES );
return $decodeHtmlEntities[ $string ];
}
/**
* Returns the string with script tags stripped.
*
* @since 4.0.0
*
* @param string $string The string.
* @return string The modified string.
*/
public function stripScriptTags( $string ) {
static $stripScriptTags = [];
if ( isset( $stripScriptTags[ $string ] ) ) {
return $stripScriptTags[ $string ];
}
$stripScriptTags[ $string ] = $this->pregReplace( '/<script(.*?)>(.*?)<\/script>/is', '', $string );
return $stripScriptTags[ $string ];
}
/**
* Returns the string with incomplete HTML tags stripped.
* Incomplete tags are not unopened/unclosed pairs but rather single tags that aren't properly formed.
* e.g. <a href='something'
* e.g. href='something' >
*
* @since 4.1.6
*
* @param string $string The string.
* @return string The modified string.
*/
public function stripIncompleteHtmlTags( $string ) {
static $stripIncompleteHtmlTags = [];
if ( isset( $stripIncompleteHtmlTags[ $string ] ) ) {
return $stripIncompleteHtmlTags[ $string ];
}
$stripIncompleteHtmlTags[ $string ] = $this->pregReplace( '/(^(?!<).*?(\/>)|<[^>]*?(?!\/>)$)/is', '', $string );
return $stripIncompleteHtmlTags[ $string ];
}
/**
* Returns the given JSON formatted data tags as a comma separated list with their values instead.
*
* @since 4.1.0
*
* @param string $tags The JSON formatted data tags.
* @return string The comma separated values.
*/
public function jsonTagsToCommaSeparatedList( $tags ) {
$tags = json_decode( $tags );
$values = [];
foreach ( $tags as $k => $tag ) {
$values[ $k ] = $tag->value;
}
return implode( ',', $values );
}
/**
* Returns the character length of the given string.
*
* @since 4.1.6
*
* @param string $string The string.
* @return int The string length.
*/
public function stringLength( $string ) {
static $stringLength = [];
if ( isset( $stringLength[ $string ] ) ) {
return $stringLength[ $string ];
}
$stringLength[ $string ] = function_exists( 'mb_strlen' ) ? mb_strlen( $string, $this->getCharset() ) : strlen( $string );
return $stringLength[ $string ];
}
/**
* Returns the word count of the given string.
*
* @since 4.1.6
*
* @param string $string The string.
* @return int The word count.
*/
public function stringWordCount( $string ) {
static $stringWordCount = [];
if ( isset( $stringWordCount[ $string ] ) ) {
return $stringWordCount[ $string ];
}
$stringWordCount[ $string ] = str_word_count( $string );
return $stringWordCount[ $string ];
}
/**
* Explodes the given string into an array.
*
* @since 4.1.6
*
* @param string $delimiter The delimiter.
* @param string $string The string.
* @return array The exploded words.
*/
public function explode( $delimiter, $string ) {
$key = $delimiter . $string;
static $exploded = [];
if ( isset( $exploded[ $key ] ) ) {
return $exploded[ $key ];
}
$exploded[ $key ] = explode( $delimiter, $string );
return $exploded[ $key ];
}
/**
* Implodes an array into a WHEREIN clause useable string.
*
* @since 4.1.6
*
* @param array $array The array.
* @param bool $outerQuotes Whether outer quotes should be added.
* @return string The imploded array.
*/
public function implodeWhereIn( $array, $outerQuotes = false ) {
// Reset the keys first in case there is no 0 index.
$array = array_values( $array );
if ( ! isset( $array[0] ) ) {
return '';
}
if ( is_numeric( $array[0] ) ) {
return implode( ', ', $array );
}
return $outerQuotes ? "'" . implode( "', '", $array ) . "'" : implode( "', '", $array );
}
/**
* Returns an imploded string of placeholders for usage in a WPDB prepare statement.
*
* @since 4.1.9
*
* @param array $array The array.
* @param string $placeholder The placeholder (e.g. "%s" or "%d").
* @return string The imploded string with placeholders.
*/
public function implodePlaceholders( $array, $placeholder = '%s' ) {
return implode( ', ', array_fill( 0, count( $array ), $placeholder ) );
}
/**
* Verifies that a string is indeed a valid regular expression.
*
* @since 4.2.1
*
* @return boolean True if the string is a valid regular expression.
*/
public function isValidRegex( $pattern ) {
// Set a custom error handler to prevent throwing errors on a bad Regular Expression.
set_error_handler( function() {}, E_WARNING );
$isValid = true;
if ( false === preg_match( $pattern, null ) ) {
$isValid = false;
}
// Restore the error handler.
restore_error_handler();
return $isValid;
}
/**
* Removes the leading slash(es) from a string.
*
* @since 4.2.3
*
* @param string $string The string.
* @return string The modified string.
*/
public function unleadingSlashIt( $string ) {
return ltrim( $string, '/' );
}
/**
* Convert the case of the given string.
*
* @since 4.2.4
*
* @param string $string The string.
* @param string $type The casing ("lower", "title", "sentence").
* @return string The converted string.
*/
public function convertCase( $string, $type ) {
switch ( $type ) {
case 'lower':
return strtolower( $string );
case 'title':
return $this->toTitleCase( $string );
case 'sentence':
return $this->toSentenceCase( $string );
default:
return $string;
}
}
/**
* Converts the given string to title case.
*
* @since 4.2.4
*
* @param string $string The string.
* @return string The converted string.
*/
public function toTitleCase( $string ) {
// List of common English words that aren't typically modified.
$exceptions = apply_filters( 'aioseo_title_case_exceptions', [
'of',
'a',
'the',
'and',
'an',
'or',
'nor',
'but',
'is',
'if',
'then',
'else',
'when',
'at',
'from',
'by',
'on',
'off',
'for',
'in',
'out',
'over',
'to',
'into',
'with'
] );
$words = explode( ' ', strtolower( $string ) );
foreach ( $words as $k => $word ) {
if ( ! in_array( $word, $exceptions, true ) ) {
$words[ $k ] = ucfirst( $word );
}
}
$string = implode( ' ', $words );
return $string;
}
/**
* Converts the given string to sentence case.
*
* @since 4.2.4
*
* @param string $string The string.
* @return string The converted string.
*/
public function toSentenceCase( $string ) {
$phrases = preg_split( '/([.?!]+)/', $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE );
$convertedString = '';
foreach ( $phrases as $index => $sentence ) {
$convertedString .= ( $index & 1 ) === 0 ? ucfirst( strtolower( trim( $sentence ) ) ) : $sentence . ' ';
}
return trim( $convertedString );
}
/**
* Returns the substring with a given start index and length.
*
* @since 4.2.5
*
* @param string $string The string.
* @param int $startIndex The start index.
* @param int $length The length.
* @return string The substring.
*/
public function substring( $string, $startIndex, $length ) {
return function_exists( 'mb_substr' ) ? mb_substr( $string, $startIndex, $length, $this->getCharset() ) : substr( $string, $startIndex, $length );
}
}