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: