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 | * 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 ); |
||
| 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' ) ); |
||
| 42 | } |
||
| 43 | |||
| 44 | // Get podcast meta. |
||
| 45 | $title = $rss->get_title(); |
||
| 46 | $title = self::get_plain_text( $title ); |
||
| 47 | |||
| 48 | $cover = $rss->get_image_url(); |
||
| 49 | $cover = ! empty( $cover ) ? esc_url( $cover ) : null; |
||
| 50 | |||
| 51 | $link = $rss->get_link(); |
||
| 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 ); |
||
|
0 ignored issues
–
show
|
|||
| 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
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
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 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 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
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 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 ) ); |
||
| 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 | private 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' ) ); |
||
| 160 | } |
||
| 161 | |||
| 162 | if ( ! $rss->get_item_quantity() ) { |
||
| 163 | return new WP_Error( 'no_tracks', __( 'Podcast audio RSS feed has no tracks.', 'jetpack' ) ); |
||
| 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 | ); |
||
| 199 | |||
| 200 | if ( empty( $track['title'] ) ) { |
||
| 201 | $track['title'] = esc_html__( '(no title)', 'jetpack' ); |
||
| 202 | } |
||
| 203 | |||
| 204 | if ( ! empty( $enclosure->duration ) ) { |
||
| 205 | $track['duration'] = esc_html( self::format_track_duration( $enclosure->duration ) ); |
||
| 206 | } |
||
| 207 | |||
| 208 | return $track; |
||
| 209 | } |
||
| 210 | |||
| 211 | /** |
||
| 212 | * Retrieves an audio enclosure. |
||
| 213 | * |
||
| 214 | * @param SimplePie_Item $episode SimplePie_Item object, representing a podcast episode. |
||
| 215 | * @return SimplePie_Enclosure|null |
||
| 216 | */ |
||
| 217 | private static function get_audio_enclosure( SimplePie_Item $episode ) { |
||
| 218 | foreach ( (array) $episode->get_enclosures() as $enclosure ) { |
||
| 219 | if ( 0 === strpos( $enclosure->type, 'audio/' ) ) { |
||
| 220 | return $enclosure; |
||
| 221 | } |
||
| 222 | } |
||
| 223 | |||
| 224 | return new WP_Error( 'invalid_audio', __( 'Podcast audio is an invalid type.', 'jetpack' ) ); |
||
| 225 | } |
||
| 226 | |||
| 227 | /** |
||
| 228 | * Returns the track duration as a formatted string. |
||
| 229 | * |
||
| 230 | * @param number $duration of the track in seconds. |
||
| 231 | * @return string |
||
| 232 | */ |
||
| 233 | private static function format_track_duration( $duration ) { |
||
| 234 | $format = $duration > HOUR_IN_SECONDS ? 'H:i:s' : 'i:s'; |
||
| 235 | |||
| 236 | return date_i18n( $format, $duration ); |
||
| 237 | } |
||
| 238 | } |
||
| 239 |
Let’s assume you have a class which uses late-static binding:
}
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:In the case above, it makes sense to update
SomeClassto useselfinstead: