Completed
Push — updates/infinity-vanilla-js ( fda899...9466ff )
by
unknown
07:55
created

Jetpack_Podcast_Helper::format_track_duration()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
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 ) {
30
			// Load feed.
31
			$rss = self::load_feed( $feed );
32
			if ( is_wp_error( $rss ) ) {
33
				return $rss;
34
			}
35
36
			// Get tracks.
37
			$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...
38
39
			// Get podcast meta.
40
			$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...
41
			$title = self::get_plain_text( $title );
42
43
			$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...
44
			$cover = ! empty( $cover ) ? esc_url( $cover ) : null;
45
46
			$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...
47
			$link = ! empty( $link ) ? esc_url( $link ) : null;
48
49
			$player_data = array(
50
				'title'  => $title,
51
				'link'   => $link,
52
				'cover'  => $cover,
53
				'tracks' => $tracks,
54
			);
55
56
			// Cache for 1 hour.
57
			set_transient( $transient_key, $player_data, HOUR_IN_SECONDS );
58
		}
59
60
		return $player_data;
61
	}
62
63
	/**
64
	 * Gets a list of tracks for the supplied RSS feed.
65
	 *
66
	 * @param string $rss      The RSS feed to load and list tracks for.
67
	 * @return array|WP_Error The feed's tracks or a error object.
68
	 */
69
	private static function get_track_list( $rss ) {
70
		// Get first ten items and format them.
71
		$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...
72
73
		// Remove empty tracks.
74
		return array_filter( $track_list );
75
	}
76
77
	/**
78
	 * Formats string as pure plaintext, with no HTML tags or entities present.
79
	 * This is ready to be used in React, innerText but needs to be escaped
80
	 * using standard `esc_html` when generating markup on server.
81
	 *
82
	 * @param string $str Input string.
83
	 * @return string Plain text string.
84
	 */
85
	private static function get_plain_text( $str ) {
86
		// Trim string and return if empty.
87
		$str = trim( (string) $str );
88
		if ( empty( $str ) ) {
89
			return '';
90
		}
91
92
		// Replace all entities with their characters, including all types of quotes.
93
		$str = wp_specialchars_decode( $str, ENT_QUOTES );
94
95
		// Make sure there are no tags.
96
		$str = wp_strip_all_tags( $str );
97
98
		return $str;
99
	}
100
101
	/**
102
	 * Loads an RSS feed using `fetch_feed`.
103
	 *
104
	 * @param string $feed        The RSS feed URL to load.
105
	 * @return SimplePie|WP_Error The RSS object or error.
106
	 */
107 View Code Duplication
	private static function load_feed( $feed ) {
108
		$rss = fetch_feed( esc_url_raw( $feed ) );
109
110
		if ( is_wp_error( $rss ) ) {
111
			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...
112
		}
113
114
		if ( ! $rss->get_item_quantity() ) {
115
			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...
116
		}
117
118
		return $rss;
119
	}
120
121
	/**
122
	 * Prepares Episode data to be used by the Podcast Player block.
123
	 *
124
	 * @param SimplePie_Item $episode SimplePie_Item object, representing a podcast episode.
125
	 * @return array
126
	 */
127
	private static function setup_tracks_callback( SimplePie_Item $episode ) {
128
		$enclosure = self::get_audio_enclosure( $episode );
129
130
		// If there is no link return an empty array. We will filter out later.
131
		if ( empty( $enclosure->link ) ) {
132
			return array();
133
		}
134
135
		// Build track data.
136
		$track = array(
137
			'id'          => wp_unique_id( 'podcast-track-' ),
138
			'link'        => esc_url( $episode->get_link() ),
139
			'src'         => esc_url( $enclosure->link ),
140
			'type'        => esc_attr( $enclosure->type ),
141
			'description' => self::get_plain_text( $episode->get_description() ),
142
			'title'       => self::get_plain_text( $episode->get_title() ),
143
		);
144
145
		if ( empty( $track['title'] ) ) {
146
			$track['title'] = esc_html__( '(no title)', 'jetpack' );
147
		}
148
149
		if ( ! empty( $enclosure->duration ) ) {
150
			$track['duration'] = esc_html( self::format_track_duration( $enclosure->duration ) );
151
		}
152
153
		return $track;
154
	}
155
156
	/**
157
	 * Retrieves an audio enclosure.
158
	 *
159
	 * @param SimplePie_Item $episode SimplePie_Item object, representing a podcast episode.
160
	 * @return SimplePie_Enclosure|null
161
	 */
162
	private static function get_audio_enclosure( SimplePie_Item $episode ) {
163
		foreach ( (array) $episode->get_enclosures() as $enclosure ) {
164
			if ( 0 === strpos( $enclosure->type, 'audio/' ) ) {
165
				return $enclosure;
166
			}
167
		}
168
169
		// Default to empty SimplePie_Enclosure object.
170
		return $episode->get_enclosure();
171
	}
172
173
	/**
174
	 * Returns the track duration as a formatted string.
175
	 *
176
	 * @param number $duration of the track in seconds.
177
	 * @return string
178
	 */
179
	private static function format_track_duration( $duration ) {
180
		$format = $duration > HOUR_IN_SECONDS ? 'H:i:s' : 'i:s';
181
182
		return date_i18n( $format, $duration );
183
	}
184
}
185