PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-premium-analytics / src / class-notices.php
jetpack / jetpack_vendor / automattic / jetpack-premium-analytics / src Last commit date
REST 2 weeks ago Reports 1 week ago Sync 2 weeks ago class-analytics.php 2 days ago class-capabilities.php 2 weeks ago class-connection-configuration.php 2 weeks ago class-dashboard-section-registry.php 2 weeks ago class-dashboard-section.php 2 days ago class-dashboard-support-routes.php 1 week ago class-jetpack-stats-tracker.php 2 weeks ago class-notices.php 2 weeks ago class-widget-type-registry.php 2 weeks ago class-widget-type.php 2 weeks ago class-woocommerce-analytics-tracker.php 2 weeks ago csv-exports.php 2 weeks ago dashboard-grammar.php 2 weeks ago dashboard-layout.php 2 days ago dashboard-sections.php 2 days ago rest-namespace.php 2 weeks ago videopress-availability.php 2 days ago widget-availability.php 2 days ago widget-i18n.json 2 weeks ago widget-modules.php 2 days ago widget-type-support.php 2 days ago widget-types.php 2 weeks ago
class-notices.php
203 lines
1 <?php
2 /**
3 * Computes the dashboard notices, merging WPCOM-stored dismissals with locally-derived flags.
4 *
5 * @package automattic/jetpack-premium-analytics
6 */
7
8 namespace Automattic\Jetpack\PremiumAnalytics;
9
10 use Automattic\Jetpack\Connection\Client;
11 use Automattic\Jetpack\Stats\Options as Stats_Options;
12 use Jetpack_Options;
13 use WP_Error;
14
15 /**
16 * Ported from `Automattic\Jetpack\Stats_Admin\Notices` so premium-analytics owns the notices
17 * surface without depending on the (to-be-deprecated) stats-admin package. The WPCOM dismissal
18 * state is fetched/updated through the blog token; the opt-in/opt-out/feedback/GDPR flags are
19 * derived locally from `Stats\Options` and the environment.
20 */
21 class Notices {
22 const NOTICES_CACHE_KEY = 'jetpack_premium_analytics_notices_cache_key';
23 const OPT_OUT_NEW_STATS_NOTICE_ID = 'opt_out_new_stats';
24 const NEW_STATS_FEEDBACK_NOTICE_ID = 'new_stats_feedback';
25 const OPT_IN_NEW_STATS_NOTICE_ID = 'opt_in_new_stats';
26 const GDPR_COOKIE_CONSENT_NOTICE_ID = 'gdpr_cookie_consent';
27
28 const VIEWS_TO_SHOW_FEEDBACK = 3;
29 const POSTPONE_OPT_IN_NOTICE_DAYS = 30;
30
31 /**
32 * How long the WPCOM dismissal state stays cached. While stats-admin runs in parallel, both
33 * surfaces read the same WPCOM resource under separate cache keys, so a dismissal through one
34 * can leave the other stale for up to this window — acceptable during the migration.
35 *
36 * @var int
37 */
38 const CACHE_TTL = 5 * MINUTE_IN_SECONDS;
39
40 /**
41 * Update notice status.
42 *
43 * @param mixed $id ID of the notice.
44 * @param mixed $status Status of the notice.
45 * @param int $postponed_for Postponed for how many seconds.
46 * @return array|WP_Error
47 */
48 public function update_notice( $id, $status, $postponed_for = 0 ) {
49 delete_transient( self::NOTICES_CACHE_KEY );
50
51 return $this->request_as_blog(
52 array(
53 'timeout' => 5,
54 'method' => 'POST',
55 'headers' => array( 'Content-Type' => 'application/json' ),
56 ),
57 wp_json_encode(
58 array(
59 'id' => $id,
60 'status' => $status,
61 'postponed_for' => $postponed_for,
62 ),
63 JSON_UNESCAPED_SLASHES
64 )
65 );
66 }
67
68 /**
69 * Return an array of notice IDs as keys and a boolean flagging whether to show them.
70 *
71 * @param bool $bypass_cache Refetch the WPCOM dismissal state instead of using the cached copy.
72 * @return array
73 */
74 public function get_notices_to_show( bool $bypass_cache = false ) {
75 // Fetch the WPCOM map once and reuse it for every flag, so a force-refresh costs a single
76 // round-trip rather than one per shared-key lookup.
77 $notices_wpcom = $this->get_notices_from_wpcom( $bypass_cache );
78
79 $new_stats_enabled = Stats_Options::get_option( 'enable_odyssey_stats' );
80 $stats_views = intval( Stats_Options::get_option( 'views' ) );
81 $odyssey_stats_changed_at = intval( Stats_Options::get_option( 'odyssey_stats_changed_at' ) );
82
83 // Check if Jetpack is integrated with the Complianz plugin, which blocks the Stats.
84 $complianz_options_integrations = get_option( 'complianz_options_integrations' );
85 $is_jetpack_blocked_by_complianz = ! isset( $complianz_options_integrations['jetpack'] ) || $complianz_options_integrations['jetpack'];
86
87 return array_merge(
88 $notices_wpcom,
89 array(
90 // Show Opt-in notice 30 days after the new stats being disabled.
91 self::OPT_IN_NEW_STATS_NOTICE_ID => ! $new_stats_enabled
92 && $odyssey_stats_changed_at < time() - self::POSTPONE_OPT_IN_NOTICE_DAYS * DAY_IN_SECONDS
93 && ! $this->is_hidden( $notices_wpcom, self::OPT_IN_NEW_STATS_NOTICE_ID ),
94
95 // Show feedback notice after 3 views of the new stats.
96 self::NEW_STATS_FEEDBACK_NOTICE_ID => $new_stats_enabled
97 && $stats_views >= self::VIEWS_TO_SHOW_FEEDBACK
98 && ! $this->is_hidden( $notices_wpcom, self::NEW_STATS_FEEDBACK_NOTICE_ID ),
99
100 // Show opt-out notice before 3 views of the new stats, where 3 is included.
101 self::OPT_OUT_NEW_STATS_NOTICE_ID => $new_stats_enabled
102 && $stats_views < self::VIEWS_TO_SHOW_FEEDBACK
103 && ! $this->is_hidden( $notices_wpcom, self::OPT_OUT_NEW_STATS_NOTICE_ID ),
104
105 // GDPR cookie consent notice for Complianz users.
106 self::GDPR_COOKIE_CONSENT_NOTICE_ID => class_exists( 'COMPLIANZ' ) && $is_jetpack_blocked_by_complianz
107 && ! $this->is_hidden( $notices_wpcom, self::GDPR_COOKIE_CONSENT_NOTICE_ID ),
108 )
109 );
110 }
111
112 /**
113 * Get the array of notice dismissal flags stored on WPCOM, cached for {@see CACHE_TTL}.
114 *
115 * @param bool $bypass_cache Refetch instead of returning the cached copy.
116 * @return array
117 */
118 public function get_notices_from_wpcom( bool $bypass_cache = false ) {
119 if ( ! $bypass_cache ) {
120 $cached = get_transient( self::NOTICES_CACHE_KEY );
121 if ( false !== $cached ) {
122 $decoded = json_decode( $cached, true );
123 return is_array( $decoded ) ? $decoded : array();
124 }
125 }
126
127 $notices_wpcom = $this->request_as_blog( array( 'timeout' => 5 ) );
128 if ( is_wp_error( $notices_wpcom ) ) {
129 return array();
130 }
131
132 set_transient( self::NOTICES_CACHE_KEY, wp_json_encode( $notices_wpcom, JSON_UNESCAPED_SLASHES ), self::CACHE_TTL );
133
134 return $notices_wpcom;
135 }
136
137 /**
138 * Checks if a notice is hidden, fetching the WPCOM dismissal state.
139 *
140 * @param mixed $id ID of the notice.
141 * @return bool
142 */
143 public function is_notice_hidden( $id ) {
144 return $this->is_hidden( $this->get_notices_from_wpcom(), $id );
145 }
146
147 /**
148 * Whether a notice is hidden in an already-fetched WPCOM dismissal map.
149 *
150 * @param array $notices_wpcom The WPCOM dismissal map.
151 * @param mixed $id ID of the notice.
152 * @return bool
153 */
154 private function is_hidden( array $notices_wpcom, $id ) {
155 return array_key_exists( $id, $notices_wpcom ) && $notices_wpcom[ $id ] === false;
156 }
157
158 /**
159 * Send a blog-token request to the WPCOM notices endpoint and return its decoded body.
160 *
161 * @param array $args Request arguments passed to the connection client.
162 * @param string|null $body Request body, for writes.
163 * @return array|WP_Error Decoded response body, or a WP_Error on transport or API error.
164 */
165 private function request_as_blog( array $args, $body = null ) {
166 $response = Client::wpcom_json_api_request_as_blog(
167 sprintf( '/sites/%d/jetpack-stats-dashboard/notices', Jetpack_Options::get_option( 'id' ) ),
168 'v2',
169 $args,
170 $body,
171 'wpcom'
172 );
173
174 if ( is_wp_error( $response ) ) {
175 return $response;
176 }
177
178 $response_code = (int) wp_remote_retrieve_response_code( $response );
179 $decoded = json_decode( wp_remote_retrieve_body( $response ), true );
180
181 $error_code = null;
182 foreach ( array( 'code', 'error' ) as $key ) {
183 if ( isset( $decoded[ $key ] ) ) {
184 $error_code = $decoded[ $key ];
185 break;
186 }
187 }
188
189 if ( null !== $error_code || 200 !== $response_code ) {
190 // Fall back to a non-empty code so WP_Error keeps the status/message instead of
191 // constructing an empty error (WP_Error::__construct bails on an empty code).
192 return new WP_Error(
193 $error_code ?? 'notices_request_failed',
194 is_array( $decoded ) ? ( $decoded['message'] ?? 'unknown remote error' ) : 'unknown remote error',
195 array( 'status' => $response_code )
196 );
197 }
198
199 // The notices endpoint always returns a JSON object; coerce anything else to an empty map.
200 return is_array( $decoded ) ? $decoded : array();
201 }
202 }
203