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-seo / src / class-author-schema-node.php

class-author-schema-node.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.5, at jetpack_vendor/automattic/jetpack-seo/src/class-author-schema-node.php

281 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Author Person / ProfilePage Schema.org node builder.
4 *
5 * @package automattic/jetpack-seo-package
6 */
7
8 namespace Automattic\Jetpack\SEO;
9
10 use WP_User;
11
12 /**
13 * Builds author Person / ProfilePage nodes and owns the extra author profile meta.
14 */
15 class Author_Schema_Node {
16
17 /**
18 * User meta key for the author's job title.
19 */
20 const META_JOB_TITLE = 'jetpack_seo_job_title';
21
22 /**
23 * User meta key for profile links emitted as Person `sameAs`.
24 */
25 const META_SAME_AS = 'jetpack_seo_same_as';
26
27 /**
28 * Wire the WP user-profile fields.
29 *
30 * @return void
31 */
32 public static function init() {
33 register_meta(
34 'user',
35 self::META_JOB_TITLE,
36 array(
37 'type' => 'string',
38 'single' => true,
39 'default' => '',
40 'sanitize_callback' => 'sanitize_text_field',
41 'show_in_rest' => true,
42 )
43 );
44 register_meta(
45 'user',
46 self::META_SAME_AS,
47 array(
48 'type' => 'array',
49 'single' => true,
50 'default' => array(),
51 'sanitize_callback' => array( __CLASS__, 'sanitize_url_list' ),
52 'show_in_rest' => array(
53 'schema' => array(
54 'type' => 'array',
55 'items' => array(
56 'type' => 'string',
57 'format' => 'uri',
58 ),
59 ),
60 ),
61 )
62 );
63
64 add_action( 'show_user_profile', array( __CLASS__, 'render_profile_fields' ) );
65 add_action( 'edit_user_profile', array( __CLASS__, 'render_profile_fields' ) );
66 add_action( 'personal_options_update', array( __CLASS__, 'save_profile_fields' ) );
67 add_action( 'edit_user_profile_update', array( __CLASS__, 'save_profile_fields' ) );
68 }
69
70 /**
71 * Build a Person node for a WP user.
72 *
73 * @param WP_User|int|null $user User object or ID.
74 * @return array|null
75 */
76 public static function build_person( $user ) {
77 $user = self::get_user( $user );
78 if ( ! $user ) {
79 return null;
80 }
81
82 $name = self::text( $user->display_name );
83 if ( '' === $name ) {
84 return null;
85 }
86
87 $node = array(
88 '@type' => 'Person',
89 '@id' => Schema_Node_Ids::person( $user->ID, $user->user_nicename ),
90 'name' => $name,
91 );
92
93 $image = get_avatar_url( $user->ID, array( 'size' => 512 ) );
94 if ( is_string( $image ) && '' !== $image ) {
95 $node['image'] = $image;
96 }
97
98 $description = self::text( get_the_author_meta( 'description', $user->ID ) );
99 if ( '' !== $description ) {
100 $node['description'] = $description;
101 }
102
103 // Prefer the user's Website field; fall back to the author archive so the
104 // Person always carries a URL identifying the author.
105 $url = self::url( $user->user_url );
106 $node['url'] = '' !== $url ? $url : get_author_posts_url( $user->ID, $user->user_nicename );
107
108 $given_name = self::text( get_the_author_meta( 'first_name', $user->ID ) );
109 if ( '' !== $given_name ) {
110 $node['givenName'] = $given_name;
111 }
112
113 $family_name = self::text( get_the_author_meta( 'last_name', $user->ID ) );
114 if ( '' !== $family_name ) {
115 $node['familyName'] = $family_name;
116 }
117
118 $job_title = self::text( get_user_meta( $user->ID, self::META_JOB_TITLE, true ) );
119 if ( '' !== $job_title ) {
120 $node['jobTitle'] = $job_title;
121 }
122
123 $same_as = self::sanitize_url_list( get_user_meta( $user->ID, self::META_SAME_AS, true ) );
124 if ( ! empty( $same_as ) ) {
125 $node['sameAs'] = $same_as;
126 }
127
128 return $node;
129 }
130
131 /**
132 * Build a ProfilePage node for an author archive.
133 *
134 * @param WP_User|int|null $user User object or ID.
135 * @return array|null
136 */
137 public static function build_profile_page( $user ) {
138 $user = self::get_user( $user );
139 if ( ! $user || '' === self::text( $user->display_name ) ) {
140 return null;
141 }
142
143 return array(
144 '@type' => 'ProfilePage',
145 '@id' => Schema_Node_Ids::profile_page( $user->ID, $user->user_nicename ),
146 'url' => get_author_posts_url( $user->ID, $user->user_nicename ),
147 'mainEntity' => array( '@id' => Schema_Node_Ids::person( $user->ID, $user->user_nicename ) ),
148 );
149 }
150
151 /**
152 * Render the Jetpack SEO author profile fields.
153 *
154 * @param WP_User $user User being edited.
155 * @return void
156 */
157 public static function render_profile_fields( WP_User $user ) {
158 $job_field = self::META_JOB_TITLE;
159 $links_field = self::META_SAME_AS;
160 $job_title = get_user_meta( $user->ID, $job_field, true );
161 $same_as = self::sanitize_url_list( get_user_meta( $user->ID, $links_field, true ) );
162
163 printf(
164 '<h2>%1$s</h2>
165 <table class="form-table" role="presentation">
166 <tr>
167 <th><label for="%2$s">%3$s</label></th>
168 <td><input type="text" class="regular-text" name="%2$s" id="%2$s" value="%4$s" /></td>
169 </tr>
170 <tr>
171 <th><label for="%5$s">%6$s</label></th>
172 <td>
173 <textarea class="large-text code" rows="5" name="%5$s" id="%5$s">%7$s</textarea>
174 <p class="description">%8$s</p>
175 </td>
176 </tr>
177 </table>',
178 esc_html__( 'Jetpack SEO author schema', 'jetpack-seo' ),
179 esc_attr( $job_field ),
180 esc_html__( 'Job title', 'jetpack-seo' ),
181 esc_attr( $job_title ),
182 esc_attr( $links_field ),
183 esc_html__( 'Profile links', 'jetpack-seo' ),
184 esc_textarea( implode( "\n", $same_as ) ),
185 esc_html__( 'One public profile URL per line.', 'jetpack-seo' )
186 );
187 }
188
189 /**
190 * Save the Jetpack SEO author profile fields.
191 *
192 * @param int $user_id User ID.
193 * @return void
194 */
195 public static function save_profile_fields( $user_id ) {
196 if ( ! current_user_can( 'edit_user', $user_id ) ) {
197 return;
198 }
199
200 $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';
201 if ( ! wp_verify_nonce( $nonce, 'update-user_' . $user_id ) ) {
202 return;
203 }
204
205 $job_title = isset( $_POST[ self::META_JOB_TITLE ] )
206 ? self::text( sanitize_text_field( wp_unslash( $_POST[ self::META_JOB_TITLE ] ) ) )
207 : '';
208 if ( '' === $job_title ) {
209 delete_user_meta( $user_id, self::META_JOB_TITLE );
210 } else {
211 update_user_meta( $user_id, self::META_JOB_TITLE, $job_title );
212 }
213
214 $same_as = isset( $_POST[ self::META_SAME_AS ] )
215 ? preg_split( '/\r\n|\r|\n/', sanitize_textarea_field( wp_unslash( $_POST[ self::META_SAME_AS ] ) ) )
216 : array();
217 $same_as = self::sanitize_url_list( $same_as );
218 if ( empty( $same_as ) ) {
219 delete_user_meta( $user_id, self::META_SAME_AS );
220 } else {
221 update_user_meta( $user_id, self::META_SAME_AS, $same_as );
222 }
223 }
224
225 /**
226 * Normalize a list of URLs: keep unique absolute http(s) URLs. Delegates to
227 * the shared {@see Schema_Settings::sanitize_url_list()} sanitizer.
228 *
229 * @param mixed $value Raw value.
230 * @return array<int, string>
231 */
232 public static function sanitize_url_list( $value ) {
233 return Schema_Settings::sanitize_url_list( $value );
234 }
235
236 /**
237 * Resolve a WP user.
238 *
239 * @param WP_User|int|null $user User object or ID.
240 * @return WP_User|null
241 */
242 private static function get_user( $user ) {
243 if ( $user instanceof WP_User ) {
244 return $user;
245 }
246
247 // Plugins such as PublishPress Authors replace the author archive's queried
248 // object with their own class; casting that to int silently yields user 1.
249 if ( ! is_numeric( $user ) ) {
250 return null;
251 }
252
253 $user = get_userdata( (int) $user );
254 return $user instanceof WP_User ? $user : null;
255 }
256
257 /**
258 * Normalize a scalar value to trimmed plain text.
259 *
260 * @param mixed $value Raw value.
261 * @return string
262 */
263 private static function text( $value ) {
264 if ( ! is_scalar( $value ) ) {
265 return '';
266 }
267 return trim( wp_strip_all_tags( (string) $value ) );
268 }
269
270 /**
271 * Normalize an absolute http(s) URL.
272 *
273 * @param mixed $value Raw value.
274 * @return string
275 */
276 private static function url( $value ) {
277 $list = Schema_Settings::sanitize_url_list( array( $value ) );
278 return $list[0] ?? '';
279 }
280 }
281