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-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 2 days ago class-dashboard.php 2 days ago class-main.php 2 days ago class-notices.php 8 months ago class-odyssey-assets.php 2 weeks ago class-odyssey-config-data.php 2 days ago class-rest-controller.php 1 month ago class-wp-dashboard-odyssey-widget.php 2 years ago class-wpcom-client.php 2 days ago
class-admin-post-list-column.php
367 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 static $post_views = null;
130
131 /**
132 * Jetpack_stats_get_post_page_views_for_current_list makes a request with all post ids in the current $wp_query.
133 * This way, we'll make a single API request instead of making one for each post.
134 *
135 * For this reason, we'll cache the result with the static $post_views variable.
136 */
137 if ( null === $post_views ) {
138 $post_views = $this->get_post_page_views_for_current_list();
139 }
140
141 $views = $post_views[ $post_id ] ?? null;
142
143 $current_locale = get_locale();
144
145 if ( null !== $views ) {
146 $formatted_views = class_exists( '\NumberFormatter' )
147 ? $this->get_formatter( $current_locale )->format( $views )
148 : $this->get_fallback_format_to_compact_version( $views );
149 } else {
150 $formatted_views = '';
151 }
152
153 ?>
154 <a href="<?php echo esc_url( $stats_post_url ); ?>"
155 title="<?php echo esc_html__( 'Views for the last thirty days. Click for detailed stats', 'jetpack-stats-admin' ); ?>">
156 <span
157 class="dashicons dashicons-visibility"></span>&nbsp;<span><?php echo null !== $views ? esc_html( $formatted_views ) : ''; ?></span>
158 </a>
159 <?php
160 }
161 }
162 }
163
164 /**
165 * Set header for column that allows to view an entry's stats.
166 *
167 * @param array $columns An array of column names.
168 *
169 * @return mixed
170 */
171 public function add_stats_post_table( $columns ) {
172 // Skip stats column for non-public post types when screen info is available.
173 if ( function_exists( 'get_current_screen' ) ) {
174 $screen = get_current_screen();
175 if ( $screen && $screen->post_type ) {
176 $post_type_object = get_post_type_object( $screen->post_type );
177 if ( $post_type_object && ! $post_type_object->public ) {
178 return $columns;
179 }
180 }
181 }
182
183 /**
184 * The manage_options capability is a fallback for Simple.
185 * This should be updated with a proper fix. Implemented based on this PR: https://github.com/Automattic/jetpack/pull/41549.
186 */
187 $has_access = current_user_can( 'view_stats' ) || current_user_can( 'manage_options' );
188
189 /*
190 * Stats can be accessed in wp-admin or in Calypso,
191 * depending on what version of the stats screen is enabled on your site.
192 *
193 * In both cases, the user must be allowed to access stats.
194 *
195 * If the Odyssey Stats experience isn't enabled, the user will need to go to Calypso,
196 * so they need to be connected to WordPress.com to be able to access that page.
197 */
198 if (
199 ! $has_access
200 || (
201 ! Stats_Options::get_option( 'enable_odyssey_stats' )
202 && ! ( new Connection_Manager( 'jetpack' ) )->is_user_connected()
203 )
204 ) {
205 return $columns;
206 }
207
208 // Array-Fu to add before comments.
209 $pos = array_search( 'comments', array_keys( $columns ), true );
210
211 // Fallback to the last position if the post type does not support comments.
212 if ( ! is_int( $pos ) ) {
213 $pos = count( $columns );
214 }
215
216 // If comments position is 0, then prepend the element at the beginning of the array.
217 if ( 0 === $pos ) {
218 return array_merge(
219 array( 'stats' => esc_html__( 'Views: 30 days', 'jetpack-stats-admin' ) ),
220 $columns
221 );
222 }
223
224 $chunks = array_chunk( $columns, $pos, true );
225 $chunks[0]['stats'] = esc_html__( 'Views: 30 days', 'jetpack-stats-admin' );
226
227 return call_user_func_array( 'array_merge', $chunks );
228 }
229
230 /**
231 * Get a list of post views for each post id from the global $wp_query.
232 *
233 * @return array
234 */
235 public function get_post_page_views_for_current_list(): array {
236 global $wp_query;
237
238 if ( $wp_query->posts ) {
239 $post_ids = wp_list_pluck( $wp_query->posts, 'ID' );
240 } elseif ( wp_doing_ajax() && ! empty( $_POST['action'] ) && 'inline-save' === $_POST['action'] && ! empty( $_POST['post_ID'] ) && check_ajax_referer( 'inlineeditnonce', '_inline_edit' ) ) {
241 $post_ids = array( absint( wp_unslash( $_POST['post_ID'] ) ) );
242 } else {
243 return array();
244 }
245
246 $wpcom_stats = $this->get_stats();
247 $post_views = $wpcom_stats->get_total_post_views(
248 array(
249 'num' => 30,
250 'post_ids' => implode( ',', $post_ids ),
251 )
252 );
253
254 if ( is_wp_error( $post_views ) || empty( $post_views ) ) {
255 return array();
256 }
257
258 $views = array();
259
260 foreach ( $post_views['posts'] as $post ) {
261 $views[ $post['ID'] ] = $post['views'];
262 }
263
264 return $views;
265 }
266
267 /**
268 * Get the stats object.
269 *
270 * @return WPCOM_Stats
271 */
272 protected function get_stats() {
273 return new WPCOM_Stats();
274 }
275
276 /**
277 * Get and validate the locale.
278 *
279 * @param string $locale The locale to validate.
280 *
281 * @return string The validated locale.
282 */
283 public function get_validated_locale( string $locale ): string {
284 if ( isset( $this->locale ) ) {
285 return $this->locale;
286 }
287
288 /*
289 * Check if the locale is valid and available.
290 * If not, fallback to en_US.
291 */
292 if ( ! in_array( $locale, \IntlCalendar::getAvailableLocales(), true ) ) {
293 $locale = 'en_US';
294 }
295
296 $this->locale = $locale;
297 return $locale;
298 }
299
300 /**
301 * Get the NumberFormatter instance.
302 *
303 * @param string $locale The current locale.
304 *
305 * @return NumberFormatter
306 */
307 protected function get_formatter( string $locale ): \NumberFormatter {
308 if ( isset( $this->formatter[ $locale ] ) ) {
309 return $this->formatter[ $locale ];
310 }
311
312 $locale = $this->get_validated_locale( $locale );
313
314 /**
315 * 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).
316 *
317 * @see https://unicode-org.github.io/icu-docs/apidoc/dev/icu4c/unum_8h.html UNUM_DECIMAL_COMPACT_SHORT constant.
318 */
319 $compact_decimal_short = 14;
320
321 /**
322 * 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.
323 *
324 * Use the constant if it's defined since it's safer.
325 */
326 if ( defined( '\NumberFormatter::DECIMAL_COMPACT_SHORT' ) ) {
327 // @phan-suppress-next-line PhanUndeclaredConstantOfClass
328 $compact_decimal_short = NumberFormatter::DECIMAL_COMPACT_SHORT;
329 }
330
331 try {
332 $formatter = new \NumberFormatter( $locale, $compact_decimal_short );
333 $formatter->setAttribute( \NumberFormatter::MAX_FRACTION_DIGITS, 1 );
334 } catch ( \Exception $e ) {
335 // Fallback to decimal if for some reason it fails to work.
336 $formatter = new \NumberFormatter( $locale, \NumberFormatter::DECIMAL );
337 }
338
339 $this->formatter[ $locale ] = $formatter;
340
341 return $formatter;
342 }
343
344 /**
345 * Fallback Format a number to a compact version if the Intl extension is not available.
346 *
347 * @param int $views The given number.
348 *
349 * @return string
350 */
351 public function get_fallback_format_to_compact_version( $views ) {
352 if ( $views >= 10000000 ) {
353 return round( $views / 1000000 ) . 'M';
354 } elseif ( $views >= 1000000 ) {
355 $views = round( $views / 1000000, 1 );
356 return preg_replace( '/\.0$/', '', (string) $views ) . 'M';
357 } elseif ( $views >= 10000 ) {
358 return round( $views / 1000 ) . 'K';
359 } elseif ( $views >= 1000 ) {
360 $views = round( $views / 1000, 1 );
361 return preg_replace( '/\.0$/', '', (string) $views ) . 'K';
362 }
363
364 return (string) $views;
365 }
366 }
367