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_VideoPress 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_VideoPress, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
6 | class Jetpack_VideoPress { |
||
7 | public $module = 'videopress'; |
||
8 | public $option_name = 'videopress'; |
||
9 | public $version = 4; |
||
10 | |||
11 | /** |
||
12 | * Singleton |
||
13 | */ |
||
14 | public static function init() { |
||
15 | static $instance = false; |
||
16 | |||
17 | if ( ! $instance ) |
||
18 | $instance = new Jetpack_VideoPress; |
||
19 | |||
20 | return $instance; |
||
21 | } |
||
22 | |||
23 | function __construct() { |
||
24 | $this->version = time(); // <s>ghost</s> cache busters! |
||
25 | add_action( 'init', array( $this, 'on_init' ) ); |
||
26 | add_action( 'jetpack_activate_module_videopress', array( $this, 'jetpack_module_activated' ) ); |
||
27 | add_action( 'jetpack_deactivate_module_videopress', array( $this, 'jetpack_module_deactivated' ) ); |
||
28 | |||
29 | require_once( dirname( __FILE__ ) . '/shortcode.php' ); |
||
30 | } |
||
31 | |||
32 | /** |
||
33 | * Fires on init since is_connection_owner should wait until the user is initialized by $wp->init(); |
||
34 | */ |
||
35 | function on_init() { |
||
36 | $options = $this->get_options(); |
||
37 | |||
38 | // Only the connection owner can configure this module. |
||
39 | if ( $this->is_connection_owner() ) { |
||
40 | Jetpack::enable_module_configurable( $this->module ); |
||
41 | Jetpack::module_configuration_load( $this->module, array( $this, 'jetpack_configuration_load' ) ); |
||
42 | Jetpack::module_configuration_screen( $this->module, array( $this, 'jetpack_configuration_screen' ) ); |
||
43 | } |
||
44 | |||
45 | // Only if the current user can manage the VideoPress library and one has been connected. |
||
46 | if ( $this->can( 'read_videos' ) && $options['blog_id'] ) { |
||
47 | add_action( 'wp_enqueue_media', array( $this, 'enqueue_admin_scripts' ) ); |
||
48 | add_action( 'print_media_templates', array( $this, 'print_media_templates' ) ); |
||
49 | |||
50 | // Load these at priority -1 so they're fired before Core's are. |
||
51 | add_action( 'wp_ajax_query-attachments', array( $this, 'wp_ajax_query_attachments' ), -1 ); |
||
52 | add_action( 'wp_ajax_save-attachment', array( $this, 'wp_ajax_save_attachment' ), -1 ); |
||
53 | add_action( 'wp_ajax_save-attachment-compat', array( $this, 'wp_ajax_save_attachment' ), -1 ); |
||
54 | add_action( 'wp_ajax_delete-post', array( $this, 'wp_ajax_delete_post' ), -1 ); |
||
55 | |||
56 | add_action( 'admin_menu', array( $this, 'admin_menu' ) ); |
||
57 | } |
||
58 | |||
59 | if ( $this->can( 'upload_videos' ) && $options['blog_id'] ) { |
||
60 | add_action( 'wp_ajax_videopress-get-upload-token', array( $this, 'wp_ajax_videopress_get_upload_token' ) ); |
||
61 | } |
||
62 | |||
63 | add_filter( 'videopress_shortcode_options', array( $this, 'videopress_shortcode_options' ) ); |
||
64 | } |
||
65 | |||
66 | function wp_ajax_videopress_get_upload_token() { |
||
67 | if ( ! $this->can( 'upload_videos' ) ) |
||
68 | return wp_send_json_error(); |
||
69 | |||
70 | $result = $this->query( 'jetpack.vpGetUploadToken' ); |
||
71 | if ( is_wp_error( $result ) ) |
||
72 | return wp_send_json_error( array( 'message' => __( 'Could not obtain a VideoPress upload token. Please try again later.', 'jetpack' ) ) ); |
||
73 | |||
74 | $response = $result; |
||
75 | if ( empty( $response['videopress_blog_id'] ) || empty( $response['videopress_token'] ) || empty( $response[ 'videopress_action_url' ] ) ) |
||
76 | return wp_send_json_error( array( 'message' => __( 'Could not obtain a VideoPress upload token. Please try again later.', 'jetpack' ) ) ); |
||
77 | |||
78 | return wp_send_json_success( $response ); |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * Get VideoPress options |
||
83 | */ |
||
84 | function get_options() { |
||
85 | $defaults = array( |
||
86 | 'blogs' => array(), |
||
87 | 'blog_id' => 0, |
||
88 | 'access' => '', |
||
89 | 'allow-upload' => false, |
||
90 | 'freedom' => false, |
||
91 | 'hd' => false, |
||
92 | 'meta' => array( |
||
93 | 'max_upload_size' => 0, |
||
94 | ), |
||
95 | ); |
||
96 | |||
97 | $options = Jetpack_Options::get_option( $this->option_name, array() ); |
||
98 | |||
99 | // If options have not been saved yet, check for older VideoPress plugin options. |
||
100 | if ( empty( $options ) ) { |
||
101 | $options['freedom'] = (bool) get_option( 'video_player_freedom', false ); |
||
102 | $options['hd'] = (bool) get_option( 'video_player_high_quality', false ); |
||
103 | } |
||
104 | |||
105 | $options = array_merge( $defaults, $options ); |
||
106 | return $options; |
||
107 | } |
||
108 | |||
109 | /** |
||
110 | * Update VideoPress options |
||
111 | */ |
||
112 | function update_options( $options ) { |
||
115 | |||
116 | /** |
||
117 | * Runs when the VideoPress module is activated. |
||
118 | */ |
||
119 | function jetpack_module_activated() { |
||
120 | if ( ! $this->is_connection_owner() ) |
||
121 | return; |
||
122 | |||
123 | $options = $this->get_options(); |
||
124 | |||
125 | // Ask WordPress.com for a list of VideoPress blogs |
||
126 | $result = $this->query( 'jetpack.vpGetBlogs' ); |
||
127 | if ( ! is_wp_error( $result ) ) |
||
128 | $options['blogs'] = $result; |
||
129 | |||
130 | // If there's at least one available blog, let's use it. |
||
131 | if ( is_array( $options['blogs'] ) && count( $options['blogs'] ) > 0 ) |
||
132 | $options['blog_id'] = $options['blogs'][0]['blog_id']; |
||
133 | |||
134 | $this->update_options( $options ); |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * Runs when the VideoPress module is deactivated. |
||
139 | */ |
||
140 | function jetpack_module_deactivated() { |
||
143 | |||
144 | /** |
||
145 | * Remote Query |
||
146 | * |
||
147 | * Performs a remote XML-RPC query using Jetpack's IXR Client. And also |
||
148 | * appends some useful stuff about this setup to the query. |
||
149 | * |
||
150 | * @return the Jetpack_IXR_Client object after querying. |
||
151 | */ |
||
152 | function query( $method, $args = null ) { |
||
153 | $options = $this->get_options(); |
||
154 | Jetpack::load_xml_rpc_client(); |
||
155 | $xml = new Jetpack_IXR_Client( array( |
||
156 | 'user_id' => JETPACK_MASTER_USER, // All requests are on behalf of the connection owner. |
||
157 | ) ); |
||
158 | |||
159 | $params = array( |
||
160 | 'args' => $args, |
||
161 | 'video_blog_id' => $options['blog_id'], |
||
162 | 'caps' => array(), |
||
163 | ); |
||
164 | |||
165 | // Let Jetpack know about our local caps. |
||
166 | View Code Duplication | foreach ( array( 'read_videos', 'edit_videos', 'delete_videos', 'upload_videos' ) as $cap ) |
|
167 | if ( $this->can( $cap ) ) |
||
168 | $params['caps'][] = $cap; |
||
169 | |||
170 | $xml->query( $method, $params ); |
||
171 | |||
172 | if ( $xml->isError() ) |
||
173 | return new WP_Error( 'xml_rpc_error', 'An XML-RPC error has occurred.' ); |
||
174 | |||
175 | $response = $xml->getResponse(); |
||
176 | |||
177 | // If there's any metadata with the response, save it for future use. |
||
178 | if ( is_array( $response ) && isset( $response['meta'] ) ) { |
||
179 | $options = $this->get_options(); |
||
180 | if ( $response['meta'] !== $options['meta'] ) { |
||
181 | $options['meta'] = array_merge( $options['meta'], $response['meta'] ); |
||
182 | $this->update_options( $options ); |
||
183 | } |
||
184 | } |
||
185 | |||
186 | if ( is_array( $response ) && isset( $response['result'] ) ) |
||
187 | return $response['result']; |
||
188 | |||
189 | return $response; |
||
190 | } |
||
191 | |||
192 | /** |
||
193 | * Runs before the VideoPress Configuration screen loads, useful |
||
194 | * to update options and yield errors. |
||
195 | */ |
||
196 | function jetpack_configuration_load() { |
||
197 | $this->enqueue_admin_scripts(); |
||
198 | |||
199 | /** |
||
200 | * Save configuration |
||
201 | */ |
||
202 | if ( ! empty( $_POST['action'] ) && $_POST['action'] == 'videopress-save' ) { |
||
203 | check_admin_referer( 'videopress-settings' ); |
||
204 | $options = $this->get_options(); |
||
205 | |||
206 | if ( isset( $_POST['blog_id'] ) && in_array( $_POST['blog_id'], wp_list_pluck( $options['blogs'], 'blog_id' ) ) ) |
||
207 | $options['blog_id'] = $_POST['blog_id']; |
||
208 | |||
209 | // Allow the None setting too. |
||
210 | if ( isset( $_POST['blog_id'] ) && $_POST['blog_id'] == 0 ) |
||
211 | $options['blog_id'] = 0; |
||
212 | |||
213 | /** |
||
214 | * @see $this->can() |
||
215 | */ |
||
216 | if ( isset( $_POST['videopress-access'] ) && in_array( $_POST['videopress-access'], array( '', 'read', 'edit', 'delete' ) ) ) |
||
217 | $options['access'] = $_POST['videopress-access']; |
||
218 | |||
219 | $options['freedom'] = isset( $_POST['videopress-freedom'] ); |
||
220 | $options['hd'] = isset( $_POST['videopress-hd'] ); |
||
221 | |||
222 | // Allow upload only if some level of access has been granted, and uploads were allowed. |
||
223 | $options['allow-upload'] = false; |
||
224 | if ( ! empty( $options['access'] ) && isset( $_POST['videopress-upload'] ) ) |
||
225 | $options['allow-upload'] = true; |
||
226 | |||
227 | $this->update_options( $options ); |
||
228 | Jetpack::state( 'message', 'module_configured' ); |
||
229 | wp_safe_redirect( Jetpack::module_configuration_url( $this->module ) ); |
||
230 | } |
||
231 | |||
232 | /** |
||
233 | * Refresh the list of available WordPress.com blogs |
||
234 | */ |
||
235 | if ( ! empty( $_GET['videopress'] ) && $_GET['videopress'] == 'refresh-blogs' ) { |
||
236 | check_admin_referer( 'videopress-settings' ); |
||
237 | $options = $this->get_options(); |
||
238 | |||
239 | $result = $this->query( 'jetpack.vpGetBlogs' ); |
||
240 | if ( ! is_wp_error( $result ) ) { |
||
241 | $options['blogs'] = $result; |
||
242 | $this->update_options( $options ); |
||
243 | } |
||
244 | |||
245 | wp_safe_redirect( Jetpack::module_configuration_url( $this->module ) ); |
||
246 | } |
||
247 | } |
||
248 | |||
249 | /** |
||
250 | * Renders the VideoPress Configuration screen in Jetpack. |
||
251 | */ |
||
252 | function jetpack_configuration_screen() { |
||
253 | $options = $this->get_options(); |
||
254 | $refresh_url = wp_nonce_url( add_query_arg( 'videopress', 'refresh-blogs' ), 'videopress-settings' ); |
||
255 | ?> |
||
256 | <div class="narrow"> |
||
257 | <form method="post" id="videopress-settings"> |
||
258 | <input type="hidden" name="action" value="videopress-save" /> |
||
259 | <?php wp_nonce_field( 'videopress-settings' ); ?> |
||
260 | |||
261 | <table id="menu" class="form-table"> |
||
262 | <tr> |
||
263 | <th scope="row" colspan="2"> |
||
264 | <p><?php _e( 'Please note that the VideoPress module requires a WordPress.com account with an active <a href="http://store.wordpress.com/premium-upgrades/videopress/" target="_blank">VideoPress subscription</a>.', 'jetpack' ); ?></p> |
||
265 | </th> |
||
266 | </tr> |
||
267 | <tr> |
||
268 | <th scope="row"> |
||
269 | <label><?php _e( 'Connected WordPress.com Blog', 'jetpack' ); ?></label> |
||
270 | </th> |
||
271 | <td> |
||
272 | <select name="blog_id"> |
||
273 | <option value="0" <?php selected( $options['blog_id'], 0 ); ?>> <?php esc_html_e( 'None', 'jetpack' ); ?></option> |
||
274 | <?php foreach ( $options['blogs'] as $blog ) : ?> |
||
|
|||
275 | <option value="<?php echo absint( $blog['blog_id'] ); ?>" <?php selected( $options['blog_id'], $blog['blog_id'] ); ?>><?php echo esc_html( $blog['name'] ); ?> (<?php echo esc_html( $blog['domain'] ); ?>)</option> |
||
276 | <?php endforeach; ?> |
||
277 | </select> |
||
278 | <p class="description"><?php _e( 'Only videos from the selected blog will be available in your media library.', 'jetpack' ); ?> |
||
279 | <?php printf( __( '<a href="%s">Click here</a> to refresh this list.', 'jetpack' ), esc_url( $refresh_url ) ); ?> |
||
280 | </p> |
||
281 | </td> |
||
282 | </tr> |
||
283 | <tr> |
||
284 | <th scope="row"> |
||
285 | <label><?php _e( 'Video Library Access', 'jetpack' ); ?></label> |
||
286 | </th> |
||
287 | <td> |
||
288 | <label><input type="radio" name="videopress-access" value="" <?php checked( '', $options['access'] ); ?> /> |
||
289 | <?php _e( 'Do not allow other users to access my VideoPress library', 'jetpack' ); ?></label><br/> |
||
290 | <label><input type="radio" name="videopress-access" value="read" <?php checked( 'read', $options['access'] ); ?> /> |
||
291 | <?php _e( 'Allow users to access my videos', 'jetpack' ); ?></label><br/> |
||
292 | <label><input type="radio" name="videopress-access" value="edit" <?php checked( 'edit', $options['access'] ); ?> /> |
||
293 | <?php _e( 'Allow users to access and edit my videos', 'jetpack' ); ?></label><br/> |
||
294 | <label><input type="radio" name="videopress-access" value="delete" <?php checked( 'delete', $options['access'] ); ?> /> |
||
295 | <?php _e( 'Allow users to access, edit, and delete my videos', 'jetpack' ); ?></label><br/><br /> |
||
296 | |||
297 | <label><input type="checkbox" name="videopress-upload" value="1" <?php checked( $options['allow-upload'] ); ?> /> |
||
298 | <?php _e( 'Allow users to upload videos', 'jetpack' ); ?></label><br /> |
||
299 | </td> |
||
300 | </tr> |
||
301 | <tr> |
||
302 | <th scope="row"> |
||
303 | <label for="videopress-freedom"><?php _e( 'Free formats', 'jetpack' ); ?></label> |
||
304 | </th> |
||
305 | <td> |
||
306 | <label><input type="checkbox" name="videopress-freedom" id="videopress-freedom" <?php checked( $options['freedom'] ); ?> /> |
||
307 | <?php _e( 'Only display videos in free software formats', 'jetpack' ); ?></label> |
||
308 | <p class="description"><?php _e( 'Ogg file container with Theora video and Vorbis audio. Note that some browsers are unable to play free software video formats, including Internet Explorer and Safari.', 'jetpack' ); ?></p> |
||
309 | </td> |
||
310 | </tr> |
||
311 | <tr> |
||
312 | <th scope="row"> |
||
313 | <label for="videopress-hd"><?php _e( 'Default quality', 'jetpack' ); ?></label> |
||
314 | </th> |
||
315 | <td> |
||
316 | <label><input type="checkbox" name="videopress-hd" id="videopress-hd" <?php checked( $options['hd'] ); ?> /> |
||
317 | <?php _e( 'Display higher quality video by default.', 'jetpack' ); ?></label> |
||
318 | <p class="description"><?php _e( 'This setting may be overridden for individual videos.', 'jetpack' ); ?></p> |
||
319 | </td> |
||
320 | </tr> |
||
321 | </table> |
||
322 | |||
323 | <?php submit_button(); ?> |
||
324 | </form> |
||
325 | </div> |
||
326 | <?php |
||
327 | } |
||
328 | |||
329 | function admin_menu() { |
||
332 | |||
333 | function admin_menu_library() { |
||
334 | wp_enqueue_media(); |
||
335 | $this->enqueue_admin_scripts(); |
||
336 | ?> |
||
337 | <div class="wrap" style="max-width: 600px;"> |
||
338 | <?php screen_icon(); ?> |
||
339 | <h2><?php _e( 'VideoPress Library', 'jetpack' ); ?></h2> |
||
340 | <p><?php _e( 'Use the button below to browse your VideoPress Library. Note that you can also browse your VideoPress Library while editing a post or page by using the <strong>Add Media</strong> button in the post editor.', 'jetpack' ); ?></p> |
||
341 | <p class="hide-if-no-js"><a href="#" id="videopress-browse" class="button"><?php _e( 'Browse Your VideoPress Library', 'jetpack' ); ?></a></p> |
||
342 | <p class="hide-if-js description"><?php _e( 'Please enable JavaScript support in your browser to use VideoPress.', 'jetpack' ); ?></p> |
||
343 | </div> |
||
344 | <?php |
||
345 | } |
||
346 | |||
347 | /** |
||
348 | * A can of coke |
||
349 | * |
||
350 | * Similar to current_user_can, but internal to VideoPress. Returns |
||
351 | * true if the given VideoPress capability is allowed by the given user. |
||
352 | */ |
||
353 | function can( $cap, $user_id = false ) { |
||
354 | if ( ! $user_id ) |
||
355 | $user_id = get_current_user_id(); |
||
356 | |||
357 | // Connection owners are allowed to do all the things. |
||
358 | if ( $this->is_connection_owner( $user_id ) ) |
||
359 | return true; |
||
360 | |||
361 | /** |
||
362 | * The access setting can be set by the connection owner, to allow sets |
||
363 | * of operations to other site users. Each access value corresponds to |
||
364 | * an array of things they can do. |
||
365 | */ |
||
366 | |||
367 | $options = $this->get_options(); |
||
368 | $map = array( |
||
369 | 'read' => array( 'read_videos' ), |
||
370 | 'edit' => array( 'read_videos', 'edit_videos' ), |
||
371 | 'delete' => array( 'read_videos', 'edit_videos', 'delete_videos' ), |
||
372 | ); |
||
373 | |||
374 | if ( ! array_key_exists( $options['access'], $map ) ) |
||
375 | return false; |
||
376 | |||
377 | if ( ! in_array( $cap, $map[ $options['access'] ] ) && 'upload_videos' != $cap ) |
||
378 | return false; |
||
379 | |||
380 | // Additional and intrenal caps checks |
||
381 | |||
382 | if ( ! user_can( $user_id, 'upload_files' ) ) |
||
383 | return false; |
||
384 | |||
385 | if ( 'edit_videos' == $cap && ! user_can( $user_id, 'edit_others_posts' ) ) |
||
386 | return false; |
||
387 | |||
388 | if ( 'delete_videos' == $cap && ! user_can( $user_id, 'delete_others_posts' ) ) |
||
389 | return false; |
||
390 | |||
391 | if ( 'upload_videos' == $cap && ! $options['allow-upload'] ) |
||
392 | return false; |
||
393 | |||
394 | return true; |
||
395 | } |
||
396 | |||
397 | /** |
||
398 | * Returns true if the provided user is the Jetpack connection owner. |
||
399 | */ |
||
400 | function is_connection_owner( $user_id = false ) { |
||
401 | if ( ! $user_id ) |
||
402 | $user_id = get_current_user_id(); |
||
403 | |||
404 | $user_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER ); |
||
405 | return $user_token && is_object( $user_token ) && isset( $user_token->external_user_id ) && $user_id === $user_token->external_user_id; |
||
406 | } |
||
407 | |||
408 | /** |
||
409 | * Our custom AJAX callback for the query-attachments action |
||
410 | * used in the media modal. By-passed if not for VideoPress. |
||
411 | */ |
||
412 | function wp_ajax_query_attachments() { |
||
462 | |||
463 | /** |
||
464 | * Sanitize user-provided WP_Query arguments |
||
465 | * |
||
466 | * These might be sent to the VideoPress server, for a remote WP_Query |
||
467 | * call so let's make sure they're sanitized and safe to send. |
||
468 | */ |
||
469 | function sanitize_wp_query_args( $args ) { |
||
470 | $args = shortcode_atts( array( |
||
471 | 'posts_per_page' => 40, |
||
472 | 'orderby' => 'date', |
||
473 | 'order' => 'desc', |
||
490 | |||
491 | /** |
||
492 | * Custom AJAX callback for the save-attachment action. If the request was |
||
493 | * not for a VideoPress object, core's fallback action will kick in. |
||
494 | */ |
||
495 | function wp_ajax_save_attachment() { |
||
543 | |||
544 | /** |
||
545 | * Custom AJAX callback for the delete-post action, only for VideoPress objects. |
||
546 | */ |
||
547 | function wp_ajax_delete_post() { |
||
573 | |||
574 | /** |
||
575 | * Register VideoPress admin scripts. |
||
576 | */ |
||
577 | function enqueue_admin_scripts() { |
||
611 | |||
612 | /** |
||
613 | * Get an array of available ratings. Keys are options, values are labels. |
||
614 | */ |
||
615 | function get_available_ratings() { |
||
623 | |||
624 | /** |
||
625 | * Additional VideoPress media templates. |
||
626 | */ |
||
627 | function print_media_templates() { |
||
721 | |||
722 | /** |
||
723 | * Filters the VideoPress shortcode options, makes sure that |
||
724 | * the settings set in Jetpack's VideoPress module are applied. |
||
725 | */ |
||
726 | function videopress_shortcode_options( $options ) { |
||
736 | } |
||
737 | |||
740 |
There are different options of fixing this problem.
If you want to be on the safe side, you can add an additional type-check:
If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:
Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.