PluginProbe
Extendify / 3.2.1
Extendify v3.2.1
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.63.2.1 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.