We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.
Total Complexity | 52 |
Total Lines | 436 |
Duplicated Lines | 0 % |
Coverage | 100% |
Changes | 7 | ||
Bugs | 0 | Features | 0 |
Complex classes like PHP_Typography often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use PHP_Typography, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
46 | class PHP_Typography { |
||
47 | |||
48 | /** |
||
49 | * A DOM-based HTML5 parser. |
||
50 | * |
||
51 | * @var \Masterminds\HTML5 |
||
52 | */ |
||
53 | private $html5_parser; |
||
54 | |||
55 | /** |
||
56 | * The hyphenator cache. |
||
57 | * |
||
58 | * @var Hyphenator\Cache |
||
59 | */ |
||
60 | protected $hyphenator_cache; |
||
61 | |||
62 | /** |
||
63 | * The node fixes registry. |
||
64 | * |
||
65 | * @var Registry|null; |
||
66 | */ |
||
67 | private $registry; |
||
68 | |||
69 | /** |
||
70 | * Whether the Hyphenator\Cache of the $registry needs to be updated. |
||
71 | * |
||
72 | * @var bool |
||
73 | */ |
||
74 | private $update_registry_cache; |
||
75 | |||
76 | /** |
||
77 | * Sets up a new PHP_Typography object. |
||
78 | * |
||
79 | * @param Registry|null $registry Optional. A fix registry instance. Default null, |
||
80 | * meaning the default fixes are used. |
||
81 | */ |
||
82 | 1 | public function __construct( Registry $registry = null ) { |
|
85 | 1 | } |
|
86 | |||
87 | /** |
||
88 | * Modifies $html according to the defined settings. |
||
89 | * |
||
90 | * @since 6.0.0 Parameter $body_classes added. |
||
91 | * |
||
92 | * @param string $html A HTML fragment. |
||
93 | * @param Settings $settings A settings object. |
||
94 | * @param bool $is_title Optional. If the HTML fragment is a title. Default false. |
||
95 | * @param string[] $body_classes Optional. CSS classes added to the virtual |
||
96 | * <body> element used for processing. Default []. |
||
97 | * |
||
98 | * @return string The processed $html. |
||
99 | */ |
||
100 | 40 | public function process( $html, Settings $settings, $is_title = false, array $body_classes = [] ) { |
|
101 | 40 | return $this->process_textnodes( |
|
102 | 40 | $html, |
|
103 | function( $html, $settings, $is_title ) { |
||
104 | 33 | $this->get_registry()->apply_fixes( $html, $settings, $is_title, false ); |
|
105 | 40 | }, |
|
106 | $settings, |
||
107 | $is_title, |
||
108 | $body_classes |
||
109 | ); |
||
110 | } |
||
111 | |||
112 | /** |
||
113 | * Modifies $html according to the defined settings, in a way that is appropriate for RSS feeds |
||
114 | * (i.e. excluding processes that may not display well with limited character set intelligence). |
||
115 | * |
||
116 | * @since 6.0.0 Parameter $body_classes added. |
||
117 | * |
||
118 | * @param string $html A HTML fragment. |
||
119 | * @param Settings $settings A settings object. |
||
120 | * @param bool $is_title Optional. If the HTML fragment is a title. Default false. |
||
121 | * @param string[] $body_classes Optional. CSS classes added to the virtual |
||
122 | * <body> element used for processing. Default []. |
||
123 | * |
||
124 | * @return string The processed $html. |
||
125 | */ |
||
126 | 40 | public function process_feed( $html, Settings $settings, $is_title = false, array $body_classes = [] ) { |
|
127 | 40 | return $this->process_textnodes( |
|
128 | 40 | $html, |
|
129 | function( $html, $settings, $is_title ) { |
||
130 | 33 | $this->get_registry()->apply_fixes( $html, $settings, $is_title, true ); |
|
131 | 40 | }, |
|
132 | $settings, |
||
133 | $is_title, |
||
134 | $body_classes |
||
135 | ); |
||
136 | } |
||
137 | |||
138 | /** |
||
139 | * Applies specific fixes to all textnodes of the HTML fragment. |
||
140 | * |
||
141 | * @since 6.0.0 Parameter $body_classes added. |
||
142 | * |
||
143 | * @param string $html A HTML fragment. |
||
144 | * @param callable $fixer A callback that applies typography fixes to a single textnode. |
||
145 | * @param Settings $settings A settings object. |
||
146 | * @param bool $is_title Optional. If the HTML fragment is a title. Default false. |
||
147 | * @param string[] $body_classes Optional. CSS classes added to the virtual |
||
148 | * <body> element used for processing. Default []. |
||
149 | * |
||
150 | * @return string The processed $html. |
||
151 | */ |
||
152 | 79 | public function process_textnodes( $html, callable $fixer, Settings $settings, $is_title = false, array $body_classes = [] ) { |
|
153 | 79 | if ( isset( $settings['ignoreTags'] ) && $is_title && ( \in_array( 'h1', /** Array. @scrutinizer ignore-type */ $settings['ignoreTags'], true ) || \in_array( 'h2', /** Array. @scrutinizer ignore-type */ $settings['ignoreTags'], true ) ) ) { |
|
154 | 38 | return $html; |
|
155 | } |
||
156 | |||
157 | // Lazy-load our parser (the text parser is not needed for feeds). |
||
158 | 41 | $html5_parser = $this->get_html5_parser(); |
|
159 | |||
160 | // Parse the HTML. |
||
161 | 41 | $dom = $this->parse_html( $html5_parser, $html, $settings, $body_classes ); |
|
162 | |||
163 | // Abort if there were parsing errors. |
||
164 | 41 | if ( empty( $dom ) ) { |
|
165 | 2 | return $html; |
|
166 | } |
||
167 | |||
168 | // Query some nodes in the DOM. |
||
169 | 39 | $xpath = new \DOMXPath( $dom ); |
|
170 | 39 | $body_node = $xpath->query( '/html/body' )->item( 0 ); |
|
171 | 39 | $tags_to_ignore = $this->query_tags_to_ignore( $xpath, $body_node, $settings ); |
|
172 | |||
173 | // Start processing. |
||
174 | 39 | foreach ( $xpath->query( '//text()', $body_node ) as $textnode ) { |
|
175 | if ( |
||
176 | // One of the ancestors should be ignored. |
||
177 | 36 | self::arrays_intersect( DOM::get_ancestors( $textnode ), $tags_to_ignore ) || |
|
178 | // The node contains only whitespace. |
||
179 | 36 | $textnode->isWhitespaceInElementContent() |
|
180 | ) { |
||
181 | 3 | continue; |
|
182 | } |
||
183 | |||
184 | // Store original content. |
||
185 | 33 | $original = $textnode->data; |
|
186 | |||
187 | // Apply fixes. |
||
188 | 33 | $fixer( $textnode, $settings, $is_title ); |
|
189 | |||
190 | // Until now, we've only been working on a textnode: HTMLify result. |
||
191 | 33 | $new = $textnode->data; |
|
192 | |||
193 | // Replace original node (if anthing was changed). |
||
194 | 33 | if ( $new !== $original ) { |
|
195 | 1 | $this->replace_node_with_html( $textnode, $settings->apply_character_mapping( $new ) ); |
|
196 | } |
||
197 | } |
||
198 | |||
199 | 39 | return $html5_parser->saveHTML( $body_node->childNodes ); |
|
200 | } |
||
201 | |||
202 | /** |
||
203 | * Determines whether two object arrays intersect. The second array is expected |
||
204 | * to use the spl_object_hash for its keys. |
||
205 | * |
||
206 | * @param array $array1 The keys are ignored. |
||
207 | * @param array $array2 This array has to be in the form ( $spl_object_hash => $object ). |
||
208 | * |
||
209 | * @return boolean |
||
210 | */ |
||
211 | 4 | protected static function arrays_intersect( array $array1, array $array2 ) { |
|
212 | 4 | foreach ( $array1 as $value ) { |
|
213 | 2 | if ( isset( $array2[ \spl_object_hash( $value ) ] ) ) { |
|
214 | 1 | return true; |
|
215 | } |
||
216 | } |
||
217 | |||
218 | 3 | return false; |
|
219 | } |
||
220 | |||
221 | /** |
||
222 | * Parse HTML5 fragment while ignoring certain warnings for invalid HTML code (e.g. duplicate IDs). |
||
223 | * |
||
224 | * @since 6.0.0 Parameter $body_classes added. |
||
225 | * |
||
226 | * @param \Masterminds\HTML5 $parser An intialized parser object. |
||
227 | * @param string $html The HTML fragment to parse (not a complete document). |
||
228 | * @param Settings $settings The settings to apply. |
||
229 | * @param string[] $body_classes Optional. CSS classes added to the virtual |
||
230 | * <body> element used for processing. Default []. |
||
231 | * |
||
232 | * @return \DOMDocument|null The encoding has already been set to UTF-8. Returns null if there were parsing errors. |
||
233 | */ |
||
234 | 79 | public function parse_html( \Masterminds\HTML5 $parser, $html, Settings $settings, array $body_classes = [] ) { |
|
235 | // Silence some parsing errors for invalid HTML. |
||
236 | 79 | \set_error_handler( [ $this, 'handle_parsing_errors' ] ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler |
|
237 | 79 | $xml_error_handling = \libxml_use_internal_errors( true ); |
|
238 | |||
239 | // Inject <body> classes. |
||
240 | 79 | $body = empty( $body_classes ) ? 'body' : 'body class="' . \implode( ' ', $body_classes ) . '"'; |
|
241 | |||
242 | // Do the actual parsing. |
||
243 | 79 | $dom = $parser->loadHTML( "<!DOCTYPE html><html><{$body}>{$html}</body></html>" ); |
|
244 | 79 | $dom->encoding = 'UTF-8'; |
|
245 | |||
246 | // Restore original error handling. |
||
247 | 79 | \libxml_clear_errors(); |
|
248 | 79 | \libxml_use_internal_errors( $xml_error_handling ); |
|
249 | 79 | \restore_error_handler(); |
|
250 | |||
251 | // Handle any parser errors. |
||
252 | 79 | $errors = $parser->getErrors(); |
|
253 | 79 | if ( ! empty( $settings['parserErrorsHandler'] ) && ! empty( $errors ) ) { |
|
254 | 2 | $errors = $settings['parserErrorsHandler']( $errors ); |
|
255 | } |
||
256 | |||
257 | // Return null if there are still unhandled parsing errors. |
||
258 | 79 | if ( ! empty( $errors ) && ! $settings['parserErrorsIgnore'] ) { |
|
259 | 2 | $dom = null; |
|
260 | } |
||
261 | |||
262 | 79 | return $dom; |
|
263 | } |
||
264 | |||
265 | /** |
||
266 | * Silently handle certain HTML parsing errors. |
||
267 | * |
||
268 | * @since 6.0.0 Unused parameters $errline and $errcontext removed. |
||
269 | * |
||
270 | * @param int $errno Error number. |
||
271 | * @param string $errstr Error message. |
||
272 | * @param string $errfile The file in which the error occurred. |
||
273 | * |
||
274 | * @return boolean Returns true if the error was handled, false otherwise. |
||
275 | */ |
||
276 | 4 | public function handle_parsing_errors( $errno, $errstr, $errfile ) { |
|
277 | 4 | if ( ! ( \error_reporting() & $errno ) ) { // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting,WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting |
|
278 | 4 | return true; // not interesting. |
|
279 | } |
||
280 | |||
281 | // Ignore warnings from parser & let PHP handle the rest. |
||
282 | 4 | return $errno & E_USER_WARNING && 0 === \substr_compare( $errfile, 'DOMTreeBuilder.php', -18 ); |
|
283 | } |
||
284 | |||
285 | /** |
||
286 | * Retrieves an array of nodes that should be skipped during processing. |
||
287 | * |
||
288 | * @param \DOMXPath $xpath A valid XPath instance for the DOM to be queried. |
||
289 | * @param \DOMNode $initial_node The starting node of the XPath query. |
||
290 | * @param Settings $settings The settings to apply. |
||
291 | * |
||
292 | * @return \DOMNode[] An array of \DOMNode (can be empty). |
||
293 | */ |
||
294 | 1 | public function query_tags_to_ignore( \DOMXPath $xpath, \DOMNode $initial_node, Settings $settings ) { |
|
295 | 1 | $elements = []; |
|
296 | 1 | $query_parts = []; |
|
297 | 1 | if ( ! empty( $settings['ignoreTags'] ) ) { |
|
298 | 1 | $query_parts[] = '//' . \implode( ' | //', /** Array. @scrutinizer ignore-type */ $settings['ignoreTags'] ); |
|
299 | } |
||
300 | 1 | if ( ! empty( $settings['ignoreClasses'] ) ) { |
|
301 | 1 | $query_parts[] = "//*[contains(concat(' ', @class, ' '), ' " . \implode( " ') or contains(concat(' ', @class, ' '), ' ", /** Array. @scrutinizer ignore-type */ $settings['ignoreClasses'] ) . " ')]"; |
|
302 | } |
||
303 | 1 | if ( ! empty( $settings['ignoreIDs'] ) ) { |
|
304 | 1 | $query_parts[] = '//*[@id=\'' . \implode( '\' or @id=\'', /** Array. @scrutinizer ignore-type */ $settings['ignoreIDs'] ) . '\']'; |
|
305 | } |
||
306 | |||
307 | 1 | if ( ! empty( $query_parts ) ) { |
|
308 | 1 | $ignore_query = \implode( ' | ', $query_parts ); |
|
309 | |||
310 | 1 | $nodelist = $xpath->query( $ignore_query, $initial_node ); |
|
311 | 1 | if ( false !== $nodelist ) { |
|
312 | 1 | $elements = DOM::nodelist_to_array( $nodelist ); |
|
313 | } |
||
314 | } |
||
315 | |||
316 | 1 | return $elements; |
|
317 | } |
||
318 | |||
319 | /** |
||
320 | * Replaces the given node with HTML content. Uses the HTML5 parser. |
||
321 | * |
||
322 | * @param \DOMNode $node The node to replace. |
||
323 | * @param string $content The HTML fragment used to replace the node. |
||
324 | * |
||
325 | * @return \DOMNode|array An array of \DOMNode containing the new nodes or the old \DOMNode if the replacement failed. |
||
326 | */ |
||
327 | 2 | public function replace_node_with_html( \DOMNode $node, $content ) { |
|
359 | } |
||
360 | |||
361 | /** |
||
362 | * Retrieves the fix registry. |
||
363 | * |
||
364 | * @return Registry |
||
365 | */ |
||
366 | 2 | public function get_registry() { |
|
367 | 2 | if ( ! isset( $this->registry ) ) { |
|
368 | 1 | $this->registry = new Default_Registry( $this->get_hyphenator_cache() ); |
|
369 | 1 | } elseif ( $this->update_registry_cache ) { |
|
370 | 1 | $this->registry->update_hyphenator_cache( $this->get_hyphenator_cache() ); |
|
|
|||
371 | 1 | $this->update_registry_cache = false; |
|
372 | } |
||
373 | |||
374 | 2 | return $this->registry; |
|
375 | } |
||
376 | |||
377 | /** |
||
378 | * Retrieves the HTML5 parser instance. |
||
379 | * |
||
380 | * @return \Masterminds\HTML5 |
||
381 | */ |
||
382 | 1 | public function get_html5_parser() { |
|
389 | } |
||
390 | |||
391 | /** |
||
392 | * Retrieves the hyphenator cache. |
||
393 | * |
||
394 | * @return Hyphenator\Cache |
||
395 | */ |
||
396 | 1 | public function get_hyphenator_cache() { |
|
397 | 1 | if ( ! isset( $this->hyphenator_cache ) ) { |
|
398 | 1 | $this->hyphenator_cache = new Hyphenator\Cache(); |
|
399 | } |
||
400 | |||
401 | 1 | return $this->hyphenator_cache; |
|
402 | } |
||
403 | |||
404 | /** |
||
405 | * Injects an existing Hyphenator\Cache (to facilitate persistent language caching). |
||
406 | * |
||
407 | * @param Hyphenator\Cache $cache A hyphenator cache instance. |
||
408 | */ |
||
409 | 2 | public function set_hyphenator_cache( Hyphenator\Cache $cache ) { |
|
415 | } |
||
416 | 2 | } |
|
417 | |||
418 | /** |
||
419 | * Retrieves the list of valid language plugins in the given directory. |
||
420 | * |
||
421 | * @param string $path The path in which to look for language plugin files. |
||
422 | * |
||
423 | * @return string[] An array in the form ( $language_code => $language_name ). |
||
424 | */ |
||
425 | 3 | private static function get_language_plugin_list( $path ) { |
|
426 | 3 | $languages = []; |
|
427 | |||
428 | // Try to open the given directory. |
||
429 | 3 | $handle = \opendir( $path ); |
|
430 | 2 | if ( false === $handle ) { |
|
431 | // Abort. |
||
432 | return $languages; // @codeCoverageIgnore |
||
433 | } |
||
434 | |||
435 | // Read all files in directory. |
||
436 | 2 | $file = \readdir( $handle ); |
|
437 | 2 | while ( $file ) { |
|
438 | // We only want the JSON files. |
||
439 | 2 | if ( '.json' === \substr( $file, -5 ) ) { |
|
440 | 2 | $file_content = \file_get_contents( $path . $file ); |
|
441 | 2 | if ( \preg_match( '/"language"\s*:\s*((".+")|(\'.+\'))\s*,/', $file_content, $matches ) ) { |
|
442 | 2 | $language_name = \substr( $matches[1], 1, -1 ); |
|
443 | 2 | $language_code = \substr( $file, 0, -5 ); |
|
444 | |||
445 | 2 | $languages[ $language_code ] = $language_name; |
|
446 | } |
||
447 | } |
||
448 | |||
449 | // Read next file. |
||
450 | 2 | $file = \readdir( $handle ); |
|
451 | } |
||
452 | 2 | \closedir( $handle ); |
|
453 | |||
454 | // Sort translated language names according to current locale. |
||
455 | 2 | \asort( $languages ); |
|
456 | |||
457 | 2 | return $languages; |
|
458 | } |
||
459 | |||
460 | /** |
||
461 | * Retrieves the list of valid hyphenation languages. |
||
462 | * |
||
463 | * Note that this method reads all the language files on disc, so you should |
||
464 | * cache the results if possible. |
||
465 | * |
||
466 | * @return string[] An array in the form of ( LANG_CODE => LANGUAGE ). |
||
467 | */ |
||
468 | 1 | public static function get_hyphenation_languages() { |
|
470 | } |
||
471 | |||
472 | /** |
||
473 | * Retrieves the list of valid diacritic replacement languages. |
||
474 | * |
||
475 | * Note that this method reads all the language files on disc, so you should |
||
476 | * cache the results if possible. |
||
477 | * |
||
478 | * @return string[] An array in the form of ( LANG_CODE => LANGUAGE ). |
||
479 | */ |
||
480 | 1 | public static function get_diacritic_languages() { |
|
482 | } |
||
483 | } |
||
484 |
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.