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