PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / trunk
Jetpack – WP Security, Backup, Speed, & Growth vtrunk
16.2-a.3 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-stats-admin / src / class-admin-post-list-column.php
jetpack / jetpack_vendor / automattic / jetpack-stats-admin / src Last commit date
class-admin-post-list-column.php 6 days ago class-dashboard.php 1 week ago class-main.php 6 days ago class-notices.php 8 months ago class-odyssey-assets.php 4 weeks ago class-odyssey-config-data.php 1 week ago class-rest-controller.php 2 months ago class-wp-dashboard-odyssey-widget.php 2 years ago class-wpcom-client.php 1 week ago
class-admin-post-list-column.php
380 lines
1 <?php
2 /**
3 * A class that adds a stats column to wp-admin Post List.
4 *
5 * @package automattic/jetpack-stats-admin
6 */
7
8 namespace Automattic\Jetpack\Stats_Admin;
9
10 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
11 use Automattic\Jetpack\Redirect;
12 use Automattic\Jetpack\Stats\Options as Stats_Options;
13 use Automattic\Jetpack\Stats\WPCOM_Stats;
14 use Automattic\Jetpack\Status\Host;
15 use NumberFormatter;
16
17 /**
18 * Add a Stats column in the post and page lists.
19 */
20 class Admin_Post_List_Column {
21
22 /**
23 * Create the object.
24 *
25 * @return self
26 */
27 public static function register() {
28 return new self();
29 }
30
31 /**
32 * A list of NumberFormatters.
33 *
34 * @var \NumberFormatter[]
35 */
36 private $formatter;
37
38 /**
39 * The current locale.
40 *
41 * @var string
42 */
43 private $locale;
44
45 /**
46 * The constructor.
47 */
48 public function __construct() {
49 // Add an icon to see stats in WordPress.com for a particular post.
50 add_action( 'admin_enqueue_scripts', array( $this, 'stats_load_admin_css' ) );
51
52 add_filter( 'manage_posts_columns', array( $this, 'add_stats_post_table' ) );
53 add_filter( 'manage_pages_columns', array( $this, 'add_stats_post_table' ) );
54
55 add_action( 'manage_posts_custom_column', array( $this, 'add_stats_post_table_cell' ), 10, 2 );
56 add_action( 'manage_pages_custom_column', array( $this, 'add_stats_post_table_cell' ), 10, 2 );
57 }
58
59 /**
60 * Load CSS needed for Stats column width in WP-Admin area.
61 *
62 * @since 4.7.0
63 * @since 0.33.0 Added the `$hook_suffix` parameter.
64 *
65 * @param string $hook_suffix The current admin page.
66 */
67 public function stats_load_admin_css( $hook_suffix = '' ) {
68 if ( 'edit.php' !== $hook_suffix ) {
69 return;
70 }
71
72 wp_add_inline_style(
73 'common',
74 '.wp-list-table.fixed .column-stats { width: 9em; white-space: nowrap; }'
75 );
76 }
77
78 /**
79 * Set content for cell with link to an entry's stats in Odyssey Stats.
80 *
81 * @param string $column The name of the column to display.
82 * @param int $post_id The current post ID.
83 *
84 * @since 4.7.0
85 */
86 public function add_stats_post_table_cell( $column, $post_id ) {
87 if ( 'stats' === $column ) {
88 if ( 'publish' !== get_post_status( $post_id ) ) {
89 printf(
90 '<span aria-hidden="true">—</span><span class="screen-reader-text">%s</span>',
91 esc_html__( 'No stats', 'jetpack-stats-admin' )
92 );
93 } else {
94 // Link to the wp-admin stats page.
95 $query_args = array(
96 'from' => 'postList',
97 'jp_post_type' => get_post_type( $post_id ),
98 );
99
100 $list_criteria_params = array(
101 's' => sanitize_text_field( get_search_query() ),
102 'paged' => absint( get_query_var( 'paged' ) ),
103 'post_status' => sanitize_text_field( get_query_var( 'post_status' ) ),
104 'orderby' => sanitize_text_field( get_query_var( 'orderby' ) ),
105 'order' => sanitize_text_field( get_query_var( 'order' ) ),
106 'author' => absint( get_query_var( 'author' ) ),
107 'cat' => absint( get_query_var( 'cat' ) ), // 'cat' is the query var for category ID
108 'm' => absint( get_query_var( 'm' ) ), // 'm' is the query var for YYYYMM
109 'category_name' => sanitize_text_field( get_query_var( 'category_name' ) ),
110 );
111
112 foreach ( $list_criteria_params as $key => $value ) {
113 if ( isset( $_GET[ $key ] ) && $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Checking if the key existts and not reading the value from the request.
114 $query_args[ 'jp_' . $key ] = $value;
115 }
116 }
117
118 $stats_post_url = add_query_arg( $query_args, admin_url( 'admin.php?page=stats#!/stats/post/' . $post_id . '/' . \Jetpack_Options::get_option( 'id', 0 ) ) );
119 // Unless the user is on a Default style WOA site, in which case link to Calypso.
120 if ( ( new Host() )->is_woa_site() && Stats_Options::get_option( 'enable_odyssey_stats' ) && 'wp-admin' !== get_option( 'wpcom_admin_interface' ) ) {
121 $stats_post_url = Redirect::get_url(
122 'calypso-stats-post',
123 array(
124 'path' => $post_id,
125 )
126 );
127 }
128
129 /**
130 * Filters where the post list table's views column links to.
131 *
132 * Lets a newer analytics dashboard claim the entry point without this
133 * package knowing about it.
134 *
135 * @since 0.34.0
136 *
137 * @param string $stats_post_url Stats URL for the post.
138 * @param int $post_id The post the row belongs to.
139 */
140 $stats_post_url = apply_filters( 'jetpack_stats_post_list_column_url', $stats_post_url, $post_id );
141
142 static $post_views = null;
143
144 /**
145 * Jetpack_stats_get_post_page_views_for_current_list makes a request with all post ids in the current $wp_query.
146 * This way, we'll make a single API request instead of making one for each post.
147 *
148 * For this reason, we'll cache the result with the static $post_views variable.
149 */
150 if ( null === $post_views ) {
151 $post_views = $this->get_post_page_views_for_current_list();
152 }
153
154 $views = $post_views[ $post_id ] ?? null;
155
156 $current_locale = get_locale();
157
158 if ( null !== $views ) {
159 $formatted_views = class_exists( '\NumberFormatter' )
160 ? $this->get_formatter( $current_locale )->format( $views )
161 : $this->get_fallback_format_to_compact_version( $views );
162 } else {
163 $formatted_views = '';
164 }
165
166 ?>
167 <a href="<?php echo esc_url( $stats_post_url ); ?>"
168 title="<?php echo esc_html__( 'Views for the last thirty days. Click for detailed stats', 'jetpack-stats-admin' ); ?>">
169 <span
170 class="dashicons dashicons-visibility"></span>&nbsp;<span><?php echo null !== $views ? esc_html( $formatted_views ) : ''; ?></span>
171 </a>
172 <?php
173 }
174 }
175 }
176
177 /**
178 * Set header for column that allows to view an entry's stats.
179 *
180 * @param array $columns An array of column names.
181 *
182 * @return mixed
183 */
184 public function add_stats_post_table( $columns ) {
185 // Skip stats column for non-public post types when screen info is available.
186 if ( function_exists( 'get_current_screen' ) ) {
187 $screen = get_current_screen();
188 if ( $screen && $screen->post_type ) {
189 $post_type_object = get_post_type_object( $screen->post_type );
190 if ( $post_type_object && ! $post_type_object->public ) {
191 return $columns;
192 }
193 }
194 }
195
196 /**
197 * The manage_options capability is a fallback for Simple.
198 * This should be updated with a proper fix. Implemented based on this PR: https://github.com/Automattic/jetpack/pull/41549.
199 */
200 $has_access = current_user_can( 'view_stats' ) || current_user_can( 'manage_options' );
201
202 /*
203 * Stats can be accessed in wp-admin or in Calypso,
204 * depending on what version of the stats screen is enabled on your site.
205 *
206 * In both cases, the user must be allowed to access stats.
207 *
208 * If the Odyssey Stats experience isn't enabled, the user will need to go to Calypso,
209 * so they need to be connected to WordPress.com to be able to access that page.
210 */
211 if (
212 ! $has_access
213 || (
214 ! Stats_Options::get_option( 'enable_odyssey_stats' )
215 && ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected()
216 )
217 ) {
218 return $columns;
219 }
220
221 // Array-Fu to add before comments.
222 $pos = array_search( 'comments', array_keys( $columns ), true );
223
224 // Fallback to the last position if the post type does not support comments.
225 if ( ! is_int( $pos ) ) {
226 $pos = count( $columns );
227 }
228
229 // If comments position is 0, then prepend the element at the beginning of the array.
230 if ( 0 === $pos ) {
231 return array_merge(
232 array( 'stats' => esc_html__( 'Views: 30 days', 'jetpack-stats-admin' ) ),
233 $columns
234 );
235 }
236
237 $chunks = array_chunk( $columns, $pos, true );
238 $chunks[0]['stats'] = esc_html__( 'Views: 30 days', 'jetpack-stats-admin' );
239
240 return call_user_func_array( 'array_merge', $chunks );
241 }
242
243 /**
244 * Get a list of post views for each post id from the global $wp_query.
245 *
246 * @return array
247 */
248 public function get_post_page_views_for_current_list(): array {
249 global $wp_query;
250
251 if ( $wp_query->posts ) {
252 $post_ids = wp_list_pluck( $wp_query->posts, 'ID' );
253 } elseif ( wp_doing_ajax() && ! empty( $_POST['action'] ) && 'inline-save' === $_POST['action'] && ! empty( $_POST['post_ID'] ) && check_ajax_referer( 'inlineeditnonce', '_inline_edit' ) ) {
254 $post_ids = array( absint( wp_unslash( $_POST['post_ID'] ) ) );
255 } else {
256 return array();
257 }
258
259 $wpcom_stats = $this->get_stats();
260 $post_views = $wpcom_stats->get_total_post_views(
261 array(
262 'num' => 30,
263 'post_ids' => implode( ',', $post_ids ),
264 )
265 );
266
267 if ( is_wp_error( $post_views ) || empty( $post_views ) ) {
268 return array();
269 }
270
271 $views = array();
272
273 foreach ( $post_views['posts'] as $post ) {
274 $views[ $post['ID'] ] = $post['views'];
275 }
276
277 return $views;
278 }
279
280 /**
281 * Get the stats object.
282 *
283 * @return WPCOM_Stats
284 */
285 protected function get_stats() {
286 return new WPCOM_Stats();
287 }
288
289 /**
290 * Get and validate the locale.
291 *
292 * @param string $locale The locale to validate.
293 *
294 * @return string The validated locale.
295 */
296 public function get_validated_locale( string $locale ): string {
297 if ( isset( $this->locale ) ) {
298 return $this->locale;
299 }
300
301 /*
302 * Check if the locale is valid and available.
303 * If not, fallback to en_US.
304 */
305 if ( ! in_array( $locale, \IntlCalendar::getAvailableLocales(), true ) ) {
306 $locale = 'en_US';
307 }
308
309 $this->locale = $locale;
310 return $locale;
311 }
312
313 /**
314 * Get the NumberFormatter instance.
315 *
316 * @param string $locale The current locale.
317 *
318 * @return NumberFormatter
319 */
320 protected function get_formatter( string $locale ): \NumberFormatter {
321 if ( isset( $this->formatter[ $locale ] ) ) {
322 return $this->formatter[ $locale ];
323 }
324
325 $locale = $this->get_validated_locale( $locale );
326
327 /**
328 * PHP's NumberFormatter is just a wrapper over the ICU C library. The library does support decimal compact short formatter, but PHP doesn't have a stub for it (=< PHP 8.4).
329 *
330 * @see https://unicode-org.github.io/icu-docs/apidoc/dev/icu4c/unum_8h.html UNUM_DECIMAL_COMPACT_SHORT constant.
331 */
332 $compact_decimal_short = 14;
333
334 /**
335 * NumberFormatter::DECIMAL_COMPACT_SHORT only exists in PHP 8.5 and later. At this time, NumberFormatter::DECIMAL_COMPACT_SHORT only exists in PHP `main` branch.
336 *
337 * Use the constant if it's defined since it's safer.
338 */
339 if ( defined( '\NumberFormatter::DECIMAL_COMPACT_SHORT' ) ) {
340 // @phan-suppress-next-line PhanUndeclaredConstantOfClass
341 $compact_decimal_short = NumberFormatter::DECIMAL_COMPACT_SHORT;
342 }
343
344 try {
345 $formatter = new \NumberFormatter( $locale, $compact_decimal_short );
346 $formatter->setAttribute( \NumberFormatter::MAX_FRACTION_DIGITS, 1 );
347 } catch ( \Exception $e ) {
348 // Fallback to decimal if for some reason it fails to work.
349 $formatter = new \NumberFormatter( $locale, \NumberFormatter::DECIMAL );
350 }
351
352 $this->formatter[ $locale ] = $formatter;
353
354 return $formatter;
355 }
356
357 /**
358 * Fallback Format a number to a compact version if the Intl extension is not available.
359 *
360 * @param int $views The given number.
361 *
362 * @return string
363 */
364 public function get_fallback_format_to_compact_version( $views ) {
365 if ( $views >= 10000000 ) {
366 return round( $views / 1000000 ) . 'M';
367 } elseif ( $views >= 1000000 ) {
368 $views = round( $views / 1000000, 1 );
369 return preg_replace( '/\.0$/', '', (string) $views ) . 'M';
370 } elseif ( $views >= 10000 ) {
371 return round( $views / 1000 ) . 'K';
372 } elseif ( $views >= 1000 ) {
373 $views = round( $views / 1000, 1 );
374 return preg_replace( '/\.0$/', '', (string) $views ) . 'K';
375 }
376
377 return (string) $views;
378 }
379 }
380