PluginProbe
Extendify / trunk
Extendify vtrunk
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
← All changes | app/Shared/DataProvider/NotificationData.php +57 -2 3.1.6 → trunk View file →
@@ -16,8 +16,16 @@
16 16 */
17 17 class NotificationData
18 18 {
19 19 /**
20 + * Kept in sync with the token withSiteHost() swaps in src/Notifications/notification-link.js.
21 + *
22 + * @var string
23 + */
24 + // phpcs:ignore PSR12.Properties.ConstantVisibility.NotFound -- 7.0 floor: no const visibility
25 + const SITE_URL_TOKEN = '{SITEURL}';
26 +
27 + /**
20 28 * Registers the wp-cron handler that refreshes stale notifications.
21 29 *
22 30 * @return void
23 31 */
@@ -41,9 +49,9 @@
41 49 $locale = \get_locale();
42 50 $cached = \get_option('extendify_notifications_' . $locale);
43 51
44 52 if (!is_array($cached) || !isset($cached['fetchedAt'])) {
45 - return self::refresh($locale) ?? [];
53 + return self::withSafeLinks(self::refresh($locale) ?? []);
46 54 }
47 55
48 56 $age = time() - $cached['fetchedAt'];
49 57 // A host whose requests are being refused would otherwise re-ask every TTL.
@@ -56,9 +64,56 @@
56 64 }
57 65 }
58 66 }
59 67
60 - return $cached['data'] ?? [];
68 + return self::withSafeLinks($cached['data'] ?? []);
69 + }
70 +
71 + /**
72 + * Drops any link a browser must not follow.
73 + *
74 + * A feed link becomes an href in wp-admin, so javascript: would run as the site owner.
75 + *
76 + * @param mixed $notifications - Notifications as the feed sent them.
77 + * @return array
78 + */
79 + private static function withSafeLinks($notifications)
80 + {
81 + if (!is_array($notifications)) {
82 + return [];
83 + }
84 +
85 + return array_map([self::class, 'withSafeLink'], $notifications);
86 + }
87 +
88 + /**
89 + * Drops one notification's link unless it is http, https or site-relative.
90 + *
91 + * esc_url_raw strips the placeholder's braces, so only the probe copy goes through it.
92 + *
93 + * @param mixed $notification - One notification as the feed sent it.
94 + * @return mixed
95 + */
96 + private static function withSafeLink($notification)
97 + {
98 + if (!is_array($notification) || !isset($notification['link'])) {
99 + return $notification;
100 + }
101 +
102 + if (!is_string($notification['link'])) {
103 + unset($notification['link']);
104 +
105 + return $notification;
106 + }
107 +
108 + $host = (string) \wp_parse_url(\home_url(), PHP_URL_HOST);
109 + $probe = str_replace(self::SITE_URL_TOKEN, $host, $notification['link']);
110 +
111 + if (\esc_url_raw($probe, ['http', 'https']) === '') {
112 + unset($notification['link']);
113 + }
114 +
115 + return $notification;
61 116 }
62 117
63 118 /**
64 119 * Fetch notifications from the API and persist them.