PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
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 / modules / seo-tools / class-jetpack-seo-admin-columns.php
jetpack / modules / seo-tools Last commit date
class-jetpack-seo-admin-columns.php 1 week ago class-jetpack-seo-posts.php 2 months ago class-jetpack-seo-titles.php 4 weeks ago class-jetpack-seo-utils.php 9 months ago class-jetpack-seo.php 3 months ago
class-jetpack-seo-admin-columns.php
259 lines
1 <?php
2 /**
3 * Adds factual SEO columns to wp-admin post list tables.
4 *
5 * Surfaces the per-post SEO *state* at a glance — schema type, whether a meta
6 * description is set, and search visibility — without grading it. Whether a
7 * given setting should be configured depends on the post's purpose, so we
8 * report facts and let the author decide.
9 *
10 * @package automattic/jetpack
11 */
12
13 /**
14 * Registers read-only SEO columns on every public post-list table.
15 */
16 class Jetpack_SEO_Admin_Columns {
17
18 /**
19 * The column IDs this class registers on the post-list tables.
20 *
21 * @var string[]
22 */
23 const COLUMNS = array( 'jetpack_seo_schema', 'jetpack_seo_description', 'jetpack_seo_search' );
24
25 /**
26 * User meta listing the post-list screen IDs whose saved hidden-column set
27 * has already been backfilled with the SEO columns.
28 */
29 const BACKFILL_USER_META_KEY = 'jetpack_seo_columns_backfilled_screens';
30
31 /**
32 * Wire all hooks.
33 *
34 * @return void
35 */
36 public static function init() {
37 add_action( 'admin_init', array( __CLASS__, 'register_columns_for_post_types' ) );
38 add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
39 add_filter( 'default_hidden_columns', array( __CLASS__, 'default_hidden_columns' ), 10, 2 );
40 add_action( 'current_screen', array( __CLASS__, 'backfill_hidden_columns' ) );
41 }
42
43 /**
44 * Hide the SEO columns by default in Screen Options.
45 *
46 * Three extra always-on columns squeeze the title column unreadably narrow,
47 * so we default them to hidden using core's `default_hidden_columns` filter —
48 * the standard mechanism for choosing which columns start hidden in Screen
49 * Options. It only reaches users who have never customized Screen Options for
50 * the screen; everyone else is handled by self::backfill_hidden_columns().
51 *
52 * @param string[] $hidden Column IDs hidden by default.
53 * @param WP_Screen $screen Current screen.
54 * @return string[]
55 */
56 public static function default_hidden_columns( $hidden, $screen ) {
57 if ( isset( $screen->base ) && 'edit' === $screen->base ) {
58 $hidden = array_merge( $hidden, self::COLUMNS );
59 }
60 return $hidden;
61 }
62
63 /**
64 * Hide the SEO columns for users whose Screen Options predate them.
65 *
66 * `default_hidden_columns` is a first-run default: core only consults it when
67 * the user has no saved hidden-column set for the screen (`$use_defaults =
68 * ! is_array( $hidden )` in wp-admin/includes/screen.php). Toggling any single
69 * column checkbox writes that user meta, so a user who customized Screen
70 * Options at any point — years before these columns existed — never gets the
71 * default and sees all three columns crowding the title column.
72 *
73 * Backfill those users once per screen by merging the SEO columns into the set
74 * they already have, the way core seeds its own nav-menu column defaults in
75 * wp-admin/includes/nav-menu.php. Screens are recorded as they're visited, so a
76 * post type registered later still gets handled the first time it's opened, and
77 * a user who turns the columns back on afterwards keeps them.
78 *
79 * @param WP_Screen $screen Current screen.
80 * @return void
81 */
82 public static function backfill_hidden_columns( $screen ) {
83 if ( ! ( $screen instanceof WP_Screen ) || 'edit' !== $screen->base ) {
84 return;
85 }
86
87 $user_id = get_current_user_id();
88 if ( ! $user_id ) {
89 return;
90 }
91
92 $backfilled = get_user_meta( $user_id, self::BACKFILL_USER_META_KEY, true );
93 $backfilled = is_array( $backfilled ) ? $backfilled : array();
94 if ( in_array( $screen->id, $backfilled, true ) ) {
95 return;
96 }
97
98 $option_name = 'manage' . $screen->id . 'columnshidden';
99 $hidden = get_user_option( $option_name, $user_id );
100
101 /*
102 * A non-array means the user has never customized this screen, so
103 * `default_hidden_columns` already hides the columns for them and there is
104 * nothing to backfill — just record the screen so we stop looking.
105 */
106 if ( is_array( $hidden ) ) {
107 $missing = array_values( array_diff( self::COLUMNS, $hidden ) );
108 if ( ! empty( $missing ) ) {
109 /*
110 * $is_global = true writes the unprefixed key — the same one core's
111 * `hidden-columns` AJAX handler writes and that the get_user_option()
112 * read above falls back to. Passing false would write a blog-prefixed
113 * key that core itself never updates, so the two would diverge.
114 */
115 update_user_option( $user_id, $option_name, array_values( array_merge( $hidden, $missing ) ), true );
116 }
117 }
118
119 $backfilled[] = $screen->id;
120 update_user_meta( $user_id, self::BACKFILL_USER_META_KEY, $backfilled );
121 }
122
123 /**
124 * Register columns + renderers for each supported post type.
125 *
126 * @return void
127 */
128 public static function register_columns_for_post_types() {
129 foreach ( self::get_supported_post_types() as $post_type ) {
130 add_filter( "manage_{$post_type}_posts_columns", array( __CLASS__, 'add_columns' ) );
131 add_action( "manage_{$post_type}_posts_custom_column", array( __CLASS__, 'render_column' ), 10, 2 );
132 }
133 }
134
135 /**
136 * Post types that get the SEO columns.
137 *
138 * Defers to the SEO package so the columns cover exactly the post types the
139 * Jetpack > SEO Content tab lists, rather than a second copy of the same query
140 * that can drift from it. The guard covers older bundled snapshots of the
141 * package that predate Post_Types.
142 *
143 * @return string[]
144 */
145 private static function get_supported_post_types() {
146 if ( method_exists( '\Automattic\Jetpack\SEO\Post_Types', 'get_supported_content_types' ) ) {
147 return \Automattic\Jetpack\SEO\Post_Types::get_supported_content_types();
148 }
149
150 $post_types = get_post_types(
151 array(
152 'public' => true,
153 'show_ui' => true,
154 'show_in_rest' => true,
155 ),
156 'names'
157 );
158 unset( $post_types['attachment'] );
159
160 return array_values( $post_types );
161 }
162
163 /**
164 * Insert the SEO columns just after the title column.
165 *
166 * @param array $columns Existing columns keyed by column name.
167 * @return array
168 */
169 public static function add_columns( $columns ) {
170 $new = array();
171 foreach ( $columns as $key => $label ) {
172 $new[ $key ] = $label;
173 if ( 'title' === $key ) {
174 $new['jetpack_seo_schema'] = __( 'Schema', 'jetpack' );
175 $new['jetpack_seo_description'] = __( 'Meta description', 'jetpack' );
176 $new['jetpack_seo_search'] = __( 'Search', 'jetpack' );
177 }
178 }
179 return $new;
180 }
181
182 /**
183 * Render a single cell — factual state only.
184 *
185 * @param string $column Column identifier.
186 * @param int $post_id Current row post ID.
187 * @return void
188 */
189 public static function render_column( $column, $post_id ) {
190 if ( ! in_array( $column, self::COLUMNS, true ) ) {
191 return;
192 }
193
194 $coverage = Jetpack_SEO_Posts::get_post_seo_coverage( $post_id );
195
196 switch ( $column ) {
197 case 'jetpack_seo_schema':
198 $schema = Jetpack_SEO_Posts::get_post_schema_type( $post_id );
199 echo esc_html( '' !== $schema ? self::schema_type_label( $schema ) : '' );
200 break;
201
202 case 'jetpack_seo_description':
203 // wp_kses_post() sanitizes the markup and signals the escaping to PHPCS;
204 // the muted branch wraps its (already-escaped) label in a <span>.
205 echo wp_kses_post(
206 $coverage['has_description']
207 ? esc_html__( 'Set', 'jetpack' )
208 : '<span class="jetpack-seo-col-muted">' . esc_html__( 'Not set', 'jetpack' ) . '</span>'
209 );
210 break;
211
212 case 'jetpack_seo_search':
213 echo wp_kses_post(
214 $coverage['noindex']
215 ? esc_html__( 'Hidden', 'jetpack' )
216 : '<span class="jetpack-seo-col-muted">' . esc_html__( 'Visible', 'jetpack' ) . '</span>'
217 );
218 break;
219 }
220 }
221
222 /**
223 * Display label for an allowed schema type.
224 *
225 * @param string $schema Schema type slug.
226 * @return string
227 */
228 private static function schema_type_label( $schema ) {
229 switch ( $schema ) {
230 case 'article':
231 return __( 'Article', 'jetpack' );
232 case 'faq':
233 return __( 'FAQ', 'jetpack' );
234 default:
235 return ucfirst( $schema );
236 }
237 }
238
239 /**
240 * Minimal column-width styling on edit.php only (no color-coding —
241 * these columns report state, not a grade).
242 *
243 * @param string $hook_suffix Current admin hook suffix.
244 * @return void
245 */
246 public static function enqueue_assets( $hook_suffix ) {
247 if ( 'edit.php' !== $hook_suffix ) {
248 return;
249 }
250 wp_register_style( 'jetpack-seo-admin-columns', false, array(), JETPACK__VERSION );
251 wp_add_inline_style(
252 'jetpack-seo-admin-columns',
253 '.column-jetpack_seo_schema,.column-jetpack_seo_description,.column-jetpack_seo_search{width:9em}' .
254 '.jetpack-seo-col-muted{color:#787c82}'
255 );
256 wp_enqueue_style( 'jetpack-seo-admin-columns' );
257 }
258 }
259