PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.1-beta
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-titles.php
jetpack / modules / seo-tools Last commit date
class-jetpack-seo-admin-columns.php 1 month ago class-jetpack-seo-posts.php 1 month ago class-jetpack-seo-titles.php 2 weeks ago class-jetpack-seo-utils.php 8 months ago class-jetpack-seo.php 2 months ago
class-jetpack-seo-titles.php
358 lines
1 <?php
2 /**
3 * Class containing utility static methods for managing SEO custom title formats.
4 *
5 * @package automattic/jetpack
6 */
7
8 /*
9 * Each title format is an array of arrays containing two values:
10 * - type
11 * - value
12 *
13 * Possible values for type are: 'token' and 'string'.
14 * Possible values for 'value' are: any string in case that 'type' is set
15 * to 'string', or allowed token values for page type in case that 'type'
16 * is set to 'token'.
17 *
18 * Examples of valid formats:
19 *
20 * [
21 * 'front_page' => [
22 * [ 'type' => 'string', 'value' => 'Front page title and site name:'],
23 * [ 'type' => 'token', 'value' => 'site_name']
24 * ],
25 * 'posts' => [
26 * [ 'type' => 'token', 'value' => 'site_name' ],
27 * [ 'type' => 'string', 'value' => ' | ' ],
28 * [ 'type' => 'token', 'value' => 'post_title' ]
29 * ],
30 * 'pages' => [],
31 * 'groups' => [],
32 * 'archives' => []
33 * ]
34 * Custom title for given page type is created by concatenating all of the array 'value' parts.
35 * Tokens are replaced with their corresponding values for current site.
36 * Empty array signals that we are not overriding the default title for particular page type.
37 */
38
39 /**
40 * Class containing utility static methods for managing SEO custom title formats.
41 */
42 class Jetpack_SEO_Titles {
43 /**
44 * Site option name used to store custom title formats.
45 */
46 const TITLE_FORMATS_OPTION = 'advanced_seo_title_formats';
47
48 /**
49 * Retrieves custom title formats from site option.
50 *
51 * @return array Array of custom title formats, or empty array.
52 */
53 public static function get_custom_title_formats() {
54 if ( Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
55 return get_option( self::TITLE_FORMATS_OPTION, array() );
56 }
57
58 return array();
59 }
60
61 /**
62 * Returns tokens that are currently supported for each page type.
63 *
64 * @return array Array of allowed token strings.
65 */
66 public static function get_allowed_tokens() {
67 return array(
68 'front_page' => array( 'site_name', 'tagline' ),
69 'posts' => array( 'site_name', 'tagline', 'post_title' ),
70 'pages' => array( 'site_name', 'tagline', 'page_title' ),
71 'groups' => array( 'site_name', 'tagline', 'group_title' ),
72 'archives' => array( 'site_name', 'tagline', 'date', 'archive_title' ),
73 );
74 }
75
76 /**
77 * Used to modify the default title with custom SEO title.
78 *
79 * @param string $default_title Default title for current page.
80 *
81 * @return string A custom per-post title, custom title structure with replaced tokens, or default title.
82 */
83 public static function get_custom_title( $default_title = '' ) {
84 // Don't filter title for unsupported themes.
85 if ( self::is_conflicted_theme() ) {
86 return $default_title;
87 }
88
89 $page_type = self::get_page_type();
90
91 // Keep default title if invalid page type is supplied.
92 if ( empty( $page_type ) ) {
93 return $default_title;
94 }
95
96 if ( ! Jetpack_SEO_Utils::is_enabled_jetpack_seo() ) {
97 return $default_title;
98 }
99
100 // If it's a singular -- page or post -- check for a meta title override. Also
101 // check a static front page, where get_post() returns that page. On a latest-posts
102 // homepage it would instead return the first post in the loop, letting the newest
103 // post's SEO title hijack the homepage title.
104 $check_post_meta = 'pages' === $page_type
105 || 'posts' === $page_type
106 || ( 'front_page' === $page_type && 'page' === get_option( 'show_on_front' ) );
107
108 if ( $check_post_meta ) {
109 $post = get_post();
110 if ( $post instanceof WP_Post ) {
111 $custom_title = get_post_meta( $post->ID, Jetpack_SEO_Posts::HTML_TITLE_META_KEY, true );
112 if ( ! empty( trim( $custom_title ) ) ) {
113 return esc_html( $custom_title );
114 }
115 }
116 }
117
118 $title_formats = self::get_custom_title_formats();
119
120 // Keep default title if user has not defined custom title for this page type.
121 if ( empty( $title_formats[ $page_type ] ) ) {
122 return $default_title;
123 }
124
125 $custom_title = '';
126 $format_array = $title_formats[ $page_type ];
127
128 foreach ( $format_array as $item ) {
129 if ( 'token' === $item['type'] ) {
130 $custom_title .= self::get_token_value( $item['value'] );
131 } else {
132 $custom_title .= $item['value'];
133 }
134 }
135
136 return esc_html( $custom_title );
137 }
138
139 /**
140 * Returns string value for given token.
141 *
142 * @param string $token_name The token name value that should be replaced.
143 *
144 * @return string Token replacement for current site, or empty string for unknown token name.
145 */
146 public static function get_token_value( $token_name ) {
147
148 switch ( $token_name ) {
149 case 'site_name':
150 return get_bloginfo( 'name' );
151
152 case 'tagline':
153 return get_bloginfo( 'description' );
154
155 case 'post_title':
156 case 'page_title':
157 return the_title_attribute( array( 'echo' => false ) );
158
159 case 'group_title':
160 return single_tag_title( '', false );
161
162 case 'date':
163 case 'archive_title':
164 return self::get_archive_title();
165
166 default:
167 return '';
168 }
169 }
170
171 /**
172 * Returns page type for current page. We need this helper in order to determine what
173 * user defined title format should be used for custom title.
174 *
175 * @return string|bool Type of current page or false if unsupported.
176 */
177 public static function get_page_type() {
178
179 if ( is_front_page() ) {
180 return 'front_page';
181 }
182
183 if ( is_category() || is_tag() || is_tax() ) {
184 return 'groups';
185 }
186
187 if ( is_archive() && ! is_author() ) {
188 return 'archives';
189 }
190
191 if ( is_page() ) {
192 return 'pages';
193 }
194
195 if ( is_singular() ) {
196 return 'posts';
197 }
198
199 return false;
200 }
201
202 /**
203 * Returns the value that should be used as a replacement for the `date` or `archive_title` tokens.
204 * For date-based archives, a date is returned. Otherwise the `post_type_archive_title` is returned.
205 *
206 * The `archive_title` token was added after the `date` token to provide a more generic option
207 * that would work for non date-based archives.
208 *
209 * @return string Token replaced string.
210 */
211 public static function get_archive_title() {
212 // If archive year, month, and day are specified.
213 if ( is_day() ) {
214 return get_the_date();
215 }
216
217 // If archive year, and month are specified.
218 if ( is_month() ) {
219 return trim( single_month_title( ' ', false ) );
220 }
221
222 // Only archive year is specified.
223 if ( is_year() ) {
224 return get_query_var( 'year' );
225 }
226
227 // Not a date based archive.
228 // An example would be "Projects" for Jetpack's Portoflio CPT.
229 return post_type_archive_title( '', false );
230 }
231
232 /**
233 * Checks if current theme is defining custom title that won't work nicely
234 * with our custom SEO title override.
235 *
236 * @return bool True if current theme sets custom title, false otherwise.
237 */
238 public static function is_conflicted_theme() {
239 /**
240 * Can be used to specify a list of themes that use their own custom title format.
241 *
242 * If current site is using one of the themes listed as conflicting,
243 * Jetpack SEO custom title formats will be disabled.
244 *
245 * @module seo-tools
246 *
247 * @since 4.4.0
248 *
249 * @param array List of conflicted theme names. Defaults to empty array.
250 */
251 $conflicted_themes = apply_filters( 'jetpack_seo_custom_title_conflicted_themes', array() );
252
253 return isset( $conflicted_themes[ get_option( 'template' ) ] );
254 }
255
256 /**
257 * Checks if a given format conforms to predefined SEO title templates.
258 *
259 * Every format type and token must be specifically allowed.
260 *
261 * @see get_allowed_tokens()
262 *
263 * @param array $title_formats Template of SEO title to check.
264 *
265 * @return bool True if the formats are valid, false otherwise.
266 */
267 public static function are_valid_title_formats( $title_formats ) {
268 $allowed_tokens = self::get_allowed_tokens();
269
270 if ( ! is_array( $title_formats ) ) {
271 return false;
272 }
273
274 foreach ( $title_formats as $format_type => $format_array ) {
275 if ( ! array_key_exists( $format_type, $allowed_tokens ) ) {
276 return false;
277 }
278
279 if ( '' === $format_array ) {
280 continue;
281 }
282
283 if ( ! is_array( $format_array ) ) {
284 return false;
285 }
286
287 foreach ( $format_array as $item ) {
288 if ( empty( $item['type'] ) || empty( $item['value'] ) ) {
289 return false;
290 }
291
292 if ( 'token' === $item['type'] ) {
293 if ( ! in_array( $item['value'], $allowed_tokens[ $format_type ], true ) ) {
294 return false;
295 }
296 }
297 }
298 }
299
300 return true;
301 }
302
303 /**
304 * Sanitizes the arbitrary user input strings for custom SEO titles.
305 *
306 * @param array $title_formats Array of custom title formats.
307 *
308 * @return array The sanitized array.
309 */
310 public static function sanitize_title_formats( $title_formats ) {
311 foreach ( $title_formats as &$format_array ) {
312 foreach ( $format_array as &$item ) {
313 if ( 'string' === $item['type'] ) {
314 // From `wp_strip_all_tags`, but omitting the `trim` portion since we want spacing preserved.
315 $item['value'] = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $item['value'] );
316 $item['value'] = strip_tags( $item['value'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
317 $item['value'] = preg_replace( '/[\r\n\t ]+/', ' ', $item['value'] );
318 }
319 }
320 }
321 unset( $format_array );
322 unset( $item );
323
324 return $title_formats;
325 }
326
327 /**
328 * Combines the previous values of title formats, stored as array in site options,
329 * with the new values that are provided.
330 *
331 * @param array $new_formats Array containing new title formats.
332 *
333 * @return array $result Array of updated title formats, or empty array if no update was performed.
334 */
335 public static function update_title_formats( $new_formats ) {
336 $new_formats = self::sanitize_title_formats( $new_formats );
337
338 // Empty array signals that custom title shouldn't be used.
339 $empty_formats = array(
340 'front_page' => array(),
341 'posts' => array(),
342 'pages' => array(),
343 'groups' => array(),
344 'archives' => array(),
345 );
346
347 $previous_formats = self::get_custom_title_formats();
348
349 $result = array_merge( $empty_formats, $previous_formats, $new_formats );
350
351 if ( update_option( self::TITLE_FORMATS_OPTION, $result ) ) {
352 return $result;
353 }
354
355 return array();
356 }
357 }
358