Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Pull Request — master (#117)
by Der Mundschenk
03:58
created

Settings::set_defaults()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 65
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 53
dl 0
loc 65
ccs 0
cts 54
cp 0
rs 9.0254
c 1
b 0
f 0
cc 1
nc 1
nop 0
crap 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 *  This file is part of PHP-Typography.
4
 *
5
 *  Copyright 2014-2019 Peter Putzer.
6
 *  Copyright 2009-2011 KINGdesk, LLC.
7
 *
8
 *  This program is free software; you can redistribute it and/or modify
9
 *  it under the terms of the GNU General Public License as published by
10
 *  the Free Software Foundation; either version 2 of the License, or
11
 *  (at your option) any later version.
12
 *
13
 *  This program is distributed in the hope that it will be useful,
14
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 *  GNU General Public License for more details.
17
 *
18
 *  You should have received a copy of the GNU General Public License along
19
 *  with this program; if not, write to the Free Software Foundation, Inc.,
20
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21
 *
22
 *  ***
23
 *
24
 *  @package mundschenk-at/php-typography
25
 *  @license http://www.gnu.org/licenses/gpl-2.0.html
26
 */
27
28
namespace PHP_Typography;
29
30
use PHP_Typography\Settings\Dash_Style;
31
use PHP_Typography\Settings\Quote_Style;
32
use PHP_Typography\Settings\Quotes;
33
34
/**
35
 * Store settings for the PHP_Typography class.
36
 *
37
 * @author Peter Putzer <[email protected]>
38
 *
39
 * @since 4.0.0
40
 */
41
class Settings implements \ArrayAccess, \JsonSerializable {
42
43
	/**
44
	 * The current no-break narrow space character.
45
	 *
46
	 * @var string
47
	 */
48
	protected $no_break_narrow_space;
49
50
	/**
51
	 * Primary quote style.
52
	 *
53
	 * @var Quotes
54
	 */
55
	protected $primary_quote_style;
56
57
	/**
58
	 * Secondary quote style.
59
	 *
60
	 * @var Quotes
61
	 */
62
	protected $secondary_quote_style;
63
64
	/**
65
	 * A regex pattern for custom units (or the empty string).
66
	 *
67
	 * @var string
68
	 */
69
	protected $custom_units = '';
70
71
	/**
72
	 * A hashmap of settings for the various typographic options.
73
	 *
74
	 * @var array
75
	 */
76
	protected $data = [];
77
78
	/**
79
	 * The current dash style.
80
	 *
81
	 * @var Settings\Dashes
82
	 */
83
	protected $dash_style;
84
85
	/**
86
	 * Sets up a new Settings object.
87
	 *
88
	 * @since 6.0.0 If $set_defaults is `false`, the settings object is not fully
89
	 *              initialized unless `set_smart_quotes_primary`,
90
	 *              `set_smart_quotes_secondary`, `set_smart_dashes_style` and
91
	 *              `set_true_no_break_narrow_space` are called explicitly.
92
	 *
93
	 * @param bool $set_defaults If true, set default values for various properties. Defaults to true.
94
	 */
95
	public function __construct( $set_defaults = true ) {
96
		if ( $set_defaults ) {
97
			$this->set_defaults();
98
		}
99
	}
100
101
	/**
102
	 * Provides access to named settings (object syntax).
103
	 *
104
	 * @param string $key The settings key.
105
	 *
106
	 * @return mixed
107
	 */
108
	public function &__get( $key ) {
109
		return $this->data[ $key ];
110
	}
111
112
	/**
113
	 * Changes a named setting (object syntax).
114
	 *
115
	 * @param string $key   The settings key.
116
	 * @param mixed  $value The settings value.
117
	 */
118
	public function __set( $key, $value ) {
119
		$this->data[ $key ] = $value;
120
	}
121
122
	/**
123
	 * Checks if a named setting exists (object syntax).
124
	 *
125
	 * @param string $key The settings key.
126
	 */
127
	public function __isset( $key ) {
128
		return isset( $this->data[ $key ] );
129
	}
130
131
	/**
132
	 * Unsets a named setting.
133
	 *
134
	 * @param string $key The settings key.
135
	 */
136
	public function __unset( $key ) {
137
		unset( $this->data[ $key ] );
138
	}
139
140
	/**
141
	 * Changes a named setting (array syntax).
142
	 *
143
	 * @param string $offset The settings key.
144
	 * @param mixed  $value  The settings value.
145
	 */
146
	public function offsetSet( $offset, $value ) {
147
		if ( ! empty( $offset ) ) {
148
			$this->data[ $offset ] = $value;
149
		}
150
	}
151
152
	/**
153
	 * Checks if a named setting exists (array syntax).
154
	 *
155
	 * @param string $offset The settings key.
156
	 */
157
	public function offsetExists( $offset ) {
158
		return isset( $this->data[ $offset ] );
159
	}
160
161
	/**
162
	 * Unsets a named setting (array syntax).
163
	 *
164
	 * @param string $offset The settings key.
165
	 */
166
	public function offsetUnset( $offset ) {
167
		unset( $this->data[ $offset ] );
168
	}
169
170
	/**
171
	 * Provides access to named settings (array syntax).
172
	 *
173
	 * @param string $offset The settings key.
174
	 *
175
	 * @return mixed
176
	 */
177
	public function offsetGet( $offset ) {
178
		return isset( $this->data[ $offset ] ) ? $this->data[ $offset ] : null;
179
	}
180
181
	/**
182
	 * Provides a JSON serialization of the settings.
183
	 *
184
	 * @return mixed
185
	 */
186
	public function jsonSerialize() {
187
		return \array_merge(
188
			$this->data,
189
			[
190
				'no_break_narrow_space' => $this->no_break_narrow_space,
191
				'primary_quotes'        => "{$this->primary_quote_style->open()}|{$this->primary_quote_style->close()}",
192
				'secondary_quotes'      => "{$this->secondary_quote_style->open()}|{$this->secondary_quote_style->close()}",
193
				'dash_style'            => "{$this->dash_style->interval_dash()}|{$this->dash_style->interval_space()}|{$this->dash_style->parenthetical_dash()}|{$this->dash_style->parenthetical_space()}",
194
				'custom_units'          => $this->custom_units,
195
			]
196
		);
197
	}
198
199
	/**
200
	 * Retrieves the current non-breaking narrow space character (either the
201
	 * regular non-breaking space &nbsp; or the the true non-breaking narrow space &#8239;).
202
	 *
203
	 * @return string
204
	 */
205
	public function no_break_narrow_space() {
206
		return $this->no_break_narrow_space;
207
	}
208
209
	/**
210
	 * Retrieves the primary (double) quote style.
211
	 *
212
	 * @return Quotes
213
	 */
214
	public function primary_quote_style() {
215
		return $this->primary_quote_style;
216
	}
217
218
	/**
219
	 * Retrieves the secondary (single) quote style.
220
	 *
221
	 * @return Quotes
222
	 */
223
	public function secondary_quote_style() {
224
		return $this->secondary_quote_style;
225
	}
226
227
	/**
228
	 * Retrieves the dash style.
229
	 *
230
	 * @return Settings\Dashes
231
	 */
232
	public function dash_style() {
233
		return $this->dash_style;
234
	}
235
236
	/**
237
	 * Retrieves the custom units pattern.
238
	 *
239
	 * @return string The pattern is suitable for inclusion into a regular expression.
240
	 */
241
	public function custom_units() {
242
		return $this->custom_units;
243
	}
244
245
	/**
246
	 * (Re)set various options to their default values.
247
	 */
248
	public function set_defaults() {
249
		// General attributes.
250
		$this->set_tags_to_ignore();
251
		$this->set_classes_to_ignore();
252
		$this->set_ids_to_ignore();
253
254
		// Smart characters.
255
		$this->set_smart_quotes();
256
		$this->set_smart_quotes_primary();
257
		$this->set_smart_quotes_secondary();
258
		$this->set_smart_quotes_exceptions();
259
		$this->set_smart_dashes();
260
		$this->set_smart_dashes_style();
261
		$this->set_smart_ellipses();
262
		$this->set_smart_diacritics();
263
		$this->set_diacritic_language();
264
		$this->set_diacritic_custom_replacements();
265
		$this->set_smart_marks();
266
		$this->set_smart_ordinal_suffix();
267
		$this->set_smart_ordinal_suffix_match_roman_numerals();
268
		$this->set_smart_math();
269
		$this->set_smart_fractions();
270
		$this->set_smart_exponents();
271
		$this->set_smart_area_units();
272
273
		// Smart spacing.
274
		$this->set_single_character_word_spacing();
275
		$this->set_fraction_spacing();
276
		$this->set_unit_spacing();
277
		$this->set_french_punctuation_spacing();
278
		$this->set_units();
279
		$this->set_dash_spacing();
280
		$this->set_dewidow();
281
		$this->set_max_dewidow_length();
282
		$this->set_max_dewidow_pull();
283
		$this->set_dewidow_word_number();
284
		$this->set_wrap_hard_hyphens();
285
		$this->set_url_wrap();
286
		$this->set_email_wrap();
287
		$this->set_min_after_url_wrap();
288
		$this->set_space_collapse();
289
		$this->set_true_no_break_narrow_space();
290
291
		// Character styling.
292
		$this->set_style_ampersands();
293
		$this->set_style_caps();
294
		$this->set_style_initial_quotes();
295
		$this->set_style_numbers();
296
		$this->set_style_hanging_punctuation();
297
		$this->set_initial_quote_tags();
298
299
		// Hyphenation.
300
		$this->set_hyphenation();
301
		$this->set_hyphenation_language();
302
		$this->set_min_length_hyphenation();
303
		$this->set_min_before_hyphenation();
304
		$this->set_min_after_hyphenation();
305
		$this->set_hyphenate_headings();
306
		$this->set_hyphenate_all_caps();
307
		$this->set_hyphenate_title_case();
308
		$this->set_hyphenate_compounds();
309
		$this->set_hyphenation_exceptions();
310
311
		// Parser error handling.
312
		$this->set_ignore_parser_errors();
313
	}
314
315
	/**
316
	 * Enable lenient parser error handling (HTML is "best guess" if enabled).
317
	 *
318
	 * @param bool $on Optional. Default false.
319
	 */
320
	public function set_ignore_parser_errors( $on = false ) {
321
		$this->data['parserErrorsIgnore'] = $on;
322
	}
323
324
	/**
325
	 * Sets an optional handler for parser errors. Invalid callbacks will be silently ignored.
326
	 *
327
	 * @since 6.0.0. callable type is enforced via typehinting.
328
	 *
329
	 * @param callable|null $handler Optional. A callable that takes an array of error strings as its parameter. Default null.
330
	 */
331
	public function set_parser_errors_handler( callable $handler = null ) {
332
		$this->data['parserErrorsHandler'] = $handler;
333
	}
334
335
	/**
336
	 * Enable usage of true "no-break narrow space" (&#8239;) instead of the normal no-break space (&nbsp;).
337
	 *
338
	 * @param bool $on Optional. Default false.
339
	 */
340
	public function set_true_no_break_narrow_space( $on = false ) {
341
342
		if ( $on ) {
343
			$this->no_break_narrow_space = U::NO_BREAK_NARROW_SPACE;
344
		} else {
345
			$this->no_break_narrow_space = U::NO_BREAK_SPACE;
346
		}
347
	}
348
349
	/**
350
	 * Sets tags for which the typography of their children will be left untouched.
351
	 *
352
	 * @param string|array $tags A comma separated list or an array of tag names.
353
	 */
354
	public function set_tags_to_ignore( $tags = [ 'code', 'head', 'kbd', 'object', 'option', 'pre', 'samp', 'script', 'noscript', 'noembed', 'select', 'style', 'textarea', 'title', 'var', 'math' ] ) {
355
		// Ensure that we pass only lower-case tag names to XPath.
356
		$tags = array_filter( array_map( 'strtolower', Strings::maybe_split_parameters( $tags ) ), 'ctype_alnum' );
357
358
		$this->data['ignoreTags'] = array_unique( array_merge( $tags, array_flip( DOM::inappropriate_tags() ) ) );
359
	}
360
361
	/**
362
	 * Sets classes for which the typography of their children will be left untouched.
363
	 *
364
	 * @param string|array $classes A comma separated list or an array of class names.
365
	 */
366
	public function set_classes_to_ignore( $classes = [ 'vcard', 'noTypo' ] ) {
367
		$this->data['ignoreClasses'] = Strings::maybe_split_parameters( $classes );
368
	}
369
370
	/**
371
	 * Sets IDs for which the typography of their children will be left untouched.
372
	 *
373
	 * @param string|array $ids A comma separated list or an array of tag names.
374
	 */
375
	public function set_ids_to_ignore( $ids = [] ) {
376
		$this->data['ignoreIDs'] = Strings::maybe_split_parameters( $ids );
377
	}
378
379
	/**
380
	 * Enables/disables typographic quotes.
381
	 *
382
	 * @param bool $on Optional. Default true.
383
	 */
384
	public function set_smart_quotes( $on = true ) {
385
		$this->data['smartQuotes'] = $on;
386
	}
387
388
	/**
389
	 * Sets the style for primary ('double') quotemarks.
390
	 *
391
	 * Allowed values for $style:
392
	 * "doubleCurled" => "&ldquo;foo&rdquo;",
393
	 * "doubleCurledReversed" => "&rdquo;foo&rdquo;",
394
	 * "doubleLow9" => "&bdquo;foo&rdquo;",
395
	 * "doubleLow9Reversed" => "&bdquo;foo&ldquo;",
396
	 * "singleCurled" => "&lsquo;foo&rsquo;",
397
	 * "singleCurledReversed" => "&rsquo;foo&rsquo;",
398
	 * "singleLow9" => "&sbquo;foo&rsquo;",
399
	 * "singleLow9Reversed" => "&sbquo;foo&lsquo;",
400
	 * "doubleGuillemetsFrench" => "&laquo;&nbsp;foo&nbsp;&raquo;",
401
	 * "doubleGuillemets" => "&laquo;foo&raquo;",
402
	 * "doubleGuillemetsReversed" => "&raquo;foo&laquo;",
403
	 * "singleGuillemets" => "&lsaquo;foo&rsaquo;",
404
	 * "singleGuillemetsReversed" => "&rsaquo;foo&lsaquo;",
405
	 * "cornerBrackets" => "&#x300c;foo&#x300d;",
406
	 * "whiteCornerBracket" => "&#x300e;foo&#x300f;"
407
	 *
408
	 * @param  Quotes|string $style Optional. A Quotes instance or a quote style constant. Defaults to 'doubleCurled'.
409
	 *
410
	 * @throws \DomainException Thrown if $style constant is invalid.
411
	 */
412
	public function set_smart_quotes_primary( $style = Quote_Style::DOUBLE_CURLED ) {
413
		$this->primary_quote_style = $this->get_quote_style( $style );
414
	}
415
416
	/**
417
	 * Sets the style for secondary ('single') quotemarks.
418
	 *
419
	 * Allowed values for $style:
420
	 * "doubleCurled" => "&ldquo;foo&rdquo;",
421
	 * "doubleCurledReversed" => "&rdquo;foo&rdquo;",
422
	 * "doubleLow9" => "&bdquo;foo&rdquo;",
423
	 * "doubleLow9Reversed" => "&bdquo;foo&ldquo;",
424
	 * "singleCurled" => "&lsquo;foo&rsquo;",
425
	 * "singleCurledReversed" => "&rsquo;foo&rsquo;",
426
	 * "singleLow9" => "&sbquo;foo&rsquo;",
427
	 * "singleLow9Reversed" => "&sbquo;foo&lsquo;",
428
	 * "doubleGuillemetsFrench" => "&laquo;&nbsp;foo&nbsp;&raquo;",
429
	 * "doubleGuillemets" => "&laquo;foo&raquo;",
430
	 * "doubleGuillemetsReversed" => "&raquo;foo&laquo;",
431
	 * "singleGuillemets" => "&lsaquo;foo&rsaquo;",
432
	 * "singleGuillemetsReversed" => "&rsaquo;foo&lsaquo;",
433
	 * "cornerBrackets" => "&#x300c;foo&#x300d;",
434
	 * "whiteCornerBracket" => "&#x300e;foo&#x300f;"
435
	 *
436
	 * @param  Quotes|string $style Optional. A Quotes instance or a quote style constant. Defaults to 'singleCurled'.
437
	 *
438
	 * @throws \DomainException Thrown if $style constant is invalid.
439
	 */
440
	public function set_smart_quotes_secondary( $style = Quote_Style::SINGLE_CURLED ) {
441
		$this->secondary_quote_style = $this->get_quote_style( $style );
442
	}
443
444
	/**
445
	 * Retrieves a Quotes instance from a given style.
446
	 *
447
	 * @param  Quotes|string $style A Quotes instance or a quote style constant.
448
	 *
449
	 * @throws \DomainException Thrown if $style constant is invalid.
450
	 *
451
	 * @return Quotes
452
	 */
453
	protected function get_quote_style( $style ) {
454
		return $this->get_style( $style, Quotes::class, [ Quote_Style::class, 'get_styled_quotes' ], 'quote' );
455
	}
456
457
	/**
458
	 * Sets the list of exceptional words for smart quotes replacement. Mainly,
459
	 * this is used for contractions beginning with an apostrophe.
460
	 *
461
	 * @param string[] $exceptions Optional. An array of replacements indexed by the ”non-smart" form.
462
	 *                             Default a list of English words beginning with an apostrophy.
463
	 */
464
	public function set_smart_quotes_exceptions( $exceptions = [
465
		"'tain't"   => U::APOSTROPHE . 'tain' . U::APOSTROPHE . 't',
466
		"'twere"    => U::APOSTROPHE . 'twere',
467
		"'twas"     => U::APOSTROPHE . 'twas',
468
		"'tis"      => U::APOSTROPHE . 'tis',
469
		"'til"      => U::APOSTROPHE . 'til',
470
		"'bout"     => U::APOSTROPHE . 'bout',
471
		"'nuff"     => U::APOSTROPHE . 'nuff',
472
		"'round"    => U::APOSTROPHE . 'round',
473
		"'cause"    => U::APOSTROPHE . 'cause',
474
		"'splainin" => U::APOSTROPHE . 'splainin',
475
		"'em'"      => U::APOSTROPHE . 'em',
476
	] ) {
477
		$this->data['smartQuotesExceptions'] = [
478
			'patterns'     => \array_keys( $exceptions ),
479
			'replacements' => \array_values( $exceptions ),
480
		];
481
	}
482
483
	/**
484
	 * Retrieves an object from a given style.
485
	 *
486
	 * @param  object|string $style          A style object instance or a style constant.
487
	 * @param  string        $expected_class A class name.
488
	 * @param  callable      $get_style      A function that returns a style object from a given style constant.
489
	 * @param  string        $description    Style description for the exception message.
490
	 *
491
	 * @throws \DomainException Thrown if $style constant is invalid.
492
	 *
493
	 * @return object An instance of $expected_class.
494
	 */
495
	protected function get_style( $style, $expected_class, callable $get_style, $description ) {
496
		if ( $style instanceof $expected_class ) {
497
			$object = $style;
498
		} else {
499
			$object = $get_style( $style, $this );
500
		}
501
502
		if ( ! \is_object( $object ) || ! $object instanceof $expected_class ) {
503
			throw new \DomainException( "Invalid $description style $style." );
504
		}
505
506
		return $object;
507
	}
508
509
	/**
510
	 * Enables/disables replacement of "a--a" with En Dash " -- " and "---" with Em Dash.
511
	 *
512
	 * @param bool $on Optional. Default true.
513
	 */
514
	public function set_smart_dashes( $on = true ) {
515
		$this->data['smartDashes'] = $on;
516
	}
517
518
	/**
519
	 * Sets the typographical conventions used by smart_dashes.
520
	 *
521
	 * Allowed values for $style:
522
	 * - "traditionalUS"
523
	 * - "international"
524
	 *
525
	 * @param string|Settings\Dashes $style Optional. Default Dash_Style::TRADITIONAL_US.
526
	 *
527
	 * @throws \DomainException Thrown if $style constant is invalid.
528
	 */
529
	public function set_smart_dashes_style( $style = Dash_Style::TRADITIONAL_US ) {
530
		$this->dash_style = $this->get_style( $style, Settings\Dashes::class, [ Dash_Style::class, 'get_styled_dashes' ], 'dash' );
531
	}
532
533
	/**
534
	 * Enables/disables replacement of "..." with "…".
535
	 *
536
	 * @param bool $on Optional. Default true.
537
	 */
538
	public function set_smart_ellipses( $on = true ) {
539
		$this->data['smartEllipses'] = $on;
540
	}
541
542
	/**
543
	 * Enables/disables replacement "creme brulee" with "crème brûlée".
544
	 *
545
	 * @param bool $on Optional. Default true.
546
	 */
547
	public function set_smart_diacritics( $on = true ) {
548
		$this->data['smartDiacritics'] = $on;
549
	}
550
551
	/**
552
	 * Sets the language used for diacritics replacements.
553
	 *
554
	 * @param string $lang Has to correspond to a filename in 'diacritics'. Optional. Default 'en-US'.
555
	 */
556
	public function set_diacritic_language( $lang = 'en-US' ) {
557
		if ( isset( $this->data['diacriticLanguage'] ) && $this->data['diacriticLanguage'] === $lang ) {
558
			return;
559
		}
560
561
		$this->data['diacriticLanguage'] = $lang;
562
		$language_file_name              = \dirname( __FILE__ ) . '/diacritics/' . $lang . '.json';
563
564
		if ( \file_exists( $language_file_name ) ) {
565
			$diacritics_file              = \json_decode( \file_get_contents( $language_file_name ), true );
566
			$this->data['diacriticWords'] = $diacritics_file['diacritic_words'];
567
		} else {
568
			unset( $this->data['diacriticWords'] );
569
		}
570
571
		$this->update_diacritics_replacement_arrays();
572
	}
573
574
	/**
575
	 * Sets up custom diacritics replacements.
576
	 *
577
	 * @param string|array $custom_replacements An array formatted [needle=>replacement, needle=>replacement...],
578
	 *                                          or a string formatted `"needle"=>"replacement","needle"=>"replacement",...
579
	 */
580
	public function set_diacritic_custom_replacements( $custom_replacements = [] ) {
581
		if ( ! \is_array( $custom_replacements ) ) {
582
			$custom_replacements = $this->parse_diacritics_replacement_string( $custom_replacements );
583
		}
584
585
		$this->data['diacriticCustomReplacements'] = self::array_map_assoc(
586
			function( $key, $replacement ) {
587
				$key         = \strip_tags( \trim( $key ) );
588
				$replacement = \strip_tags( \trim( $replacement ) );
589
590
				if ( ! empty( $key ) && ! empty( $replacement ) ) {
591
					return [ $key => $replacement ];
592
				} else {
593
					return [];
594
				}
595
			},
596
			$custom_replacements
597
		);
598
599
		$this->update_diacritics_replacement_arrays();
600
	}
601
602
	/**
603
	 * Parses a custom diacritics replacement string into an array.
604
	 *
605
	 * @param string $custom_replacements A string formatted `"needle"=>"replacement","needle"=>"replacement",...
606
	 *
607
	 * @return array
608
	 */
609
	private function parse_diacritics_replacement_string( $custom_replacements ) {
610
		return self::array_map_assoc(
611
			function( $key, $replacement ) {
612
				// Account for single and double quotes in keys in and values, discard everything else.
613
				if ( \preg_match( '/(?<kquo>"|\')(?<key>(?:(?!\k<kquo>).)+)\k<kquo>\s*=>\s*(?<rquo>"|\')(?<replacement>(?:(?!\k<rquo>).)+)\k<rquo>/', $replacement, $match ) ) {
614
					$key         = $match['key'];
615
					$replacement = $match['replacement'];
616
617
					return [ $key => $replacement ];
618
				}
619
620
				return [];
621
			},
622
			/** RE correct. @scrutinizer ignore-type */
623
			\preg_split( '/,/', $custom_replacements, -1, PREG_SPLIT_NO_EMPTY )
624
		);
625
	}
626
627
	/**
628
	 * Provides an array_map implementation with control over resulting array's keys.
629
	 *
630
	 * Based on https://gist.github.com/jasand-pereza/84ecec7907f003564584.
631
	 *
632
	 * @since 6.0.0
633
	 *
634
	 * @param  callable $callback A callback function that needs to return [ $key => $value ] pairs.
635
	 * @param  array    $array    The array.
636
	 *
637
	 * @return array
638
	 */
639
	protected static function array_map_assoc( callable $callback, array $array ) {
640
		$new = [];
641
642
		foreach ( $array as $k => $v ) {
643
			$u = $callback( $k, $v );
644
645
			if ( ! empty( $u ) ) {
646
				$new[ \key( $u ) ] = \current( $u );
647
			}
648
		}
649
650
		return $new;
651
	}
652
653
	/**
654
	 * Update the pattern and replacement arrays in $settings['diacriticReplacement'].
655
	 *
656
	 * Should be called whenever a new diacritics replacement language is selected or
657
	 * when the custom replacements are updated.
658
	 */
659
	private function update_diacritics_replacement_arrays() {
660
		$patterns     = [];
661
		$replacements = [];
662
663
		if ( ! empty( $this->data['diacriticCustomReplacements'] ) ) {
664
			$this->parse_diacritics_rules( $this->data['diacriticCustomReplacements'], $patterns, $replacements );
665
		}
666
		if ( ! empty( $this->data['diacriticWords'] ) ) {
667
			$this->parse_diacritics_rules( $this->data['diacriticWords'], $patterns, $replacements );
668
		}
669
670
		$this->data['diacriticReplacement'] = [
671
			'patterns'     => $patterns,
672
			'replacements' => $replacements,
673
		];
674
	}
675
676
	/**
677
	 * Parse an array of diacritics rules.
678
	 *
679
	 * @param array $diacritics_rules The rules ( $word => $replacement ).
680
	 * @param array $patterns         Resulting patterns. Passed by reference.
681
	 * @param array $replacements     Resulting replacements. Passed by reference.
682
	 */
683
	private function parse_diacritics_rules( array $diacritics_rules, array &$patterns, array &$replacements ) {
684
685
		foreach ( $diacritics_rules as $needle => $replacement ) {
686
			$patterns[]              = '/\b(?<!\w[' . U::NO_BREAK_SPACE . U::SOFT_HYPHEN . '])' . $needle . '\b(?![' . U::NO_BREAK_SPACE . U::SOFT_HYPHEN . ']\w)/u';
687
			$replacements[ $needle ] = $replacement;
688
		}
689
	}
690
691
	/**
692
	 * Enables/disables replacement of (r) (c) (tm) (sm) (p) (R) (C) (TM) (SM) (P) with ® © ™ ℠ ℗.
693
	 *
694
	 * @param bool $on Optional. Default true.
695
	 */
696
	public function set_smart_marks( $on = true ) {
697
		$this->data['smartMarks'] = $on;
698
	}
699
700
	/**
701
	 * Enables/disables proper mathematical symbols.
702
	 *
703
	 * @param bool $on Optional. Default true.
704
	 */
705
	public function set_smart_math( $on = true ) {
706
		$this->data['smartMath'] = $on;
707
	}
708
709
	/**
710
	 * Enables/disables replacement of 2^2 with 2<sup>2</sup>
711
	 *
712
	 * @param bool $on Optional. Default true.
713
	 */
714
	public function set_smart_exponents( $on = true ) {
715
		$this->data['smartExponents'] = $on;
716
	}
717
718
	/**
719
	 * Enables/disables replacement of 1/4 with <sup>1</sup>&#8260;<sub>4</sub>.
720
	 *
721
	 * @param bool $on Optional. Default true.
722
	 */
723
	public function set_smart_fractions( $on = true ) {
724
		$this->data['smartFractions'] = $on;
725
	}
726
727
	/**
728
	 * Enables/disables replacement of 1st with 1<sup>st</sup>.
729
	 *
730
	 * @param bool $on Optional. Default true.
731
	 */
732
	public function set_smart_ordinal_suffix( $on = true ) {
733
		$this->data['smartOrdinalSuffix'] = $on;
734
	}
735
736
	/**
737
	 * Enables/disables replacement of XXe with XX<sup>e</sup>.
738
	 *
739
	 * @since 6.5.0
740
	 *
741
	 * @param bool $on Optional. Default false.
742
	 */
743
	public function set_smart_ordinal_suffix_match_roman_numerals( $on = false ) {
744
		$this->data['smartOrdinalSuffixRomanNumerals'] = $on;
745
	}
746
747
	/**
748
	 * Enables/disables replacement of m2 with m³ and m3 with m³.
749
	 *
750
	 * @param bool $on Optional. Default true.
751
	 */
752
	public function set_smart_area_units( $on = true ) {
753
		$this->data['smartAreaVolumeUnits'] = $on;
754
	}
755
756
	/**
757
	 * Enables/disables forcing single character words to next line with the insertion of &nbsp;.
758
	 *
759
	 * @param bool $on Optional. Default true.
760
	 */
761
	public function set_single_character_word_spacing( $on = true ) {
762
		$this->data['singleCharacterWordSpacing'] = $on;
763
	}
764
765
	/**
766
	 * Enables/disables fraction spacing.
767
	 *
768
	 * @param bool $on Optional. Default true.
769
	 */
770
	public function set_fraction_spacing( $on = true ) {
771
		$this->data['fractionSpacing'] = $on;
772
	}
773
774
	/**
775
	 * Enables/disables keeping units and values together with the insertion of &nbsp;.
776
	 *
777
	 * @param bool $on Optional. Default true.
778
	 */
779
	public function set_unit_spacing( $on = true ) {
780
		$this->data['unitSpacing'] = $on;
781
	}
782
783
	/**
784
	 * Enables/disables numbered abbreviations like "ISO 9000" together with the insertion of &nbsp;.
785
	 *
786
	 * @param bool $on Optional. Default true.
787
	 */
788
	public function set_numbered_abbreviation_spacing( $on = true ) {
789
		$this->data['numberedAbbreviationSpacing'] = $on;
790
	}
791
792
	/**
793
	 * Enables/disables extra whitespace before certain punction marks, as is the French custom.
794
	 *
795
	 * @since 6.0.0 The default value is now `false`.`
796
	 *
797
	 * @param bool $on Optional. Default false.
798
	 */
799
	public function set_french_punctuation_spacing( $on = false ) {
800
		$this->data['frenchPunctuationSpacing'] = $on;
801
	}
802
803
	/**
804
	 * Sets the list of units to keep together with their values.
805
	 *
806
	 * @param string|array $units A comma separated list or an array of units.
807
	 */
808
	public function set_units( $units = [] ) {
809
		$this->data['units'] = Strings::maybe_split_parameters( $units );
810
		$this->custom_units  = $this->update_unit_pattern( $this->data['units'] );
811
	}
812
813
	/**
814
	 * Update pattern for matching custom units.
815
	 *
816
	 * @since 6.4.0 Visibility changed to protected, return value added.
817
	 *
818
	 * @param array $units An array of unit names.
819
	 *
820
	 * @return string
821
	 */
822
	protected function update_unit_pattern( array $units ) {
823
		// Update unit regex pattern.
824
		foreach ( $units as $index => $unit ) {
825
			$units[ $index ] = \preg_quote( $unit, '/' );
826
		}
827
828
		$custom_units  = \implode( '|', $units );
829
		$custom_units .= ! empty( $custom_units ) ? '|' : '';
830
831
		return $custom_units;
832
	}
833
834
	/**
835
	 * Enables/disables wrapping of Em and En dashes are in thin spaces.
836
	 *
837
	 * @param bool $on Optional. Default true.
838
	 */
839
	public function set_dash_spacing( $on = true ) {
840
		$this->data['dashSpacing'] = $on;
841
	}
842
843
	/**
844
	 * Enables/disables removal of extra whitespace characters.
845
	 *
846
	 * @param bool $on Optional. Default true.
847
	 */
848
	public function set_space_collapse( $on = true ) {
849
		$this->data['spaceCollapse'] = $on;
850
	}
851
852
	/**
853
	 * Enables/disables widow handling.
854
	 *
855
	 * @param bool $on Optional. Default true.
856
	 */
857
	public function set_dewidow( $on = true ) {
858
		$this->data['dewidow'] = $on;
859
	}
860
861
	/**
862
	 * Sets the maximum length of widows that will be protected.
863
	 *
864
	 * @param int $length Defaults to 5. Trying to set the value to less than 2 resets the length to the default.
865
	 */
866
	public function set_max_dewidow_length( $length = 5 ) {
867
		$length = ( $length > 1 ) ? $length : 5;
868
869
		$this->data['dewidowMaxLength'] = $length;
870
	}
871
872
	/**
873
	 * Sets the maximum number of words considered for dewidowing.
874
	 *
875
	 * @param int $number Defaults to 1. Only 1, 2 and 3 are valid.
876
	 */
877
	public function set_dewidow_word_number( $number = 1 ) {
878
		$number = ( $number > 3 || $number < 1 ) ? 1 : $number;
879
880
		$this->data['dewidowWordNumber'] = $number;
881
	}
882
883
	/**
884
	 * Sets the maximum length of pulled text to keep widows company.
885
	 *
886
	 * @param int $length Defaults to 5. Trying to set the value to less than 2 resets the length to the default.
887
	 */
888
	public function set_max_dewidow_pull( $length = 5 ) {
889
		$length = ( $length > 1 ) ? $length : 5;
890
891
		$this->data['dewidowMaxPull'] = $length;
892
	}
893
894
	/**
895
	 * Enables/disables wrapping at internal hard hyphens with the insertion of a zero-width-space.
896
	 *
897
	 * @param bool $on Optional. Default true.
898
	 */
899
	public function set_wrap_hard_hyphens( $on = true ) {
900
		$this->data['hyphenHardWrap'] = $on;
901
	}
902
903
	/**
904
	 * Enables/disables wrapping of urls.
905
	 *
906
	 * @param bool $on Optional. Default true.
907
	 */
908
	public function set_url_wrap( $on = true ) {
909
		$this->data['urlWrap'] = $on;
910
	}
911
912
	/**
913
	 * Enables/disables wrapping of email addresses.
914
	 *
915
	 * @param bool $on Optional. Default true.
916
	 */
917
	public function set_email_wrap( $on = true ) {
918
		$this->data['emailWrap'] = $on;
919
	}
920
921
	/**
922
	 * Sets the minimum character requirement after an URL wrapping point.
923
	 *
924
	 * @param int $length Defaults to 5. Trying to set the value to less than 1 resets the length to the default.
925
	 */
926
	public function set_min_after_url_wrap( $length = 5 ) {
927
		$length = ( $length > 0 ) ? $length : 5;
928
929
		$this->data['urlMinAfterWrap'] = $length;
930
	}
931
932
	/**
933
	 * Enables/disables wrapping of ampersands in <span class="amp">.
934
	 *
935
	 * @param bool $on Optional. Default true.
936
	 */
937
	public function set_style_ampersands( $on = true ) {
938
		$this->data['styleAmpersands'] = $on;
939
	}
940
941
	/**
942
	 * Enables/disables wrapping caps in <span class="caps">.
943
	 *
944
	 * @param bool $on Optional. Default true.
945
	 */
946
	public function set_style_caps( $on = true ) {
947
		$this->data['styleCaps'] = $on;
948
	}
949
950
	/**
951
	 * Enables/disables wrapping of initial quotes in <span class="quo"> or <span class="dquo">.
952
	 *
953
	 * @param bool $on Optional. Default true.
954
	 */
955
	public function set_style_initial_quotes( $on = true ) {
956
		$this->data['styleInitialQuotes'] = $on;
957
	}
958
959
	/**
960
	 * Enables/disables wrapping of numbers in <span class="numbers">.
961
	 *
962
	 * @param bool $on Optional. Default true.
963
	 */
964
	public function set_style_numbers( $on = true ) {
965
		$this->data['styleNumbers'] = $on;
966
	}
967
968
	/**
969
	 * Enables/disables wrapping of punctiation and wide characters in <span class="pull-*">.
970
	 *
971
	 * @param bool $on Optional. Default true.
972
	 */
973
	public function set_style_hanging_punctuation( $on = true ) {
974
		$this->data['styleHangingPunctuation'] = $on;
975
	}
976
977
	/**
978
	 * Sets the list of tags where initial quotes and guillemets should be styled.
979
	 *
980
	 * @param string|array $tags A comma separated list or an array of tag names.
981
	 */
982
	public function set_initial_quote_tags( $tags = [ 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'li', 'dd', 'dt' ] ) {
983
		// Make array if handed a list of tags as a string.
984
		if ( ! \is_array( $tags ) ) {
985
			$tags = \preg_split( '/[^a-z0-9]+/', $tags, -1, PREG_SPLIT_NO_EMPTY );
986
		}
987
988
		// Store the tag array inverted (with the tagName as its index for faster lookup).
989
		$this->data['initialQuoteTags'] = \array_change_key_case( \array_flip( /** Array. @scrutinizer ignore-type */ $tags ), CASE_LOWER );
990
	}
991
992
	/**
993
	 * Enables/disables hyphenation.
994
	 *
995
	 * @param bool $on Optional. Default true.
996
	 */
997
	public function set_hyphenation( $on = true ) {
998
		$this->data['hyphenation'] = $on;
999
	}
1000
1001
	/**
1002
	 * Sets the hyphenation pattern language.
1003
	 *
1004
	 * @param string $lang Has to correspond to a filename in 'lang'. Optional. Default 'en-US'.
1005
	 */
1006
	public function set_hyphenation_language( $lang = 'en-US' ) {
1007
		if ( isset( $this->data['hyphenLanguage'] ) && $this->data['hyphenLanguage'] === $lang ) {
1008
			return; // Bail out, no need to do anything.
1009
		}
1010
1011
		$this->data['hyphenLanguage'] = $lang;
1012
	}
1013
1014
	/**
1015
	 * Sets the minimum length of a word that may be hyphenated.
1016
	 *
1017
	 * @param int $length Defaults to 5. Trying to set the value to less than 2 resets the length to the default.
1018
	 */
1019
	public function set_min_length_hyphenation( $length = 5 ) {
1020
		$length = ( $length > 1 ) ? $length : 5;
1021
1022
		$this->data['hyphenMinLength'] = $length;
1023
	}
1024
1025
	/**
1026
	 * Sets the minimum character requirement before a hyphenation point.
1027
	 *
1028
	 * @param int $length Defaults to 3. Trying to set the value to less than 1 resets the length to the default.
1029
	 */
1030
	public function set_min_before_hyphenation( $length = 3 ) {
1031
		$length = ( $length > 0 ) ? $length : 3;
1032
1033
		$this->data['hyphenMinBefore'] = $length;
1034
	}
1035
1036
	/**
1037
	 * Sets the minimum character requirement after a hyphenation point.
1038
	 *
1039
	 * @param int $length Defaults to 2. Trying to set the value to less than 1 resets the length to the default.
1040
	 */
1041
	public function set_min_after_hyphenation( $length = 2 ) {
1042
		$length = ( $length > 0 ) ? $length : 2;
1043
1044
		$this->data['hyphenMinAfter'] = $length;
1045
	}
1046
1047
	/**
1048
	 * Enables/disables hyphenation of titles and headings.
1049
	 *
1050
	 * @param bool $on Optional. Default true.
1051
	 */
1052
	public function set_hyphenate_headings( $on = true ) {
1053
		$this->data['hyphenateTitle'] = $on;
1054
	}
1055
1056
	/**
1057
	 * Enables/disables hyphenation of words set completely in capital letters.
1058
	 *
1059
	 * @param bool $on Optional. Default true.
1060
	 */
1061
	public function set_hyphenate_all_caps( $on = true ) {
1062
		$this->data['hyphenateAllCaps'] = $on;
1063
	}
1064
1065
	/**
1066
	 * Enables/disables hyphenation of words starting with a capital letter.
1067
	 *
1068
	 * @param bool $on Optional. Default true.
1069
	 */
1070
	public function set_hyphenate_title_case( $on = true ) {
1071
		$this->data['hyphenateTitleCase'] = $on;
1072
	}
1073
1074
	/**
1075
	 * Enables/disables hyphenation of compound words (e.g. "editor-in-chief").
1076
	 *
1077
	 * @param bool $on Optional. Default true.
1078
	 */
1079
	public function set_hyphenate_compounds( $on = true ) {
1080
		$this->data['hyphenateCompounds'] = $on;
1081
	}
1082
1083
	/**
1084
	 * Sets custom word hyphenations.
1085
	 *
1086
	 * @param string|array $exceptions An array of words with all hyphenation points marked with a hard hyphen (or a string list of such words).
1087
	 *        In the latter case, only alphanumeric characters and hyphens are recognized. The default is empty.
1088
	 */
1089
	public function set_hyphenation_exceptions( $exceptions = [] ) {
1090
		$this->data['hyphenationCustomExceptions'] = Strings::maybe_split_parameters( $exceptions );
1091
	}
1092
1093
	/**
1094
	 * Retrieves a unique hash value for the current settings.
1095
	 *
1096
	 * @since 5.2.0 The new parameter $raw_output has been added.
1097
	 *
1098
	 * @param int  $max_length Optional. The maximum number of bytes returned (0 for unlimited). Default 16.
1099
	 * @param bool $raw_output Optional. Wether to return raw binary data for the hash. Default true.
1100
	 *
1101
	 * @return string A binary hash value for the current settings limited to $max_length.
1102
	 */
1103
	public function get_hash( $max_length = 16, $raw_output = true ) {
1104
		$hash = \md5( \json_encode( $this ), $raw_output );
1105
1106
		if ( $max_length < \strlen( $hash ) && $max_length > 0 ) {
1107
			$hash = \substr( $hash, 0, $max_length );
1108
		}
1109
1110
		return $hash;
1111
	}
1112
}
1113