Completed
Push — upgrade/wordpress-components ( f88191...1d8633 )
by
unknown
30:51 queued 24:01
created

Jetpack_Podcast_Helper   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 186
Duplicated Lines 6.99 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 13
loc 186
rs 10
c 0
b 0
f 0
wmc 24
lcom 1
cbo 1

7 Methods

Rating   Name   Duplication   Size   Complexity  
B get_player_data() 0 46 8
A get_track_list() 0 8 1
A get_plain_text() 0 15 2
A load_feed() 13 13 3
A setup_tracks_callback() 0 35 5
A get_audio_enclosure() 0 9 3
A format_track_duration() 0 5 2

How to fix   Duplicated Code   

Duplicated Code

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:

1
<?php
2
/**
3
 * Helper to massage Podcast data to be used in the Podcast block.
4
 *
5
 * @package jetpack
6
 */
7
8
/**
9
 * Class Jetpack_Podcast_Helper
10
 */
11
class Jetpack_Podcast_Helper {
12
	/**
13
	 * Gets podcast data formatted to be used by the Podcast Player block in both server-side
14
	 * block rendering and in API `WPCOM_REST_API_V2_Endpoint_Podcast_Player`.
15
	 *
16
	 * The result is cached for one hour.
17
	 *
18
	 * @param string $feed     The RSS feed to load and list tracks for.
19
	 * @return array|WP_Error The player data or a error object.
20
	 */
21
	public static function get_player_data( $feed ) {
22
		$feed = esc_url_raw( $feed );
23
24
		// Try loading data from the cache.
25
		$transient_key = 'jetpack_podcast_' . md5( $feed );
26
		$player_data   = get_transient( $transient_key );
27
28
		// Fetch data if we don't have any cached.
29
		if ( false === $player_data || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) {
30
			// Load feed.
31
			$rss = self::load_feed( $feed );
32
33
			if ( is_wp_error( $rss ) ) {
34
				return $rss;
35
			}
36
37
			// Get tracks.
38
			$tracks = self::get_track_list( $rss );
0 ignored issues
show
Bug introduced by
It seems like $rss defined by self::load_feed($feed) on line 31 can also be of type object<WP_Error>; however, Jetpack_Podcast_Helper::get_track_list() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
39
40
			if ( empty( $tracks ) ) {
41
				return new WP_Error( 'no_tracks', __( 'Your Podcast couldn\'t be embedded as it doesn\'t contain any tracks. Please double check your URL.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_tracks'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
42
			}
43
44
			// Get podcast meta.
45
			$title = $rss->get_title();
0 ignored issues
show
Bug introduced by
The method get_title() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
46
			$title = self::get_plain_text( $title );
47
48
			$cover = $rss->get_image_url();
0 ignored issues
show
Bug introduced by
The method get_image_url() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
49
			$cover = ! empty( $cover ) ? esc_url( $cover ) : null;
50
51
			$link = $rss->get_link();
0 ignored issues
show
Bug introduced by
The method get_link() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
52
			$link = ! empty( $link ) ? esc_url( $link ) : null;
53
54
			$player_data = array(
55
				'title'  => $title,
56
				'link'   => $link,
57
				'cover'  => $cover,
58
				'tracks' => $tracks,
59
			);
60
61
			// Cache for 1 hour.
62
			set_transient( $transient_key, $player_data, HOUR_IN_SECONDS );
63
		}
64
65
		return $player_data;
66
	}
67
68
	/**
69
	 * Gets a list of tracks for the supplied RSS feed.
70
	 *
71
	 * @param string $rss      The RSS feed to load and list tracks for.
72
	 * @return array|WP_Error The feed's tracks or a error object.
73
	 */
74
	private static function get_track_list( $rss ) {
75
		// Get first ten items and format them.
76
		$track_list = array_map( array( __CLASS__, 'setup_tracks_callback' ), $rss->get_items( 0, 10 ) );
0 ignored issues
show
Bug introduced by
The method get_items cannot be called on $rss (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
77
78
		// Filter out any tracks that are empty.
79
		// Reset the array indicies.
80
		return array_values( array_filter( $track_list ) );
81
	}
82
83
	/**
84
	 * Formats string as pure plaintext, with no HTML tags or entities present.
85
	 * This is ready to be used in React, innerText but needs to be escaped
86
	 * using standard `esc_html` when generating markup on server.
87
	 *
88
	 * @param string $str Input string.
89
	 * @return string Plain text string.
90
	 */
91
	private static function get_plain_text( $str ) {
92
		// Trim string and return if empty.
93
		$str = trim( (string) $str );
94
		if ( empty( $str ) ) {
95
			return '';
96
		}
97
98
		// Replace all entities with their characters, including all types of quotes.
99
		$str = wp_specialchars_decode( $str, ENT_QUOTES );
100
101
		// Make sure there are no tags.
102
		$str = wp_strip_all_tags( $str );
103
104
		return $str;
105
	}
106
107
	/**
108
	 * Loads an RSS feed using `fetch_feed`.
109
	 *
110
	 * @param string $feed        The RSS feed URL to load.
111
	 * @return SimplePie|WP_Error The RSS object or error.
112
	 */
113 View Code Duplication
	private static function load_feed( $feed ) {
114
		$rss = fetch_feed( esc_url_raw( $feed ) );
115
116
		if ( is_wp_error( $rss ) ) {
117
			return new WP_Error( 'invalid_url', __( 'Your podcast couldn\'t be embedded. Please double check your URL.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_url'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
118
		}
119
120
		if ( ! $rss->get_item_quantity() ) {
121
			return new WP_Error( 'no_tracks', __( 'Podcast audio RSS feed has no tracks.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_tracks'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
122
		}
123
124
		return $rss;
125
	}
126
127
	/**
128
	 * Prepares Episode data to be used by the Podcast Player block.
129
	 *
130
	 * @param SimplePie_Item $episode SimplePie_Item object, representing a podcast episode.
131
	 * @return array
132
	 */
133
	private static function setup_tracks_callback( SimplePie_Item $episode ) {
134
		$enclosure = self::get_audio_enclosure( $episode );
135
136
		// If the audio enclosure is empty then it is not playable.
137
		// We therefore return an empty array for this track.
138
		// It will be filtered out later.
139
		if ( is_wp_error( $enclosure ) ) {
140
			return array();
141
		}
142
143
		// If there is no link return an empty array. We will filter out later.
144
		if ( empty( $enclosure->link ) ) {
145
			return array();
146
		}
147
148
		// Build track data.
149
		$track = array(
150
			'id'          => wp_unique_id( 'podcast-track-' ),
151
			'link'        => esc_url( $episode->get_link() ),
152
			'src'         => esc_url( $enclosure->link ),
153
			'type'        => esc_attr( $enclosure->type ),
154
			'description' => self::get_plain_text( $episode->get_description() ),
155
			'title'       => self::get_plain_text( $episode->get_title() ),
156
		);
157
158
		if ( empty( $track['title'] ) ) {
159
			$track['title'] = esc_html__( '(no title)', 'jetpack' );
160
		}
161
162
		if ( ! empty( $enclosure->duration ) ) {
163
			$track['duration'] = esc_html( self::format_track_duration( $enclosure->duration ) );
164
		}
165
166
		return $track;
167
	}
168
169
	/**
170
	 * Retrieves an audio enclosure.
171
	 *
172
	 * @param SimplePie_Item $episode SimplePie_Item object, representing a podcast episode.
173
	 * @return SimplePie_Enclosure|null
174
	 */
175
	private static function get_audio_enclosure( SimplePie_Item $episode ) {
176
		foreach ( (array) $episode->get_enclosures() as $enclosure ) {
177
			if ( 0 === strpos( $enclosure->type, 'audio/' ) ) {
178
				return $enclosure;
179
			}
180
		}
181
182
		return new WP_Error( 'invalid_audio', __( 'Podcast audio is an invalid type.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_audio'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
183
	}
184
185
	/**
186
	 * Returns the track duration as a formatted string.
187
	 *
188
	 * @param number $duration of the track in seconds.
189
	 * @return string
190
	 */
191
	private static function format_track_duration( $duration ) {
192
		$format = $duration > HOUR_IN_SECONDS ? 'H:i:s' : 'i:s';
193
194
		return date_i18n( $format, $duration );
195
	}
196
}
197