Completed
Push — update/get-podcast-episode-ima... ( 1d21dc )
by
unknown
185:40 queued 177:12
created

Jetpack_Podcast_Helper   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 244
Duplicated Lines 5.33 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 13
loc 244
rs 9.68
c 0
b 0
f 0
wmc 34
lcom 1
cbo 1

9 Methods

Rating   Name   Duplication   Size   Complexity  
B get_player_data() 0 46 8
B get_track_data() 0 34 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 36 5
A get_episode_image_url() 0 7 2
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 specific track from the supplied feed URL.
70
	 *
71
	 * @param string $feed     The RSS feed to find the track in.
72
	 * @param string $guid     The GUID of the track.
73
	 * @return array|WP_Error  The track object or an error object.
74
	 */
75
	public static function get_track_data( $feed, $guid ) {
76
		$feed = esc_url_raw( $feed );
77
78
		// Try loading track data from the cache.
79
		$transient_key = 'jetpack_podcast_' . md5( "$feed::$guid" );
80
		$track_data    = get_transient( $transient_key );
81
82
		// Fetch data if we don't have any cached.
83
		if ( false === $track_data || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) {
84
			// Load feed.
85
			$rss = static::load_feed( $feed );
86
87
			if ( is_wp_error( $rss ) ) {
88
				return $rss;
89
			}
90
91
			// Loop over all tracks to find the one.
92
			foreach ( $rss->get_items() as $track ) {
0 ignored issues
show
Bug introduced by
The method get_items() 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...
93
				if ( $guid === $track->get_id() ) {
94
					$track_data = static::setup_tracks_callback( $track );
0 ignored issues
show
Bug introduced by
Since setup_tracks_callback() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of setup_tracks_callback() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
95
					break;
96
				}
97
			}
98
99
			if ( false === $track_data ) {
100
				return new WP_Error( 'no_track', __( 'The track was not found.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'no_track'.

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...
101
			}
102
103
			// Cache for 1 hour.
104
			set_transient( $transient_key, $track_data, HOUR_IN_SECONDS );
105
		}
106
107
		return $track_data;
108
	}
109
110
	/**
111
	 * Gets a list of tracks for the supplied RSS feed.
112
	 *
113
	 * @param string $rss      The RSS feed to load and list tracks for.
114
	 * @return array|WP_Error The feed's tracks or a error object.
115
	 */
116
	private static function get_track_list( $rss ) {
117
		// Get first ten items and format them.
118
		$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...
119
120
		// Filter out any tracks that are empty.
121
		// Reset the array indicies.
122
		return array_values( array_filter( $track_list ) );
123
	}
124
125
	/**
126
	 * Formats string as pure plaintext, with no HTML tags or entities present.
127
	 * This is ready to be used in React, innerText but needs to be escaped
128
	 * using standard `esc_html` when generating markup on server.
129
	 *
130
	 * @param string $str Input string.
131
	 * @return string Plain text string.
132
	 */
133
	private static function get_plain_text( $str ) {
134
		// Trim string and return if empty.
135
		$str = trim( (string) $str );
136
		if ( empty( $str ) ) {
137
			return '';
138
		}
139
140
		// Make sure there are no tags.
141
		$str = wp_strip_all_tags( $str );
142
143
		// Replace all entities with their characters, including all types of quotes.
144
		$str = html_entity_decode( $str, ENT_QUOTES );
145
146
		return $str;
147
	}
148
149
	/**
150
	 * Loads an RSS feed using `fetch_feed`.
151
	 *
152
	 * @param string $feed        The RSS feed URL to load.
153
	 * @return SimplePie|WP_Error The RSS object or error.
154
	 */
155 View Code Duplication
	public static function load_feed( $feed ) {
156
		$rss = fetch_feed( esc_url_raw( $feed ) );
157
158
		if ( is_wp_error( $rss ) ) {
159
			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...
160
		}
161
162
		if ( ! $rss->get_item_quantity() ) {
163
			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...
164
		}
165
166
		return $rss;
167
	}
168
169
	/**
170
	 * Prepares Episode data to be used by the Podcast Player block.
171
	 *
172
	 * @param SimplePie_Item $episode SimplePie_Item object, representing a podcast episode.
173
	 * @return array
174
	 */
175
	private static function setup_tracks_callback( SimplePie_Item $episode ) {
176
		$enclosure = self::get_audio_enclosure( $episode );
177
178
		// If the audio enclosure is empty then it is not playable.
179
		// We therefore return an empty array for this track.
180
		// It will be filtered out later.
181
		if ( is_wp_error( $enclosure ) ) {
182
			return array();
183
		}
184
185
		// If there is no link return an empty array. We will filter out later.
186
		if ( empty( $enclosure->link ) ) {
187
			return array();
188
		}
189
190
		// Build track data.
191
		$track = array(
192
			'id'          => wp_unique_id( 'podcast-track-' ),
193
			'link'        => esc_url( $episode->get_link() ),
194
			'src'         => esc_url( $enclosure->link ),
195
			'type'        => esc_attr( $enclosure->type ),
196
			'description' => self::get_plain_text( $episode->get_description() ),
197
			'title'       => self::get_plain_text( $episode->get_title() ),
198
			'image'       => esc_url( self::get_episode_image_url( $episode ) ),
199
		);
200
201
		if ( empty( $track['title'] ) ) {
202
			$track['title'] = esc_html__( '(no title)', 'jetpack' );
203
		}
204
205
		if ( ! empty( $enclosure->duration ) ) {
206
			$track['duration'] = esc_html( self::format_track_duration( $enclosure->duration ) );
207
		}
208
209
		return $track;
210
	}
211
212
	/**
213
	 * Retrieves an audio enclosure.
214
	 *
215
	 * @param SimplePie_Item $episode SimplePie_Item object, representing a podcast episode.
216
	 * @param string         $itunes_ns The itunes namespace, defaulted to the standard 1.0 version.
217
	 * @return string|null The image URL or null if not found.
218
	 */
219
	private static function get_episode_image_url( SimplePie_Item $episode, $itunes_ns = 'http://www.itunes.com/dtds/podcast-1.0.dtd' ) {
220
		$image = $episode->get_item_tags( $itunes_ns, 'image' );
221
		if ( isset( $image[0]['attribs']['']['href'] ) ) {
222
			return $image[0]['attribs']['']['href'];
223
		}
224
		return null;
225
	}
226
227
	/**
228
	 * Retrieves an audio enclosure.
229
	 *
230
	 * @param SimplePie_Item $episode SimplePie_Item object, representing a podcast episode.
231
	 * @return SimplePie_Enclosure|null
232
	 */
233
	private static function get_audio_enclosure( SimplePie_Item $episode ) {
234
		foreach ( (array) $episode->get_enclosures() as $enclosure ) {
235
			if ( 0 === strpos( $enclosure->type, 'audio/' ) ) {
236
				return $enclosure;
237
			}
238
		}
239
240
		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...
241
	}
242
243
	/**
244
	 * Returns the track duration as a formatted string.
245
	 *
246
	 * @param number $duration of the track in seconds.
247
	 * @return string
248
	 */
249
	private static function format_track_duration( $duration ) {
250
		$format = $duration > HOUR_IN_SECONDS ? 'H:i:s' : 'i:s';
251
252
		return date_i18n( $format, $duration );
253
	}
254
}
255