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.

Smart_Quotes_Fix::apply()   B
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 129
Code Lines 74

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 64
CRAP Score 5

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 74
c 5
b 0
f 0
dl 0
loc 129
ccs 64
cts 64
cp 1
rs 8.2561
cc 5
nc 5
nop 3
crap 5

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\Fixes\Node_Fixes;
29
30
use PHP_Typography\DOM;
31
use PHP_Typography\RE;
32
use PHP_Typography\Settings;
33
use PHP_Typography\Strings;
34
use PHP_Typography\U;
35
36
/**
37
 * Applies smart quotes (if enabled).
38
 *
39
 * @author Peter Putzer <[email protected]>
40
 *
41
 * @since 5.0.0
42
 */
43
class Smart_Quotes_Fix extends Abstract_Node_Fix {
44
45
	const NUMBERS_BEFORE_PRIME = '\b(?:\d+\/)?\d{1,3}';
46
47
	const DOUBLE_PRIME        = '/(' . self::NUMBERS_BEFORE_PRIME . ")(?:''|\")(?=\W|\Z|-\w)/S";
48
	const SINGLE_PRIME        = '/(' . self::NUMBERS_BEFORE_PRIME . ")'(?=\W|\Z|-\w)/S";
49
	const SINGLE_DOUBLE_PRIME = '/(' . self::NUMBERS_BEFORE_PRIME . ")'(\s*)(\b(?:\d+\/)?\d+)(?:''|\")(?=\W|\Z)/S";
50
51
	const SINGLE_QUOTED_NUMBERS = "/(?<=\W|\A)'([^\"]*\d+)'(?=\W|\Z)/S";
52
	const DOUBLE_QUOTED_NUMBERS = '/(?<=\W|\A)"([^"]*\d+)"(?=\W|\Z)/S';
53
	const COMMA_QUOTE           = '/(?<=\s|\A),(?=\S)/S';
54
	const APOSTROPHE_WORDS      = "/(?<=\w)'(?=\w)/S";
55
	const APOSTROPHE_DECADES    = "/'(\d\d(s|er)?\b)/S"; // Allow both English '80s and German '80er.
56
	const SINGLE_QUOTE_OPEN     = "/(?: '(?=\w) )  | (?: (?<=\s|\A)'(?=\S) )/Sx"; // Alternative is for expressions like _'¿hola?'_.
57
	const SINGLE_QUOTE_CLOSE    = "/(?: (?<=\w)' ) | (?: (?<=\S)'(?=\s|\Z) )/Sx";
58
	const DOUBLE_QUOTE_OPEN     = '/(?: "(?=\w) )  | (?: (?<=\s|\A)"(?=\S) )/Sx';
59
	const DOUBLE_QUOTE_CLOSE    = '/(?: (?<=\w)" ) | (?: (?<=\S)"(?=\s|\Z) )/Sx';
60
61
	/**
62
	 * Cached primary quote style.
63
	 *
64
	 * @var \PHP_Typography\Settings\Quotes|null
65
	 */
66
	protected $cached_primary_quotes;
67
68
	/**
69
	 * Cached secondary quote style.
70
	 *
71
	 * @var \PHP_Typography\Settings\Quotes|null
72
	 */
73
	protected $cached_secondary_quotes;
74
75
	/**
76
	 * Brackets matching array (depending on quote styles).
77
	 *
78
	 * @var array
79
	 */
80
	protected $brackets_matches;
81
82
	/**
83
	 * Brackets replacement array (depending on quote styles).
84
	 *
85
	 * @var array
86
	 */
87
	protected $brackets_replacements;
88
89
	/**
90
	 * Apply the fix to a given textnode.
91
	 *
92
	 * @param \DOMText $textnode Required.
93
	 * @param Settings $settings Required.
94
	 * @param bool     $is_title Optional. Default false.
95
	 */
96 52
	public function apply( \DOMText $textnode, Settings $settings, $is_title = false ) {
97 52
		if ( empty( $settings[ Settings::SMART_QUOTES ] ) ) {
98 16
			return;
99
		}
100
101
		// Need to get context of adjacent characters outside adjacent inline tags or HTML comment
102
		// if we have adjacent characters add them to the text.
103 36
		$previous_character = DOM::get_prev_chr( $textnode );
104 36
		$next_character     = DOM::get_next_chr( $textnode );
105 36
		$node_data          = "{$previous_character}{$textnode->data}{$next_character}";
106 36
		$f                  = Strings::functions( $node_data );
107
108
		// Various special characters and regular expressions.
109 36
		$double = $settings->primary_quote_style();
110 36
		$single = $settings->secondary_quote_style();
111
112
		// Mark quotes to ensure proper removal of replaced adjacent characters.
113 36
		$double_open  = RE::ESCAPE_MARKER . $double->open() . RE::ESCAPE_MARKER;
114 36
		$double_close = RE::ESCAPE_MARKER . $double->close() . RE::ESCAPE_MARKER;
115 36
		$single_open  = RE::ESCAPE_MARKER . $single->open() . RE::ESCAPE_MARKER;
116 36
		$single_close = RE::ESCAPE_MARKER . $single->close() . RE::ESCAPE_MARKER;
117
118 36
		if ( $double != $this->cached_primary_quotes || $single != $this->cached_secondary_quotes ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison -- object value comparison.
119 36
			$this->update_smart_quotes_brackets( $double_open, $double_close, $single_open, $single_close );
120 36
			$this->cached_primary_quotes   = $double;
121 36
			$this->cached_secondary_quotes = $single;
122
		}
123
124
		// Handle excpetions first.
125 36
		if ( ! empty( $settings[ Settings::SMART_QUOTES_EXCEPTIONS ] ) ) {
126 36
			$node_data = \str_replace( $settings[ Settings::SMART_QUOTES_EXCEPTIONS ]['patterns'], $settings[ Settings::SMART_QUOTES_EXCEPTIONS ]['replacements'], $node_data );
127
		}
128
129
		// Before primes, handle quoted numbers (and quotes ending in numbers).
130 36
		$node_data = \preg_replace(
131
			[
132 36
				self::SINGLE_QUOTED_NUMBERS . $f['u'],
133 36
				self::DOUBLE_QUOTED_NUMBERS . $f['u'],
134
			],
135
			[
136 36
				"{$single_open}\$1{$single_close}",
137 36
				"{$double_open}\$1{$double_close}",
138
			],
139 36
			$node_data
140
		);
141
142
		// Guillemets.
143 36
		$node_data = \str_replace( [ '<<', '>>' ], [ U::GUILLEMET_OPEN, U::GUILLEMET_CLOSE ],  $node_data );
144
145
		// Primes.
146 36
		$node_data = \preg_replace(
147
			[
148 36
				self::SINGLE_DOUBLE_PRIME . $f['u'],
149 36
				self::DOUBLE_PRIME . $f['u'], // should not interfere with regular quote matching.
150 36
				self::SINGLE_PRIME . $f['u'],
151
			],
152
			[
153
				'$1' . U::SINGLE_PRIME . '$2$3' . U::DOUBLE_PRIME, // @codeCoverageIgnoreStart
154
				'$1' . U::DOUBLE_PRIME,
155
				'$1' . U::SINGLE_PRIME, // @codeCoverageIgnoreEnd
156
			],
157 36
			$node_data
158
		);
159
160
		// Backticks & comma quotes.
161 36
		$node_data = \str_replace(
162 36
			[ '``', '`', "''", ',,' ],
163 36
			[ $double_open, $single_open, $double_close, U::DOUBLE_LOW_9_QUOTE ],
164 36
			$node_data
165
		);
166 36
		$node_data = \preg_replace( self::COMMA_QUOTE . $f['u'], U::SINGLE_LOW_9_QUOTE, $node_data ); // like _,¿hola?'_.
167
168
		// Apostrophes.
169 36
		$node_data = \preg_replace(
170 36
			[ self::APOSTROPHE_WORDS . $f['u'], self::APOSTROPHE_DECADES . $f['u'] ],
171 36
			[ U::APOSTROPHE, U::APOSTROPHE . '$1' ],
172 36
			$node_data
173
		);
174
175
		// Quotes.
176 36
		$node_data = \str_replace( $this->brackets_matches, $this->brackets_replacements, $node_data );
177 36
		$node_data = \preg_replace(
178
			[
179 36
				self::SINGLE_QUOTE_OPEN . $f['u'],
180 36
				self::SINGLE_QUOTE_CLOSE . $f['u'],
181 36
				self::DOUBLE_QUOTE_OPEN . $f['u'],
182 36
				self::DOUBLE_QUOTE_CLOSE . $f['u'],
183
			],
184
			[
185 36
				$single_open,
186 36
				$single_close,
187 36
				$double_open,
188 36
				$double_close,
189
			],
190 36
			$node_data
191
		);
192
193
		// Quote catch-alls - assume left over quotes are closing - as this is often the most complicated position, thus most likely to be missed.
194 36
		$node_data = \str_replace( [ "'", '"' ], [ $single_close, $double_close ], $node_data );
195
196
		// Add a thin non-breaking space between secondary and primary quotes.
197 36
		$node_data = \str_replace(
198 36
			[ "{$double_open}{$single_open}", "{$single_close}{$double_close}" ],
199 36
			[ $double_open . U::NO_BREAK_NARROW_SPACE . $single_open, $single_close . U::NO_BREAK_NARROW_SPACE . $double_close ],
200 36
			$node_data
201
		);
202
203
		// Check if adjacent characters where replaced with multi-byte replacements.
204
		$quotes          = [
205 36
			$double_open,
206 36
			$double_close,
207 36
			$single_open,
208 36
			$single_close,
209
			U::GUILLEMET_OPEN, // @codeCoverageIgnoreStart
210
			U::GUILLEMET_CLOSE,
211
			U::DOUBLE_PRIME,
212
			U::SINGLE_PRIME,
213
			U::APOSTROPHE,
214
			U::DOUBLE_LOW_9_QUOTE,
215
			U::SINGLE_LOW_9_QUOTE, // @codeCoverageIgnoreEnd
216
		];
217 36
		$previous_length = self::calc_adjacent_length( $f['strlen']( $previous_character ), $previous_character, $node_data, $quotes, $f['substr'], $f['strlen'], false );
218 36
		$next_length     = self::calc_adjacent_length( $f['strlen']( $next_character ), $next_character, $node_data, $quotes, $f['substr'], $f['strlen'], true );
219
220
		// If we have adjacent characters, remove them from the text.
221 36
		$node_data = self::remove_adjacent_characters( $node_data, $f['strlen'], $f['substr'], $previous_length, $next_length );
222
223
		// Remove the escape markers and restore the text to the actual node.
224 36
		$textnode->data = \str_replace( RE::ESCAPE_MARKER, '', $node_data );
225 36
	}
226
227
	/**
228
	 * Calculates the adjacent character length.
229
	 *
230
	 * @param  int      $current_length     The current length of the adjacent character(s).
231
	 * @param  string   $adjacent_character The adjacent character.
232
	 * @param  string   $haystack           The complete string.
233
	 * @param  string[] $needles            The replacement(s) to look for.
234
	 * @param  callable $substr             A `substr`-like function.
235
	 * @param  callable $strlen             A 'strlen'-like function.
236
	 * @param  bool     $reverse            Optional. Default false.
237
	 *
238
	 * @return int
239
	 */
240 36
	private static function calc_adjacent_length( $current_length, $adjacent_character, $haystack, array $needles, callable $substr, callable $strlen, $reverse = false ) {
241 36
		if ( $current_length > 0 && $adjacent_character !== $substr( $haystack, $reverse ? -$current_length : 0, $current_length ) ) {
242 2
			foreach ( $needles as $needle ) {
243 2
				$len = $strlen( $needle );
244
245 2
				if ( $needle === $substr( $haystack, ( $reverse ? -$len : 0 ), $len ) ) {
246 2
					return $len;
247
				}
248
			}
249
		}
250
251 35
		return $current_length;
252
	}
253
254
	/**
255
	 * Update smartQuotesBrackets component after quote style change.
256
	 *
257
	 * @param  string $primary_open    Primary quote style open.
258
	 * @param  string $primary_close   Primary quote style close.
259
	 * @param  string $secondary_open  Secondary quote style open.
260
	 * @param  string $secondary_close Secondary quote style close.
261
	 */
262 16
	private function update_smart_quotes_brackets( $primary_open, $primary_close, $secondary_open, $secondary_close ) {
263
		$brackets = [
264
			// Single quotes.
265 16
			"['"  => '[' . $secondary_open,
266 16
			"{'"  => '{' . $secondary_open,
267 16
			"('"  => '(' . $secondary_open,
268 16
			"']"  => $secondary_close . ']',
269 16
			"'}"  => $secondary_close . '}',
270 16
			"')"  => $secondary_close . ')',
271
272
			// Double quotes.
273 16
			'["'  => '[' . $primary_open,
274 16
			'{"'  => '{' . $primary_open,
275 16
			'("'  => '(' . $primary_open,
276 16
			'"]'  => $primary_close . ']',
277 16
			'"}'  => $primary_close . '}',
278 16
			'")'  => $primary_close . ')',
279
280
			// Quotes & quotes.
281 16
			"\"'" => $primary_open . $secondary_open,
282 16
			"'\"" => $secondary_close . $primary_close,
283
		];
284
285 16
		$this->brackets_matches      = \array_keys( $brackets );
286 16
		$this->brackets_replacements = \array_values( $brackets );
287 16
	}
288
}
289