PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.5
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.5
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 13.8.3 All 506 releases
jetpack / jetpack_vendor / automattic / jetpack-stats-admin / src / class-admin-bar.php

class-admin-bar.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.5, at jetpack_vendor/automattic/jetpack-stats-admin/src/class-admin-bar.php

255 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Stats in the WordPress admin bar.
4 *
5 * @package automattic/jetpack-stats-admin
6 */
7
8 namespace Automattic\Jetpack\Stats_Admin;
9
10 use Automattic\Jetpack\Connection\Client;
11 use Automattic\Jetpack\Constants;
12 use Automattic\Jetpack\Stats\Options as Stats_Options;
13 use Automattic\Jetpack\Status\Host;
14
15 /**
16 * Adds the views chart and a Stats link to the admin bar.
17 */
18 class Admin_Bar {
19 /**
20 * The admin bar charts WordPress.com draws, at 1x and 2x.
21 *
22 * @var string[]
23 */
24 const CHARTS = array( 'admin-bar-hours-scale', 'admin-bar-hours-scale-2x' );
25
26 /**
27 * Register the hooks.
28 *
29 * @return void
30 */
31 public static function init() {
32 add_action( 'admin_head', array( __CLASS__, 'maybe_add_chart' ), 100 );
33 add_action( 'wp_head', array( __CLASS__, 'maybe_add_chart' ), 100 );
34 add_action( 'wp_before_admin_bar_render', array( __CLASS__, 'add_site_menu_link' ) );
35 add_action( 'admin_init', array( __CLASS__, 'maybe_serve_chart' ), 1 );
36 add_filter( 'pre_option_db_version', array( __CLASS__, 'ignore_db_version' ) );
37 }
38
39 /**
40 * Queue the views chart and print its styles when the current user should see it.
41 *
42 * @return void
43 */
44 public static function maybe_add_chart() {
45 if (
46 ! is_user_logged_in() ||
47 ! Stats_Options::get_option( 'admin_bar' ) ||
48 ! current_user_can( 'view_stats' ) ||
49 ! is_admin_bar_showing()
50 ) {
51 return;
52 }
53
54 add_action( 'admin_bar_menu', array( __CLASS__, 'add_chart_node' ), 100 );
55 ?>
56 <style data-ampdevmode type='text/css'>
57 #wpadminbar .quicklinks li#wp-admin-bar-stats {
58 height: 32px;
59 }
60 #wpadminbar .quicklinks li#wp-admin-bar-stats a {
61 height: 32px;
62 padding: 0;
63 }
64 #wpadminbar .quicklinks li#wp-admin-bar-stats a div {
65 height: 32px;
66 width: 95px;
67 overflow: hidden;
68 margin: 0 10px;
69 }
70 #wpadminbar .quicklinks li#wp-admin-bar-stats a:hover div {
71 width: auto;
72 margin: 0 8px 0 10px;
73 }
74 #wpadminbar .quicklinks li#wp-admin-bar-stats a img {
75 height: 24px;
76 margin: 4px 0;
77 max-width: none;
78 border: none;
79 }
80 </style>
81 <?php
82 }
83
84 /**
85 * Add the views chart, linked to the Stats dashboard.
86 *
87 * @param \WP_Admin_Bar $wp_admin_bar The admin bar.
88 * @return void
89 */
90 public static function add_chart_node( $wp_admin_bar ) {
91 $img_src = esc_attr( self::get_chart_src( 'admin-bar-hours-scale' ) );
92 $img_src_2x = esc_attr( self::get_chart_src( 'admin-bar-hours-scale-2x' ) );
93 $alt = esc_attr__( 'Stats', 'jetpack-stats-admin' );
94 $title = esc_attr__( 'Views over 48 hours. Click for more Jetpack Stats.', 'jetpack-stats-admin' );
95
96 $wp_admin_bar->add_menu(
97 array(
98 'id' => 'stats',
99 'href' => admin_url( 'admin.php?page=stats' ),
100 'title' => "<div><img fetchpriority='low' loading='lazy' decoding='async' src='$img_src' srcset='$img_src 1x, $img_src_2x 2x' width='112' height='24' alt='$alt' title='$title'></div>",
101 )
102 );
103 }
104
105 /**
106 * Add a Stats link to the site-name menu, next to Dashboard.
107 *
108 * @return void
109 */
110 public static function add_site_menu_link() {
111 global $wp_admin_bar;
112
113 // WordPress.com adds its own Stats link to this menu.
114 if (
115 ! is_object( $wp_admin_bar ) ||
116 ! $wp_admin_bar->get_node( 'dashboard' ) ||
117 ! current_user_can( 'view_stats' ) ||
118 ( new Host() )->is_wpcom_platform()
119 ) {
120 return;
121 }
122
123 $wp_admin_bar->add_node(
124 array(
125 'parent' => 'site-name',
126 'id' => 'jetpack-stats',
127 'title' => __( 'Stats', 'jetpack-stats-admin' ),
128 'href' => admin_url( 'admin.php?page=stats' ),
129 )
130 );
131 }
132
133 /**
134 * Get the local URL that serves a chart image.
135 *
136 * @param string $chart One of the CHARTS.
137 * @return string
138 */
139 public static function get_chart_src( $chart ) {
140 return add_query_arg(
141 array(
142 'page' => 'stats',
143 'chart' => $chart,
144 ),
145 admin_url( 'admin.php' )
146 );
147 }
148
149 /**
150 * Serve a chart image and stop, when the request asks for one.
151 *
152 * The browser cannot fetch the chart from WordPress.com directly, because only the blog token can read it.
153 *
154 * @return void
155 */
156 public static function maybe_serve_chart() {
157 // URLs with `proxy` come from the Jetpack plugin's deprecated stats_get_image_chart_src(), and its own handler forwards their extra parameters.
158 if ( isset( $_GET['proxy'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- An image request carries no nonce, and it changes nothing.
159 return;
160 }
161
162 $chart = self::get_requested_chart();
163 if ( null === $chart || ! Stats_Options::get_option( 'admin_bar' ) || ! current_user_can( 'view_stats' ) ) {
164 return;
165 }
166
167 $image = self::fetch_chart( $chart );
168 if ( null === $image ) {
169 status_header( 502 );
170 exit( 0 );
171 }
172
173 header( 'Content-Type: ' . $image['type'] );
174 header( 'Content-Length: ' . strlen( $image['body'] ) );
175 echo $image['body']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Binary image data.
176 exit( 0 );
177 }
178
179 /**
180 * Fetch a chart image from WordPress.com with the blog token.
181 *
182 * @param string $chart One of the CHARTS.
183 * @return array{type: string, body: string}|null The image, or null when WordPress.com did not return one.
184 */
185 public static function fetch_chart( $chart ) {
186 $server = Constants::get_constant( 'STATS_DASHBOARD_SERVER' ) ?? 'dashboard.wordpress.com';
187
188 // WordPress.com accepts the blog token on this file only when `page`, `proxy` and `blog` are all present.
189 $url = add_query_arg(
190 array(
191 'page' => 'stats',
192 'proxy' => '',
193 'blog' => Stats_Options::get_option( 'blog_id' ),
194 ),
195 "https://{$server}/wp-includes/charts/{$chart}.php"
196 );
197
198 $response = Client::remote_request(
199 array(
200 'url' => $url,
201 'method' => 'GET',
202 'timeout' => 90,
203 'user_id' => 0,
204 )
205 );
206
207 // A failed request has the code '', which PHP 8 cannot divide.
208 $code = (int) wp_remote_retrieve_response_code( $response );
209 $type = wp_remote_retrieve_header( $response, 'content-type' );
210 $body = wp_remote_retrieve_body( $response );
211 if ( 2 !== (int) ( $code / 100 ) || ! is_string( $type ) || ! str_starts_with( $type, 'image/' ) || '' === $body ) {
212 return null;
213 }
214
215 return array(
216 'type' => $type,
217 'body' => $body,
218 );
219 }
220
221 /**
222 * Keep chart requests from redirecting to upgrade.php while a database upgrade is pending.
223 *
224 * @see wp-admin/admin.php, which compares the stored `db_version` with `$wp_db_version`.
225 *
226 * @param mixed $version The stored database version.
227 * @return mixed
228 */
229 public static function ignore_db_version( $version ) {
230 if ( is_admin() && null !== self::get_requested_chart() ) {
231 global $wp_db_version;
232 return $wp_db_version;
233 }
234
235 return $version;
236 }
237
238 /**
239 * Get the chart the current request asks for.
240 *
241 * @return string|null One of the CHARTS, or null when the request is not for a chart.
242 */
243 private static function get_requested_chart() {
244 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- An image request carries no nonce, and it changes nothing.
245 if ( ! isset( $_GET['page'] ) || ! isset( $_GET['chart'] ) || 'stats' !== $_GET['page'] ) {
246 return null;
247 }
248
249 $chart = sanitize_key( wp_unslash( $_GET['chart'] ) );
250 // phpcs:enable WordPress.Security.NonceVerification.Recommended
251
252 return in_array( $chart, self::CHARTS, true ) ? $chart : null;
253 }
254 }
255