Automattic /
jetpack
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 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 | * The RSS feed of the podcast. |
||
| 14 | * |
||
| 15 | * @var string |
||
| 16 | */ |
||
| 17 | protected $feed = null; |
||
| 18 | |||
| 19 | /** |
||
| 20 | * Initialize class. |
||
| 21 | * |
||
| 22 | * @param string $feed The RSS feed of the podcast. |
||
| 23 | */ |
||
| 24 | public function __construct( $feed ) { |
||
| 25 | $this->feed = esc_url_raw( $feed ); |
||
| 26 | } |
||
| 27 | |||
| 28 | /** |
||
| 29 | * Gets podcast data formatted to be used by the Podcast Player block in both server-side |
||
| 30 | * block rendering and in API `WPCOM_REST_API_V2_Endpoint_Podcast_Player`. |
||
| 31 | * |
||
| 32 | * The result is cached for one hour. |
||
| 33 | * |
||
| 34 | * @return array|WP_Error The player data or a error object. |
||
| 35 | */ |
||
| 36 | public function get_player_data() { |
||
| 37 | // Try loading data from the cache. |
||
| 38 | $transient_key = 'jetpack_podcast_' . md5( $this->feed ); |
||
| 39 | $player_data = get_transient( $transient_key ); |
||
| 40 | |||
| 41 | // Fetch data if we don't have any cached. |
||
| 42 | if ( false === $player_data || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) { |
||
| 43 | // Load feed. |
||
| 44 | $rss = $this->load_feed(); |
||
| 45 | |||
| 46 | if ( is_wp_error( $rss ) ) { |
||
| 47 | return $rss; |
||
| 48 | } |
||
| 49 | |||
| 50 | // Get tracks. |
||
| 51 | $tracks = $this->get_track_list(); |
||
| 52 | |||
| 53 | if ( empty( $tracks ) ) { |
||
| 54 | 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' ) ); |
||
| 55 | } |
||
| 56 | |||
| 57 | // Get podcast meta. |
||
| 58 | $title = $rss->get_title(); |
||
| 59 | $title = $this->get_plain_text( $title ); |
||
| 60 | |||
| 61 | $cover = $rss->get_image_url(); |
||
| 62 | $cover = ! empty( $cover ) ? esc_url( $cover ) : null; |
||
| 63 | |||
| 64 | $link = $rss->get_link(); |
||
| 65 | $link = ! empty( $link ) ? esc_url( $link ) : null; |
||
| 66 | |||
| 67 | $player_data = array( |
||
| 68 | 'title' => $title, |
||
| 69 | 'link' => $link, |
||
| 70 | 'cover' => $cover, |
||
| 71 | 'tracks' => $tracks, |
||
| 72 | ); |
||
| 73 | |||
| 74 | // Cache for 1 hour. |
||
| 75 | set_transient( $transient_key, $player_data, HOUR_IN_SECONDS ); |
||
| 76 | } |
||
| 77 | |||
| 78 | return $player_data; |
||
| 79 | } |
||
| 80 | |||
| 81 | /** |
||
| 82 | * Gets a specific track from the supplied feed URL. |
||
| 83 | * |
||
| 84 | * @param string $guid The GUID of the track. |
||
| 85 | * @return array|WP_Error The track object or an error object. |
||
| 86 | */ |
||
| 87 | public function get_track_data( $guid ) { |
||
| 88 | // Try loading track data from the cache. |
||
| 89 | $transient_key = 'jetpack_podcast_' . md5( "$this->feed::$guid" ); |
||
| 90 | $track_data = get_transient( $transient_key ); |
||
| 91 | |||
| 92 | // Fetch data if we don't have any cached. |
||
| 93 | if ( false === $track_data || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) { |
||
| 94 | // Load feed. |
||
| 95 | $rss = $this->load_feed(); |
||
| 96 | |||
| 97 | if ( is_wp_error( $rss ) ) { |
||
| 98 | return $rss; |
||
| 99 | } |
||
| 100 | |||
| 101 | // Loop over all tracks to find the one. |
||
| 102 | foreach ( $rss->get_items() as $track ) { |
||
| 103 | if ( $guid === $track->get_id() ) { |
||
| 104 | $track_data = $this->setup_tracks_callback( $track ); |
||
| 105 | break; |
||
| 106 | } |
||
| 107 | } |
||
| 108 | |||
| 109 | if ( false === $track_data ) { |
||
| 110 | return new WP_Error( 'no_track', __( 'The track was not found.', 'jetpack' ) ); |
||
| 111 | } |
||
| 112 | |||
| 113 | // Cache for 1 hour. |
||
| 114 | set_transient( $transient_key, $track_data, HOUR_IN_SECONDS ); |
||
| 115 | } |
||
| 116 | |||
| 117 | return $track_data; |
||
| 118 | } |
||
| 119 | |||
| 120 | /** |
||
| 121 | * Gets a list of tracks for the supplied RSS feed. |
||
| 122 | * |
||
| 123 | * @return array|WP_Error The feed's tracks or a error object. |
||
| 124 | */ |
||
| 125 | public function get_track_list() { |
||
| 126 | $rss = $this->load_feed(); |
||
| 127 | |||
| 128 | if ( is_wp_error( $rss ) ) { |
||
| 129 | return $rss; |
||
| 130 | } |
||
| 131 | |||
| 132 | // Get first ten items and format them. |
||
| 133 | $track_list = array_map( array( __CLASS__, 'setup_tracks_callback' ), $rss->get_items( 0, 10 ) ); |
||
|
0 ignored issues
–
show
|
|||
| 134 | |||
| 135 | // Filter out any tracks that are empty. |
||
| 136 | // Reset the array indicies. |
||
| 137 | return array_values( array_filter( $track_list ) ); |
||
| 138 | } |
||
| 139 | |||
| 140 | /** |
||
| 141 | * Formats string as pure plaintext, with no HTML tags or entities present. |
||
| 142 | * This is ready to be used in React, innerText but needs to be escaped |
||
| 143 | * using standard `esc_html` when generating markup on server. |
||
| 144 | * |
||
| 145 | * @param string $str Input string. |
||
| 146 | * @return string Plain text string. |
||
| 147 | */ |
||
| 148 | protected function get_plain_text( $str ) { |
||
| 149 | // Trim string and return if empty. |
||
| 150 | $str = trim( (string) $str ); |
||
| 151 | if ( empty( $str ) ) { |
||
| 152 | return ''; |
||
| 153 | } |
||
| 154 | |||
| 155 | // Make sure there are no tags. |
||
| 156 | $str = wp_strip_all_tags( $str ); |
||
| 157 | |||
| 158 | // Replace all entities with their characters, including all types of quotes. |
||
| 159 | $str = html_entity_decode( $str, ENT_QUOTES ); |
||
| 160 | |||
| 161 | return $str; |
||
| 162 | } |
||
| 163 | |||
| 164 | /** |
||
| 165 | * Loads an RSS feed using `fetch_feed`. |
||
| 166 | * |
||
| 167 | * @return SimplePie|WP_Error The RSS object or error. |
||
| 168 | */ |
||
| 169 | public function load_feed() { |
||
| 170 | $rss = fetch_feed( $this->feed ); |
||
| 171 | if ( is_wp_error( $rss ) ) { |
||
| 172 | return new WP_Error( 'invalid_url', __( 'Your podcast couldn\'t be embedded. Please double check your URL.', 'jetpack' ) ); |
||
| 173 | } |
||
| 174 | |||
| 175 | if ( ! $rss->get_item_quantity() ) { |
||
| 176 | return new WP_Error( 'no_tracks', __( 'Podcast audio RSS feed has no tracks.', 'jetpack' ) ); |
||
| 177 | } |
||
| 178 | |||
| 179 | return $rss; |
||
| 180 | } |
||
| 181 | |||
| 182 | /** |
||
| 183 | * Prepares Episode data to be used by the Podcast Player block. |
||
| 184 | * |
||
| 185 | * @param SimplePie_Item $episode SimplePie_Item object, representing a podcast episode. |
||
| 186 | * @return array |
||
| 187 | */ |
||
| 188 | protected function setup_tracks_callback( SimplePie_Item $episode ) { |
||
| 189 | $enclosure = $this->get_audio_enclosure( $episode ); |
||
| 190 | |||
| 191 | // If the audio enclosure is empty then it is not playable. |
||
| 192 | // We therefore return an empty array for this track. |
||
| 193 | // It will be filtered out later. |
||
| 194 | if ( is_wp_error( $enclosure ) ) { |
||
| 195 | return array(); |
||
| 196 | } |
||
| 197 | |||
| 198 | // If there is no link return an empty array. We will filter out later. |
||
| 199 | if ( empty( $enclosure->link ) ) { |
||
| 200 | return array(); |
||
| 201 | } |
||
| 202 | |||
| 203 | // Build track data. |
||
| 204 | $track = array( |
||
| 205 | 'id' => wp_unique_id( 'podcast-track-' ), |
||
| 206 | 'link' => esc_url( $episode->get_link() ), |
||
| 207 | 'src' => esc_url( $enclosure->link ), |
||
| 208 | 'type' => esc_attr( $enclosure->type ), |
||
| 209 | 'description' => $this->get_plain_text( $episode->get_description() ), |
||
| 210 | 'title' => $this->get_plain_text( $episode->get_title() ), |
||
| 211 | 'image' => esc_url( $this->get_episode_image_url( $episode ) ), |
||
| 212 | 'guid' => $this->get_plain_text( $episode->get_id() ), |
||
| 213 | ); |
||
| 214 | |||
| 215 | if ( empty( $track['title'] ) ) { |
||
| 216 | $track['title'] = esc_html__( '(no title)', 'jetpack' ); |
||
| 217 | } |
||
| 218 | |||
| 219 | if ( ! empty( $enclosure->duration ) ) { |
||
| 220 | $track['duration'] = esc_html( $this->format_track_duration( $enclosure->duration ) ); |
||
| 221 | } |
||
| 222 | |||
| 223 | return $track; |
||
| 224 | } |
||
| 225 | |||
| 226 | /** |
||
| 227 | * Retrieves an episode's image URL, if it's available. |
||
| 228 | * |
||
| 229 | * @param SimplePie_Item $episode SimplePie_Item object, representing a podcast episode. |
||
| 230 | * @param string $itunes_ns The itunes namespace, defaulted to the standard 1.0 version. |
||
| 231 | * @return string|null The image URL or null if not found. |
||
| 232 | */ |
||
| 233 | protected function get_episode_image_url( SimplePie_Item $episode, $itunes_ns = 'http://www.itunes.com/dtds/podcast-1.0.dtd' ) { |
||
| 234 | $image = $episode->get_item_tags( $itunes_ns, 'image' ); |
||
| 235 | if ( isset( $image[0]['attribs']['']['href'] ) ) { |
||
| 236 | return $image[0]['attribs']['']['href']; |
||
| 237 | } |
||
| 238 | return null; |
||
| 239 | } |
||
| 240 | |||
| 241 | /** |
||
| 242 | * Retrieves an audio enclosure. |
||
| 243 | * |
||
| 244 | * @param SimplePie_Item $episode SimplePie_Item object, representing a podcast episode. |
||
| 245 | * @return SimplePie_Enclosure|null |
||
| 246 | */ |
||
| 247 | protected function get_audio_enclosure( SimplePie_Item $episode ) { |
||
| 248 | foreach ( (array) $episode->get_enclosures() as $enclosure ) { |
||
| 249 | if ( 0 === strpos( $enclosure->type, 'audio/' ) ) { |
||
| 250 | return $enclosure; |
||
| 251 | } |
||
| 252 | } |
||
| 253 | |||
| 254 | return new WP_Error( 'invalid_audio', __( 'Podcast audio is an invalid type.', 'jetpack' ) ); |
||
| 255 | } |
||
| 256 | |||
| 257 | /** |
||
| 258 | * Returns the track duration as a formatted string. |
||
| 259 | * |
||
| 260 | * @param number $duration of the track in seconds. |
||
| 261 | * @return string |
||
| 262 | */ |
||
| 263 | protected function format_track_duration( $duration ) { |
||
| 264 | $format = $duration > HOUR_IN_SECONDS ? 'H:i:s' : 'i:s'; |
||
| 265 | |||
| 266 | return date_i18n( $format, $duration ); |
||
| 267 | } |
||
| 268 | } |
||
| 269 |
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.