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 Jetpack_Perf_Optimize_Assets 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 Jetpack_Perf_Optimize_Assets, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
10 | class Jetpack_Perf_Optimize_Assets { |
||
11 | private static $__instance = null; |
||
12 | private $remove_remote_fonts = false; |
||
13 | private $inline_scripts_and_styles = false; |
||
14 | private $async_scripts = false; |
||
15 | private $defer_scripts = false; |
||
16 | private $defer_inline_scripts = false; |
||
17 | |||
18 | /** |
||
19 | * Singleton implementation |
||
20 | * |
||
21 | * @return object |
||
22 | */ |
||
23 | public static function instance() { |
||
30 | |||
31 | public function disable_for_request() { |
||
38 | |||
39 | /** |
||
40 | * TODO: detect if this is worth doing for wp-admin? |
||
|
|||
41 | */ |
||
42 | |||
43 | /** |
||
44 | * Registers actions |
||
45 | */ |
||
46 | private function __construct() { |
||
47 | $this->is_first_load = ! isset( $_COOKIE['jetpack_perf_loaded'] ); |
||
48 | $this->remove_remote_fonts = get_option( 'perf_remove_remote_fonts', true ); |
||
49 | $this->inline_always = get_option( 'perf_inline_on_every_request', false ); |
||
50 | $this->inline_scripts_and_styles = get_option( 'perf_inline_scripts_and_styles', true ) && ( $this->is_first_load || $this->inline_always ); |
||
51 | $this->async_scripts = get_option( 'perf_async_scripts', true ); |
||
52 | $this->defer_scripts = get_option( 'perf_defer_scripts', true ); |
||
53 | $this->defer_inline_scripts = get_option( 'perf_defer_inline_scripts', true ); |
||
54 | |||
55 | if ( $this->remove_remote_fonts ) { |
||
56 | add_filter( 'jetpack_perf_remove_script', array( $this, 'remove_external_font_scripts' ), 10, 3 ); |
||
57 | add_filter( 'jetpack_perf_remove_style', array( $this, 'remove_external_font_styles' ), 10, 3 ); |
||
58 | } |
||
59 | |||
60 | add_filter( 'script_loader_src', array( $this, 'filter_inline_scripts' ), - 100, 2 ); |
||
61 | add_filter( 'script_loader_tag', array( $this, 'print_inline_scripts' ), - 100, 3 ); |
||
62 | add_filter( 'style_loader_src', array( $this, 'filter_inline_styles' ), - 100, 2 ); |
||
63 | add_filter( 'style_loader_tag', array( $this, 'print_inline_styles' ), - 100, 4 ); |
||
64 | |||
65 | if ( $this->defer_inline_scripts ) { |
||
66 | add_filter( 'wp_head', array( $this, 'content_start' ), - 2000 ); |
||
67 | add_filter( 'wp_footer', array( $this, 'content_end' ), 2000 ); |
||
68 | } |
||
69 | |||
70 | add_action( 'init', array( $this, 'set_first_load_cookie' ) ); |
||
71 | |||
72 | /** |
||
73 | * Feature, theme and plugin-specific hacks |
||
74 | */ |
||
75 | // remove emoji detection - TODO a setting for this |
||
76 | add_action( 'init', array( $this, 'disable_emojis' ) ); |
||
77 | |||
78 | // inline/defer/async stuff for Jetpack |
||
79 | add_action( 'init', array( $this, 'optimize_jetpack' ) ); |
||
80 | } |
||
81 | |||
82 | function content_start() { |
||
83 | ob_start( array( $this, 'do_defer_inline_scripts' ) ); |
||
84 | } |
||
85 | |||
86 | function content_end() { |
||
87 | ob_end_flush(); |
||
88 | } |
||
89 | |||
90 | function do_defer_inline_scripts( $content ) { |
||
91 | preg_match_all( '#<script.*?>(.*?)<\/script>#is', $content, $matches, PREG_OFFSET_CAPTURE ); |
||
92 | $original_length = mb_strlen( $content ); |
||
93 | $offset = 0; |
||
94 | $counter = 0; |
||
95 | $rewrite = ""; |
||
96 | foreach ( $matches[1] as $value ) { |
||
97 | if ( ! empty( $value[0] ) && strpos( $value[0], "<![CDATA" ) === false ) { |
||
98 | $length = mb_strlen( $matches[0][ $counter ][0] ); |
||
99 | $script = base64_encode( $value[0] ); |
||
100 | $beginning = $matches[0][ $counter ][1]; |
||
101 | $rewrite .= mb_substr( $content, $offset, $beginning ); |
||
102 | $rewrite .= "<script defer src='data:text/javascript;base64,$script'></script>"; |
||
103 | $offset += $beginning + $length; |
||
104 | unset( $length, $script, $beginning ); |
||
105 | } |
||
106 | $counter ++; |
||
107 | } |
||
108 | $rewrite .= mb_substr( $content, $offset, $original_length ); |
||
109 | |||
110 | return $rewrite . "\n<!-- This is The End: All Inline Scripts Deferred -->"; |
||
111 | } |
||
112 | |||
113 | /** Disabling Emojis **/ |
||
114 | // improves page load performance |
||
115 | |||
116 | function disable_emojis() { |
||
117 | remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); |
||
118 | remove_action( 'admin_print_scripts', 'print_emoji_detection_script' ); |
||
119 | remove_action( 'embed_head', 'print_emoji_detection_script', 7 ); |
||
120 | |||
121 | remove_action( 'wp_print_styles', 'print_emoji_styles' ); |
||
122 | remove_action( 'admin_print_styles', 'print_emoji_styles' ); |
||
123 | |||
124 | remove_filter( 'the_content_feed', 'wp_staticize_emoji' ); |
||
125 | remove_filter( 'comment_text_rss', 'wp_staticize_emoji' ); |
||
126 | remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' ); |
||
127 | |||
128 | add_filter( 'tiny_mce_plugins', array( $this, 'disable_emojis_tinymce' ) ); |
||
129 | add_filter( 'wp_resource_hints', array( $this, 'disable_emojis_remove_dns_prefetch' ), 10, 2 ); |
||
130 | } |
||
131 | |||
132 | function optimize_jetpack() { |
||
133 | |||
134 | } |
||
135 | |||
136 | /** |
||
137 | * Filter function used to remove the tinymce emoji plugin. |
||
138 | * |
||
139 | * @param array $plugins |
||
140 | * @return array Difference betwen the two arrays |
||
141 | */ |
||
142 | function disable_emojis_tinymce( $plugins ) { |
||
143 | if ( is_array( $plugins ) ) { |
||
144 | return array_diff( $plugins, array( 'wpemoji' ) ); |
||
145 | } else { |
||
146 | return array(); |
||
147 | } |
||
148 | } |
||
149 | |||
150 | /** |
||
151 | * Remove emoji CDN hostname from DNS prefetching hints. |
||
152 | * |
||
153 | * @param array $urls URLs to print for resource hints. |
||
154 | * @param string $relation_type The relation type the URLs are printed for. |
||
155 | * @return array Difference betwen the two arrays. |
||
156 | */ |
||
157 | function disable_emojis_remove_dns_prefetch( $urls, $relation_type ) { |
||
158 | if ( 'dns-prefetch' == $relation_type ) { |
||
159 | /** This filter is documented in wp-includes/formatting.php */ |
||
160 | $emoji_svg_url = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/2/svg/' ); |
||
161 | |||
162 | $urls = array_diff( $urls, array( $emoji_svg_url ) ); |
||
163 | } |
||
164 | |||
165 | return $urls; |
||
166 | } |
||
167 | |||
168 | // by default we only inline scripts+styles on first page load for a given user |
||
169 | function set_first_load_cookie() { |
||
170 | View Code Duplication | if ( ! isset( $_COOKIE['jetpack_perf_loaded'] ) ) { |
|
171 | setcookie( 'jetpack_perf_loaded', '1', time() + YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); |
||
172 | } |
||
173 | } |
||
174 | |||
175 | /** FILTERS **/ |
||
176 | public function remove_external_font_scripts( $should_remove, $handle, $asset_url ) { |
||
177 | $font_script_url = 'http://use.typekit.com/'; |
||
178 | return strncmp( $asset_url, $font_script_url, strlen( $font_script_url ) ) === 0; |
||
179 | } |
||
180 | |||
181 | public function remove_external_font_styles( $should_remove, $handle, $asset_url ) { |
||
182 | $font_url = 'https://fonts.googleapis.com'; |
||
183 | return strncmp( $asset_url, $font_url, strlen( $font_url ) ) === 0; |
||
184 | } |
||
185 | |||
186 | /** SCRIPTS **/ |
||
187 | View Code Duplication | public function filter_inline_scripts( $src, $handle ) { |
|
188 | global $wp_scripts; |
||
189 | |||
190 | if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) { |
||
191 | return $src; |
||
192 | } |
||
193 | |||
194 | $script = $wp_scripts->registered[$handle]; |
||
195 | |||
196 | // reset src to empty - can't return empty string though because then it skips rendering the tag |
||
197 | if ( $this->should_inline_script( $script ) ) { |
||
198 | return '#'; |
||
199 | } |
||
200 | |||
201 | return $src; |
||
202 | } |
||
203 | |||
204 | public function print_inline_scripts( $tag, $handle, $src ) { |
||
205 | global $wp_scripts; |
||
206 | |||
207 | if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) { |
||
208 | return $tag; |
||
209 | } |
||
210 | |||
211 | $script = $wp_scripts->registered[$handle]; |
||
212 | |||
213 | if ( $this->should_remove_script( $script ) ) { |
||
214 | return ''; |
||
215 | } |
||
216 | |||
217 | if ( $this->should_inline_script( $script ) ) { |
||
218 | $tag = '<script type="text/javascript" src="data:text/javascript;base64,' . base64_encode( file_get_contents( $script->extra['jetpack-inline-file'] ) ) . '"></script>'; |
||
219 | } |
||
220 | |||
221 | if ( $this->should_async_script( $script ) ) { |
||
222 | $tag = preg_replace( '/<script /', '<script async ', $tag ); |
||
223 | } elseif ( $this->should_defer_script( $script ) ) { |
||
224 | $tag = preg_replace( '/<script /', '<script defer ', $tag ); |
||
225 | } |
||
226 | |||
227 | return $tag; |
||
228 | } |
||
229 | |||
230 | private function should_async_script( $script ) { |
||
231 | global $wp_scripts; |
||
232 | $should_async_script = empty( $script->deps ); |
||
233 | |||
234 | $skip_self = true; |
||
235 | // only make scripts async if nothing depends on them and they don't depend on anything else |
||
236 | foreach ( $wp_scripts->to_do as $other_script_handle ) { |
||
237 | if ( $skip_self ) { |
||
238 | $skip_self = false; |
||
239 | continue; |
||
240 | } |
||
241 | $other_script = $wp_scripts->registered[ $other_script_handle ]; |
||
242 | if ( array_intersect( array( $script->handle ), $other_script->deps ) ) { |
||
243 | $should_async_script = false; |
||
244 | break; |
||
245 | } |
||
246 | } |
||
247 | |||
248 | $script->extra['jetpack-defer'] = ! $should_async_script; |
||
249 | |||
250 | return $this->async_scripts && apply_filters( 'jetpack_perf_async_script', $should_async_script, $script->handle, $script->src ); |
||
251 | } |
||
252 | |||
253 | private function should_defer_script( $script ) { |
||
254 | $should_defer_script = isset( $script->extra['jetpack-defer'] ) && $script->extra['jetpack-defer']; |
||
255 | return $this->defer_scripts && apply_filters( 'jetpack_perf_defer_script', $should_defer_script, $script->handle, $script->src ); |
||
256 | } |
||
257 | |||
258 | private function should_remove_script( $script ) { |
||
259 | return $this->should_remove_asset( 'jetpack_perf_remove_script', $script ); |
||
260 | } |
||
261 | |||
262 | private function should_inline_script( $script ) { |
||
263 | return ( $this->inline_scripts_and_styles || $this->inline_always ) && $this->should_inline_asset( 'jetpack_perf_inline_script', $script ); |
||
264 | } |
||
265 | |||
266 | /** STYLES **/ |
||
267 | View Code Duplication | public function filter_inline_styles( $src, $handle ) { |
|
268 | global $wp_scripts; |
||
269 | |||
270 | if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) { |
||
271 | return $src; |
||
272 | } |
||
273 | |||
274 | $style = $wp_scripts->registered[$handle]; |
||
275 | |||
276 | if ( $this->should_inline_style( $style ) ) { |
||
277 | return '#'; |
||
278 | } |
||
279 | |||
280 | return $src; |
||
281 | } |
||
282 | |||
283 | public function print_inline_styles( $tag, $handle, $href, $media ) { |
||
284 | global $wp_styles; |
||
285 | |||
286 | if ( is_admin() || ! isset( $wp_styles->registered[$handle] ) ) { |
||
287 | return $tag; |
||
288 | } |
||
289 | |||
290 | $style = $wp_styles->registered[$handle]; |
||
291 | |||
292 | if ( $this->should_inline_style( $style ) ) { |
||
293 | return "<style type='text/css' media='$media'>" . file_get_contents( $style->extra['jetpack-inline-file'] ) . '</style>'; |
||
294 | } |
||
295 | |||
296 | if ( $this->should_remove_style( $style ) ) { |
||
297 | return ''; |
||
298 | } |
||
299 | |||
300 | return $tag; |
||
301 | } |
||
302 | |||
303 | private function should_inline_style( $style ) { |
||
304 | return ( $this->inline_scripts_and_styles || $this->inline_always ) && $this->should_inline_asset( 'jetpack_perf_inline_style', $style ); |
||
305 | } |
||
306 | |||
307 | private function should_remove_style( $style ) { |
||
308 | return $this->should_remove_asset( 'jetpack_perf_remove_style', $style ); |
||
309 | } |
||
310 | |||
311 | /** shared code **/ |
||
312 | |||
313 | private function should_inline_asset( $filter, $dependency ) { |
||
314 | // inline anything local, with a src starting with /, or starting with site_url |
||
315 | $site_url = site_url(); |
||
316 | |||
317 | $is_local_url = ( strncmp( $dependency->src, '/', 1 ) === 0 && strncmp( $dependency->src, '//', 2 ) !== 0 ) |
||
318 | || strpos( $dependency->src, $site_url ) === 0; |
||
319 | |||
320 | if ( $is_local_url && ! isset( $dependency->extra['jetpack-inline'] ) ) { |
||
321 | $dependency->extra['jetpack-inline'] = true; |
||
322 | |||
323 | $path = untrailingslashit( ABSPATH ) . str_replace( $site_url, '', $dependency->src ); |
||
324 | |||
325 | if ( ! file_exists( $path ) ) { |
||
326 | $path = str_replace('/', DIRECTORY_SEPARATOR, str_replace( $site_url, '', $dependency->src )); |
||
327 | |||
328 | $prefix = explode( DIRECTORY_SEPARATOR, untrailingslashit( WP_CONTENT_DIR ) ); |
||
329 | $prefix = array_slice( $prefix, 0, array_search( $path[1], $prefix ) - 1 ); |
||
330 | |||
331 | $path = implode( DIRECTORY_SEPARATOR, $prefix ) . $path; |
||
332 | } |
||
333 | |||
334 | $dependency->extra['jetpack-inline-file'] = $path; |
||
335 | } |
||
336 | |||
337 | $should_inline = isset( $dependency->extra['jetpack-inline'] ) && $dependency->extra['jetpack-inline']; |
||
338 | |||
339 | return apply_filters( $filter, $should_inline, $dependency->handle, $dependency->src ) && file_exists( $dependency->extra['jetpack-inline-file'] ); |
||
340 | } |
||
341 | |||
342 | private function should_remove_asset( $filter, $dependency ) { |
||
345 | |||
346 | /** |
||
347 | * if inline assets are enabled, renders inline |
||
348 | * TODO: enable this just for certain paths/patterns/filetypes |
||
349 | * This is actually currently unused |
||
350 | */ |
||
351 | public function register_inline_script( $handle, $file, $plugin_file, $deps = false, $ver = false, $in_footer = false ) { |
||
362 | |||
363 | /** |
||
364 | * if inline assets are enabled, renders inline |
||
365 | * TODO: enable this just for certain paths/patterns/filetypes |
||
366 | * This is actually currently unused |
||
367 | */ |
||
368 | public function register_inline_style( $handle, $file, $plugin_file, $deps = array(), $ver = false, $media = 'all' ) { |
||
377 | } |
||
378 |