Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like autoptimizeBase 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 autoptimizeBase, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
4 | abstract class autoptimizeBase { |
||
5 | protected $content = ''; |
||
6 | protected $tagWarning = false; |
||
7 | |||
8 | public function __construct($content) { |
||
11 | |||
12 | //Reads the page and collects tags |
||
13 | abstract public function read($justhead); |
||
14 | |||
15 | //Joins and optimizes collected things |
||
16 | abstract public function minify(); |
||
17 | |||
18 | //Caches the things |
||
19 | abstract public function cache(); |
||
20 | |||
21 | //Returns the content |
||
22 | abstract public function getcontent(); |
||
23 | |||
24 | //Converts an URL to a full path |
||
25 | protected function getpath($url) { |
||
26 | $url=apply_filters( 'autoptimize_filter_cssjs_alter_url', $url); |
||
27 | |||
28 | if (strpos($url,'%')!==false) { |
||
29 | $url=urldecode($url); |
||
30 | } |
||
31 | |||
32 | $siteHost=parse_url(AUTOPTIMIZE_WP_SITE_URL,PHP_URL_HOST); |
||
33 | $contentHost=parse_url(AUTOPTIMIZE_WP_ROOT_URL,PHP_URL_HOST); |
||
34 | |||
35 | // normalize |
||
36 | if (strpos($url,'//')===0) { |
||
37 | if (is_ssl()) { |
||
38 | $url = "https:".$url; |
||
39 | } else { |
||
40 | $url = "http:".$url; |
||
41 | } |
||
42 | } else if ((strpos($url,'//')===false) && (strpos($url,$siteHost)===false)) { |
||
43 | if (AUTOPTIMIZE_WP_SITE_URL === $siteHost) { |
||
44 | $url = AUTOPTIMIZE_WP_SITE_URL.$url; |
||
45 | } else { |
||
46 | $subdir_levels=substr_count(preg_replace("/https?:\/\//","",AUTOPTIMIZE_WP_SITE_URL),"/"); |
||
47 | $url = AUTOPTIMIZE_WP_SITE_URL.str_repeat("/..",$subdir_levels).$url; |
||
48 | } |
||
49 | } |
||
50 | |||
51 | if ($siteHost !== $contentHost) { |
||
52 | $url=str_replace(AUTOPTIMIZE_WP_CONTENT_URL,AUTOPTIMIZE_WP_SITE_URL.AUTOPTIMIZE_WP_CONTENT_NAME,$url); |
||
53 | } |
||
54 | |||
55 | // first check; hostname wp site should be hostname of url |
||
56 | $thisHost=@parse_url($url,PHP_URL_HOST); |
||
57 | if ($thisHost !== $siteHost) { |
||
58 | /* |
||
59 | * first try to get all domains from WPML (if available) |
||
60 | * then explicitely declare $this->cdn_url as OK as well |
||
61 | * then apply own filter autoptimize_filter_cssjs_multidomain takes an array of hostnames |
||
62 | * each item in that array will be considered part of the same WP multisite installation |
||
63 | */ |
||
64 | $multidomains = array(); |
||
65 | |||
66 | $multidomainsWPML = apply_filters('wpml_setting', array(), 'language_domains'); |
||
67 | if (!empty($multidomainsWPML)) { |
||
68 | $multidomains = array_map(array($this,"ao_getDomain"),$multidomainsWPML); |
||
69 | } |
||
70 | |||
71 | if (!empty($this->cdn_url)) { |
||
72 | $multidomains[]=parse_url($this->cdn_url,PHP_URL_HOST); |
||
|
|||
73 | } |
||
74 | |||
75 | $multidomains = apply_filters('autoptimize_filter_cssjs_multidomain', $multidomains); |
||
76 | |||
77 | if (!empty($multidomains)) { |
||
78 | if (in_array($thisHost,$multidomains)) { |
||
79 | $url=str_replace($thisHost, parse_url(AUTOPTIMIZE_WP_SITE_URL,PHP_URL_HOST), $url); |
||
80 | } else { |
||
81 | return false; |
||
82 | } |
||
83 | } else { |
||
84 | return false; |
||
85 | } |
||
86 | } |
||
87 | |||
88 | // try to remove "wp root url" from url while not minding http<>https |
||
89 | $tmp_ao_root = preg_replace('/https?:/','',AUTOPTIMIZE_WP_ROOT_URL); |
||
90 | if ($siteHost !== $contentHost) { |
||
91 | // as we replaced the content-domain with the site-domain, we should match against that |
||
92 | $tmp_ao_root = preg_replace('/https?:/','',AUTOPTIMIZE_WP_SITE_URL); |
||
93 | } |
||
94 | $tmp_url = preg_replace('/https?:/','',$url); |
||
95 | $path = str_replace($tmp_ao_root,'',$tmp_url); |
||
96 | |||
97 | // if path starts with :// or //, this is not a URL in the WP context and we have to assume we can't aggregate |
||
98 | if (preg_match('#^:?//#',$path)) { |
||
99 | /** External script/css (adsense, etc) */ |
||
100 | return false; |
||
101 | } |
||
102 | |||
103 | // prepend with WP_ROOT_DIR to have full path to file |
||
104 | $path = str_replace('//','/',WP_ROOT_DIR.$path); |
||
105 | |||
106 | // final check: does file exist and is it readable |
||
107 | if (file_exists($path) && is_file($path) && is_readable($path)) { |
||
108 | return $path; |
||
109 | } else { |
||
110 | return false; |
||
111 | } |
||
112 | } |
||
113 | |||
114 | // needed for WPML-filter |
||
115 | protected function ao_getDomain($in) { |
||
129 | |||
130 | |||
131 | // logger |
||
132 | protected function ao_logger($logmsg,$appendHTML=true) { |
||
140 | |||
141 | // hide everything between noptimize-comment tags |
||
142 | protected function hide_noptimize($noptimize_in) { |
||
157 | |||
158 | // unhide noptimize-tags |
||
159 | View Code Duplication | protected function restore_noptimize($noptimize_in) { |
|
174 | |||
175 | View Code Duplication | protected function hide_iehacks($iehacks_in) { |
|
190 | |||
191 | View Code Duplication | protected function restore_iehacks($iehacks_in) { |
|
206 | |||
207 | View Code Duplication | protected function hide_comments($comments_in) { |
|
222 | |||
223 | View Code Duplication | protected function restore_comments($comments_in) { |
|
238 | |||
239 | protected function url_replace_cdn( $url ) { |
||
269 | |||
270 | protected function inject_in_html($payload,$replaceTag) { |
||
288 | |||
289 | protected function isremovable($tag, $removables) { |
||
297 | |||
298 | // inject already minified code in optimized JS/CSS |
||
299 | View Code Duplication | protected function inject_minified($in) { |
|
300 | if ( strpos( $in, '%%INJECTLATER%%' ) !== false ) { |
||
301 | $out = preg_replace_callback( |
||
302 | '#\/\*\!%%INJECTLATER'.AUTOPTIMIZE_HASH.'%%(.*?)%%INJECTLATER%%\*\/#is', |
||
303 | create_function( |
||
304 | '$matches', |
||
305 | '$filepath=base64_decode(strtok($matches[1],"|")); |
||
306 | $filecontent=file_get_contents($filepath); |
||
307 | |||
308 | // remove BOM |
||
309 | $filecontent = preg_replace("#\x{EF}\x{BB}\x{BF}#","",$filecontent); |
||
310 | |||
311 | // remove comments and blank lines |
||
312 | if (substr($filepath,-3,3)===".js") { |
||
313 | $filecontent=preg_replace("#^\s*\/\/.*$#Um","",$filecontent); |
||
314 | } |
||
315 | |||
316 | $filecontent=preg_replace("#^\s*\/\*[^!].*\*\/\s?#Um","",$filecontent); |
||
317 | $filecontent=preg_replace("#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#", "\n", $filecontent); |
||
318 | |||
319 | // differentiate between JS, CSS and other files |
||
320 | if (substr($filepath,-3,3)===".js") { |
||
321 | if ((substr($filecontent,-1,1)!==";")&&(substr($filecontent,-1,1)!=="}")) { |
||
322 | $filecontent.=";"; |
||
323 | } |
||
324 | |||
325 | if (get_option("autoptimize_js_trycatch")==="on") { |
||
326 | $filecontent="try{".$filecontent."}catch(e){}"; |
||
327 | } |
||
328 | } else if ((substr($filepath,-4,4)===".css")) { |
||
329 | $filecontent=autoptimizeStyles::fixurls($filepath,$filecontent); |
||
330 | } else { |
||
331 | $filecontent=""; |
||
332 | } |
||
333 | |||
334 | // return |
||
335 | return "\n".$filecontent;' |
||
336 | ), |
||
337 | $in |
||
338 | ); |
||
339 | } else { |
||
340 | $out = $in; |
||
341 | } |
||
342 | return $out; |
||
343 | } |
||
344 | |||
345 | protected function minify_single($pathIn) { |
||
346 | // determine JS or CSS and set var (also mimetype), return false if neither |
||
347 | if ( $this->str_ends_in($pathIn,".js") === true ) { |
||
348 | $codeType="js"; |
||
349 | $codeMime="text/javascript"; |
||
350 | } else if ( $this->str_ends_in($pathIn,".css") === true ) { |
||
351 | $codeType="css"; |
||
352 | $codeMime="text/css"; |
||
353 | } else { |
||
354 | return false; |
||
355 | } |
||
356 | |||
357 | // if min.js or min.css return false |
||
358 | if (( $this->str_ends_in($pathIn,"-min.".$codeType) === true ) || ( $this->str_ends_in($pathIn,".min.".$codeType) === true ) || ( $this->str_ends_in($pathIn,"js/jquery/jquery.js") === true ) ) { |
||
359 | return false; |
||
360 | } |
||
361 | |||
362 | // read file, return false if empty |
||
363 | $_toMinify = file_get_contents($pathIn); |
||
364 | if ( empty($_toMinify) ) return false; |
||
365 | |||
366 | // check cache |
||
367 | $_md5hash = "single_".md5($_toMinify); |
||
368 | $_cache = new autoptimizeCache($_md5hash,$codeType); |
||
369 | if ($_cache->check() ) { |
||
370 | $_CachedMinifiedUrl = AUTOPTIMIZE_CACHE_URL.$_cache->getname(); |
||
371 | } else { |
||
372 | // if not in cache first minify |
||
373 | $_Minified = $_toMinify; |
||
374 | if ($codeType === "js") { |
||
375 | if (class_exists('JSMin') && apply_filters( 'autoptimize_js_do_minify' , true)) { |
||
376 | if (@is_callable(array("JSMin","minify"))) { |
||
377 | $tmp_code = trim(JSMin::minify($_toMinify)); |
||
378 | } |
||
379 | } |
||
380 | View Code Duplication | } else if ($codeType === "css") { |
|
381 | if (class_exists('Minify_CSS_Compressor')) { |
||
382 | $tmp_code = trim(Minify_CSS_Compressor::process($_toMinify)); |
||
383 | } else if(class_exists('CSSmin')) { |
||
384 | $cssmin = new CSSmin(); |
||
385 | if (method_exists($cssmin,"run")) { |
||
386 | $tmp_code = trim($cssmin->run($_toMinify)); |
||
387 | } elseif (@is_callable(array($cssmin,"minify"))) { |
||
388 | $tmp_code = trim(CssMin::minify($_toMinify)); |
||
389 | } |
||
390 | } |
||
391 | } |
||
392 | if (!empty($tmp_code)) { |
||
393 | $_Minified = $tmp_code; |
||
394 | unset($tmp_code); |
||
395 | } |
||
396 | // and then cache |
||
397 | $_cache->cache($_Minified,$codeMime); |
||
398 | $_CachedMinifiedUrl = AUTOPTIMIZE_CACHE_URL.$_cache->getname(); |
||
399 | } |
||
400 | unset($_cache); |
||
401 | |||
402 | // if CDN, then CDN |
||
403 | $_CachedMinifiedUrl = $this->url_replace_cdn($_CachedMinifiedUrl); |
||
404 | |||
405 | return $_CachedMinifiedUrl; |
||
406 | } |
||
407 | |||
408 | protected function str_ends_in($haystack,$needle) { |
||
418 | |||
419 | /** |
||
420 | * Specialized method to create the INJECTLATER marker. |
||
421 | * These are somewhat "special", in the sense that they're additionally wrapped |
||
422 | * within an "exclamation mark style" comment, so that they're not stripped out by minifiers. |
||
423 | * They also currently contain the hash of the file's contents too (unlike other markers). |
||
424 | * |
||
425 | * @param string $filepath |
||
426 | * @param string $hash |
||
427 | * @return string |
||
428 | */ |
||
429 | public static function build_injectlater_marker($filepath, $hash) |
||
434 | |||
435 | /** |
||
436 | * Creates and returns a `%%`-style named marker which holds |
||
437 | * the base64 encoded `$data`. |
||
438 | * If `$hash` is provided, it's appended to the base64 encoded string |
||
439 | * using `|` as the separator (in order to support building the |
||
440 | * somewhat special/different INJECTLATER marker). |
||
441 | * |
||
442 | * @param string $name Marker name |
||
443 | * @param string $data Marker data which will be encoded in base64 |
||
444 | * @param string|null $hash Optional. |
||
445 | * |
||
446 | * @return string |
||
447 | */ |
||
448 | public static function build_marker($name, $data, $hash = null) |
||
461 | |||
462 | /** |
||
463 | * Returns true if the string is a valid regex. |
||
464 | * |
||
465 | * @param string $string |
||
466 | * |
||
467 | * @return bool |
||
468 | */ |
||
469 | protected function str_is_valid_regex($string) |
||
476 | |||
477 | /** |
||
478 | * Searches for `$search` in `$content` (using either `preg_match()` |
||
479 | * or `strpos()`, depending on whether `$search` is a valid regex pattern or not). |
||
480 | * If something is found, it replaces `$content` using `$re_replace_pattern`, |
||
481 | * effectively creating our named markers (`%%{$marker}%%`. |
||
482 | * These are then at some point replaced back to their actual/original/modified |
||
483 | * contents using `autoptimizeBase::restore_marked_content()`. |
||
484 | * |
||
485 | * @param string $marker Marker name (without percent characters) |
||
486 | * @param string $search A string or full blown regex pattern to search for in $content. Uses `strpos()` or `preg_match()` |
||
487 | * @param string $re_replace_pattern Regex pattern to use when replacing contents |
||
488 | * @param string $content Content to work on |
||
489 | * |
||
490 | * @return string |
||
491 | */ |
||
492 | protected function replace_contents_with_marker_if_exists($marker, $search, $re_replace_pattern, $content) |
||
513 | |||
514 | /** |
||
515 | * Complements `autoptimizeBase::replace_contents_with_marker_if_exists()` |
||
516 | * |
||
517 | * @param string $marker |
||
518 | * @param string $content |
||
519 | * @return string |
||
520 | */ |
||
521 | protected function restore_marked_content($marker, $content) |
||
535 | } |
||
536 |
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: