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.