PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 All 507 releases
jetpack / jetpack_vendor / automattic / jetpack-podcast / src / feed / class-feed-detection.php

class-feed-detection.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-beta, at jetpack_vendor/automattic/jetpack-podcast/src/feed/class-feed-detection.php

109 lines 3.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Records when podcast directory crawlers fetch the feed.
4 *
5 * @package automattic/jetpack-podcast
6 */
7
8 declare( strict_types = 1 );
9
10 namespace Automattic\Jetpack\Podcast\Feed;
11
12 use Automattic\Jetpack\Podcast\Settings;
13
14 /**
15 * Promotes an existing pending podcatcher `podcasting_show_states` entry to
16 * `'active'` the first time we see its UA fetch the feed. Idempotent
17 * thereafter.
18 */
19 class Feed_Detection {
20
21 /**
22 * UA needle → podcatcher slug. First match wins, so order matters: more
23 * specific brand needles (e.g. `GooglePodcasts`, `YouTubeMusic`) come
24 * before generic ones (e.g. Apple's `Podcasts/`) that they share a
25 * substring with.
26 *
27 * Cross-referenced against opawg/user-agents-v2 (the OPAWG community
28 * dataset used by Podtrac, Megaphone, Art19, Chartable). When directories
29 * change UAs or new ones emerge, sync the relevant entries here:
30 * https://github.com/opawg/user-agents-v2/blob/master/src/{apps,bots}.json
31 *
32 * @var array<string, string>
33 */
34 private const NEEDLES = array(
35 // YouTube / Google Podcasts — listed first so `GooglePodcasts/`
36 // doesn't get caught by Apple's broader `Podcasts/` needle below.
37 'Google-Podcast' => 'youtube',
38 'YouTube-Podcast' => 'youtube',
39 'GooglePodcasts' => 'youtube',
40 'GoogleChirp' => 'youtube',
41 'YouTubeMusic' => 'youtube',
42
43 // Apple Podcasts (iOS app, Mac app, automated checks, HomePod, Apple TV).
44 'iTMS' => 'apple',
45 'AppleCoreMedia' => 'apple',
46 'Podcasts/' => 'apple',
47 'iTunes' => 'apple',
48 'AirPodcasts/' => 'apple',
49
50 // Spotify — substring catches `Spotify/…`, `spotify-rss-…`, all variants.
51 'Spotify' => 'spotify',
52
53 // Pocket Casts.
54 'Pocket Casts' => 'pocketcasts',
55 'PocketCasts' => 'pocketcasts',
56
57 // Amazon — `AmazonMusic` is the listening app; `Amazon Music Podcast`
58 // (with spaces) is the actual feed crawler. Keep both.
59 'AmazonMusic' => 'amazon',
60 'Amazon Music Podcast' => 'amazon',
61
62 // Podcast Index — substring `PodcastIndex` catches `Podcastindex.org/`,
63 // `PodcastIndexer/`, `PodcastIndexManager/`, `PodcastIndex Classifier/`.
64 'PodcastIndex' => 'podcastindex',
65 );
66
67 /**
68 * Inspect the current request's User-Agent and, if it's a directory
69 * crawler with an existing pending state, mark its state `'active'`.
70 * No-op if the UA is missing, not in the directory allowlist, or the
71 * matched directory has no pending state.
72 */
73 public static function detect_and_record(): void {
74 $ua = isset( $_SERVER['HTTP_USER_AGENT'] )
75 ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) )
76 : '';
77 if ( '' === $ua ) {
78 return;
79 }
80
81 $slug = null;
82 foreach ( self::NEEDLES as $needle => $candidate ) {
83 if ( false !== stripos( $ua, $needle ) ) {
84 $slug = $candidate;
85 break;
86 }
87 }
88
89 if ( null === $slug || ! isset( Settings::SHOW_URL_HOSTS[ $slug ] ) ) {
90 return;
91 }
92
93 $states = get_option( 'podcasting_show_states', array() );
94 if ( ! is_array( $states ) ) {
95 $states = array();
96 }
97
98 if ( ! isset( $states[ $slug ] ) || 'pending' !== $states[ $slug ] ) {
99 return;
100 }
101
102 // Concurrent first-fetches from different apps can race the read-modify-write.
103 // Benign: the loser's next poll (multi-hour cadence) re-writes its key. Worst
104 // case is one app sitting in `pending` for one extra poll cycle.
105 $states[ $slug ] = 'active';
106 update_option( 'podcasting_show_states', $states );
107 }
108 }
109