Version Description
This minor update addresses a change in WordPress 6.0 that causes taxonomy sitemaps to crash, allows paginated deindexing to supersede forced indexing, and improves image cropping by preserving metadata.
Download this release
Release Info
Developer | Cybr |
Plugin | The SEO Framework |
Version | 4.2.5 |
Comparing to | |
See all releases |
Code changes from version 4.2.4 to 4.2.5
- autodescription.php +2 -2
- bootstrap/activation.php +1 -1
- bootstrap/deactivation.php +1 -1
- bootstrap/load.php +1 -1
- bootstrap/upgrade.php +3 -3
- inc/classes/admin-init.class.php +1 -6
- inc/classes/bridges/ajax.class.php +46 -19
- inc/classes/bridges/listtable.class.php +1 -1
- inc/classes/builders/coresitemaps/taxonomies.class.php +9 -5
- inc/classes/builders/robots/query.class.php +17 -21
- inc/classes/builders/sitemap/base.class.php +6 -0
- inc/classes/core.class.php +7 -5
- inc/classes/detect.class.php +5 -2
- inc/classes/generate-title.class.php +2 -2
- inc/classes/generate-url.class.php +1 -1
- inc/classes/internal/deprecated.class.php +2 -2
- inc/classes/post-data.class.php +4 -1
- inc/classes/sanitize.class.php +4 -18
- inc/classes/term-data.class.php +3 -0
- inc/compat/plugin-edd.php +1 -1
- inc/compat/plugin-polylang.php +4 -4
- inc/compat/plugin-woocommerce.php +2 -2
- inc/compat/plugin-wpml.php +2 -2
- inc/functions/upgrade-suggestion.php +5 -5
- inc/views/notice/persistent.php +19 -15
- inc/views/settings/metaboxes/title.php +2 -2
- lib/js/pt.js +4 -4
- lib/js/tabs.js +4 -3
- readme.txt +6 -2
autodescription.php
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
* Plugin Name: The SEO Framework
|
4 |
* Plugin URI: https://theseoframework.com/
|
5 |
* Description: An automated, advanced, accessible, unbranded and extremely fast SEO solution for your WordPress website.
|
6 |
-
* Version: 4.2.
|
7 |
* Author: The SEO Framework Team
|
8 |
* Author URI: https://theseoframework.com/
|
9 |
* License: GPLv3
|
@@ -41,7 +41,7 @@ defined( 'ABSPATH' ) or die;
|
|
41 |
*
|
42 |
* @since 2.3.5
|
43 |
*/
|
44 |
-
define( 'THE_SEO_FRAMEWORK_VERSION', '4.2.
|
45 |
|
46 |
/**
|
47 |
* The plugin Database version.
|
3 |
* Plugin Name: The SEO Framework
|
4 |
* Plugin URI: https://theseoframework.com/
|
5 |
* Description: An automated, advanced, accessible, unbranded and extremely fast SEO solution for your WordPress website.
|
6 |
+
* Version: 4.2.5
|
7 |
* Author: The SEO Framework Team
|
8 |
* Author URI: https://theseoframework.com/
|
9 |
* License: GPLv3
|
41 |
*
|
42 |
* @since 2.3.5
|
43 |
*/
|
44 |
+
define( 'THE_SEO_FRAMEWORK_VERSION', '4.2.5' );
|
45 |
|
46 |
/**
|
47 |
* The plugin Database version.
|
bootstrap/activation.php
CHANGED
@@ -71,7 +71,7 @@ function _activation_set_options_autoload() {
|
|
71 |
\remove_all_filters( "sanitize_option_{$setting}" );
|
72 |
|
73 |
$temp_options = $options;
|
74 |
-
|
75 |
if ( \is_array( $temp_options ) )
|
76 |
$temp_options['update_buster'] = (int) time();
|
77 |
|
71 |
\remove_all_filters( "sanitize_option_{$setting}" );
|
72 |
|
73 |
$temp_options = $options;
|
74 |
+
// Write a small difference, so the change will be forwarded to the database.
|
75 |
if ( \is_array( $temp_options ) )
|
76 |
$temp_options['update_buster'] = (int) time();
|
77 |
|
bootstrap/deactivation.php
CHANGED
@@ -52,7 +52,7 @@ function _deactivation_unset_options_autoload() {
|
|
52 |
\remove_all_filters( "sanitize_option_{$setting}" );
|
53 |
|
54 |
$temp_options = $options;
|
55 |
-
|
56 |
if ( \is_array( $temp_options ) )
|
57 |
$temp_options['update_buster'] = (int) time();
|
58 |
|
52 |
\remove_all_filters( "sanitize_option_{$setting}" );
|
53 |
|
54 |
$temp_options = $options;
|
55 |
+
// Write a small difference, so the change will be forwarded to the database.
|
56 |
if ( \is_array( $temp_options ) )
|
57 |
$temp_options['update_buster'] = (int) time();
|
58 |
|
bootstrap/load.php
CHANGED
@@ -148,7 +148,7 @@ function _autoload_classes( $class ) {
|
|
148 |
$_chunck_count = \count( $_chunks );
|
149 |
|
150 |
if ( $_chunck_count > 2 ) {
|
151 |
-
|
152 |
$rel_dir = implode( DIRECTORY_SEPARATOR, array_splice( $_chunks, 1, $_chunck_count - 2 ) ) . DIRECTORY_SEPARATOR;
|
153 |
} else {
|
154 |
$rel_dir = '';
|
148 |
$_chunck_count = \count( $_chunks );
|
149 |
|
150 |
if ( $_chunck_count > 2 ) {
|
151 |
+
// directory position = $_chunck_count - ( 2 = (The_SEO_Framework)\ + (Bridges/Builders/Interpreters)\ )
|
152 |
$rel_dir = implode( DIRECTORY_SEPARATOR, array_splice( $_chunks, 1, $_chunck_count - 2 ) ) . DIRECTORY_SEPARATOR;
|
153 |
} else {
|
154 |
$rel_dir = '';
|
bootstrap/upgrade.php
CHANGED
@@ -208,9 +208,9 @@ function _upgrade( $previous_version ) {
|
|
208 |
$current_version = $previous_version;
|
209 |
|
210 |
//! From update 3103 henceforth, the upgrade procedures should be backward compatible.
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
$versions = [ '1', '2701', '2802', '2900', '3001', '3103', '3300', '4051', '4103', '4110', '4120', '4200' ];
|
215 |
|
216 |
foreach ( $versions as $_version ) {
|
208 |
$current_version = $previous_version;
|
209 |
|
210 |
//! From update 3103 henceforth, the upgrade procedures should be backward compatible.
|
211 |
+
// This means no data may be erased for at least 1 major version, or 1 year, whichever is later.
|
212 |
+
// We must manually delete settings that are no longer used; we merge them otherwise.
|
213 |
+
// When a user upgrades beyond this scope, they aren't expected to roll back.
|
214 |
$versions = [ '1', '2701', '2802', '2900', '3001', '3103', '3300', '4051', '4103', '4110', '4120', '4200' ];
|
215 |
|
216 |
foreach ( $versions as $_version ) {
|
inc/classes/admin-init.class.php
CHANGED
@@ -391,11 +391,6 @@ class Admin_Init extends Init {
|
|
391 |
// Predict white screen:
|
392 |
$headers_sent = headers_sent();
|
393 |
|
394 |
-
/**
|
395 |
-
* Dev debug:
|
396 |
-
* 1. Change 302 to 500 if you wish to test headers.
|
397 |
-
* 2. Also force handle_admin_redirect_error() to run.
|
398 |
-
*/
|
399 |
\wp_safe_redirect( $target, 302 );
|
400 |
|
401 |
// White screen of death for non-debugging users. Let's make it friendlier.
|
@@ -581,7 +576,7 @@ class Admin_Init extends Init {
|
|
581 |
public function _dismiss_notice() {
|
582 |
|
583 |
// phpcs:ignore, WordPress.Security.NonceVerification.Missing -- We require the POST data to find locally stored nonces.
|
584 |
-
$key = $_POST['tsf-notice-submit'] ?? '';
|
585 |
|
586 |
if ( ! $key ) return;
|
587 |
|
391 |
// Predict white screen:
|
392 |
$headers_sent = headers_sent();
|
393 |
|
|
|
|
|
|
|
|
|
|
|
394 |
\wp_safe_redirect( $target, 302 );
|
395 |
|
396 |
// White screen of death for non-debugging users. Let's make it friendlier.
|
576 |
public function _dismiss_notice() {
|
577 |
|
578 |
// phpcs:ignore, WordPress.Security.NonceVerification.Missing -- We require the POST data to find locally stored nonces.
|
579 |
+
$key = \sanitize_key( $_POST['tsf-notice-submit'] ?? '' );
|
580 |
|
581 |
if ( ! $key ) return;
|
582 |
|
inc/classes/bridges/ajax.class.php
CHANGED
@@ -137,7 +137,9 @@ final class AJAX {
|
|
137 |
* @since 3.1.0 Introduced in 2.9.0, but the name changed.
|
138 |
* @since 4.1.4 Moved to \The_SEO_Framework\Bridges\AJAX and made static.
|
139 |
* @since 4.2.0 Now cleans response header.
|
140 |
-
* @
|
|
|
|
|
141 |
* @access private
|
142 |
*/
|
143 |
public static function _wp_ajax_crop_image() {
|
@@ -153,6 +155,9 @@ final class AJAX {
|
|
153 |
|
154 |
$attachment_id = \absint( $_POST['id'] );
|
155 |
|
|
|
|
|
|
|
156 |
$context = str_replace( '_', '-', \sanitize_key( $_POST['context'] ) );
|
157 |
$data = array_map( 'absint', $_POST['cropDetails'] );
|
158 |
$cropped = \wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
|
@@ -175,43 +180,65 @@ final class AJAX {
|
|
175 |
*/
|
176 |
\do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped );
|
177 |
|
178 |
-
/** This filter is documented in wp-admin/custom-header.php */
|
179 |
$cropped = \apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
|
180 |
|
181 |
-
$parent_url
|
182 |
-
$
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
$
|
189 |
-
|
190 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
191 |
'post_mime_type' => $image_type,
|
192 |
'guid' => $url,
|
193 |
'context' => $context,
|
194 |
];
|
195 |
|
196 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
197 |
$metadata = \wp_generate_attachment_metadata( $attachment_id, $cropped );
|
198 |
|
199 |
/**
|
200 |
-
* Filters the cropped image attachment metadata.
|
201 |
-
*
|
202 |
* @since 4.3.0 WordPress Core
|
203 |
* @see wp_generate_attachment_metadata()
|
204 |
-
*
|
205 |
* @param array $metadata Attachment metadata.
|
206 |
*/
|
207 |
$metadata = \apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata );
|
208 |
\wp_update_attachment_metadata( $attachment_id, $metadata );
|
209 |
|
210 |
/**
|
211 |
-
* Filters the attachment ID for a cropped image.
|
212 |
-
*
|
213 |
* @since 4.3.0 WordPress Core
|
214 |
-
*
|
215 |
* @param int $attachment_id The attachment ID of the cropped image.
|
216 |
* @param string $context The Customizer control requesting the cropped image.
|
217 |
*/
|
137 |
* @since 3.1.0 Introduced in 2.9.0, but the name changed.
|
138 |
* @since 4.1.4 Moved to \The_SEO_Framework\Bridges\AJAX and made static.
|
139 |
* @since 4.2.0 Now cleans response header.
|
140 |
+
* @since 4.2.5 1. Backported cropping support for WebP (WP 5.9).
|
141 |
+
* 2. Backported title, description, alt tag, and excerpt preservation (WP 6.0).
|
142 |
+
* @securitycheck 4.2.5 OK.
|
143 |
* @access private
|
144 |
*/
|
145 |
public static function _wp_ajax_crop_image() {
|
155 |
|
156 |
$attachment_id = \absint( $_POST['id'] );
|
157 |
|
158 |
+
if ( ! $attachment_id || 'attachment' !== \get_post_type( $attachment_id ) || ! \wp_attachment_is_image( $attachment_id ) )
|
159 |
+
\wp_send_json_error( [ 'message' => \esc_js( \__( 'Image could not be processed.', 'default' ) ) ] );
|
160 |
+
|
161 |
$context = str_replace( '_', '-', \sanitize_key( $_POST['context'] ) );
|
162 |
$data = array_map( 'absint', $_POST['cropDetails'] );
|
163 |
$cropped = \wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
|
180 |
*/
|
181 |
\do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped );
|
182 |
|
183 |
+
/** This filter is documented in wp-admin/includes/class-custom-image-header.php */
|
184 |
$cropped = \apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
|
185 |
|
186 |
+
$parent_url = \wp_get_attachment_url( $attachment_id );
|
187 |
+
$parent_basename = \wp_basename( $parent_url );
|
188 |
+
$cropped_basename = \wp_basename( $cropped );
|
189 |
+
$url = str_replace( $parent_basename, $cropped_basename, $parent_url );
|
190 |
+
|
191 |
+
// phpcs:ignore, WordPress.PHP.NoSilencedErrors -- See https://core.trac.wordpress.org/ticket/42480
|
192 |
+
$size = \function_exists( '\\wp_getimagesize' ) ? \wp_getimagesize( $cropped ) : @getimagesize( $cropped );
|
193 |
+
$image_type = $size ? $size['mime'] : 'image/jpeg';
|
194 |
+
|
195 |
+
// Get the original image's post to pre-populate the cropped image.
|
196 |
+
$original_attachment = \get_post( $attachment_id );
|
197 |
+
$sanitized_post_title = \sanitize_file_name( $original_attachment->post_title );
|
198 |
+
$use_original_title = (
|
199 |
+
\strlen( trim( $original_attachment->post_title ) ) &&
|
200 |
+
/**
|
201 |
+
* Check if the original image has a title other than the "filename" default,
|
202 |
+
* meaning the image had a title when originally uploaded or its title was edited.
|
203 |
+
*/
|
204 |
+
( $parent_basename !== $sanitized_post_title ) &&
|
205 |
+
( pathinfo( $parent_basename, PATHINFO_FILENAME ) !== $sanitized_post_title )
|
206 |
+
);
|
207 |
+
$use_original_description = \strlen( trim( $original_attachment->post_content ) );
|
208 |
+
|
209 |
+
$attachment = [
|
210 |
+
'post_title' => $use_original_title ? $original_attachment->post_title : $cropped_basename,
|
211 |
+
'post_content' => $use_original_description ? $original_attachment->post_content : $url,
|
212 |
'post_mime_type' => $image_type,
|
213 |
'guid' => $url,
|
214 |
'context' => $context,
|
215 |
];
|
216 |
|
217 |
+
// Copy the image caption attribute (post_excerpt field) from the original image.
|
218 |
+
if ( \strlen( trim( $original_attachment->post_excerpt ) ) ) {
|
219 |
+
$attachment['post_excerpt'] = $original_attachment->post_excerpt;
|
220 |
+
}
|
221 |
+
|
222 |
+
// Copy the image alt text attribute from the original image.
|
223 |
+
if ( \strlen( trim( $original_attachment->_wp_attachment_image_alt ) ) ) {
|
224 |
+
$attachment['meta_input'] = [
|
225 |
+
'_wp_attachment_image_alt' => \wp_slash( $original_attachment->_wp_attachment_image_alt ),
|
226 |
+
];
|
227 |
+
}
|
228 |
+
|
229 |
+
$attachment_id = \wp_insert_attachment( $attachment, $cropped );
|
230 |
$metadata = \wp_generate_attachment_metadata( $attachment_id, $cropped );
|
231 |
|
232 |
/**
|
|
|
|
|
233 |
* @since 4.3.0 WordPress Core
|
234 |
* @see wp_generate_attachment_metadata()
|
|
|
235 |
* @param array $metadata Attachment metadata.
|
236 |
*/
|
237 |
$metadata = \apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata );
|
238 |
\wp_update_attachment_metadata( $attachment_id, $metadata );
|
239 |
|
240 |
/**
|
|
|
|
|
241 |
* @since 4.3.0 WordPress Core
|
|
|
242 |
* @param int $attachment_id The attachment ID of the cropped image.
|
243 |
* @param string $context The Customizer control requesting the cropped image.
|
244 |
*/
|
inc/classes/bridges/listtable.class.php
CHANGED
@@ -200,7 +200,7 @@ abstract class ListTable {
|
|
200 |
$taxonomy = isset( $_POST['taxonomy'] ) ? stripslashes( $_POST['taxonomy'] ) : '';
|
201 |
$post_type = isset( $_POST['post_type'] ) ? stripslashes( $_POST['post_type'] ) : '';
|
202 |
|
203 |
-
|
204 |
$post_type = $post_type
|
205 |
?: ( isset( $_POST['tax_type'] ) ? stripslashes( $_POST['tax_type'] ) : '' );
|
206 |
|
200 |
$taxonomy = isset( $_POST['taxonomy'] ) ? stripslashes( $_POST['taxonomy'] ) : '';
|
201 |
$post_type = isset( $_POST['post_type'] ) ? stripslashes( $_POST['post_type'] ) : '';
|
202 |
|
203 |
+
// /wp-admin/js/inline-edit-tax.js doesn't send post_type, instead, it sends tax_type, which is the same.
|
204 |
$post_type = $post_type
|
205 |
?: ( isset( $_POST['tax_type'] ) ? stripslashes( $_POST['tax_type'] ) : '' );
|
206 |
|
inc/classes/builders/coresitemaps/taxonomies.class.php
CHANGED
@@ -40,6 +40,7 @@ class Taxonomies extends \WP_Sitemaps_Taxonomies {
|
|
40 |
* @since 4.1.2
|
41 |
* @since 4.2.0 Renamed `$taxonomy` to `$object_subtype` to match parent class
|
42 |
* for PHP 8 named parameter support. (Backport WP 5.9)
|
|
|
43 |
* @source \WP_Sitemaps_Taxonomies\get_url_list()
|
44 |
* @TEMP https://wordpress.slack.com/archives/CTKTGNJJW/p1604995479019700
|
45 |
* @link <https://core.trac.wordpress.org/ticket/51860>
|
@@ -82,21 +83,22 @@ class Taxonomies extends \WP_Sitemaps_Taxonomies {
|
|
82 |
|
83 |
$url_list = [];
|
84 |
|
85 |
-
$main = Main::get_instance();
|
86 |
-
|
87 |
// Offset by how many terms should be included in previous pages.
|
88 |
$offset = ( $page_num - 1 ) * \wp_sitemaps_get_max_urls( $this->object_type );
|
89 |
|
90 |
$args = $this->get_taxonomies_query_args( $taxonomy );
|
|
|
91 |
$args['offset'] = $offset;
|
92 |
|
93 |
$taxonomy_terms = new \WP_Term_Query( $args );
|
94 |
|
|
|
|
|
95 |
foreach ( $taxonomy_terms->terms ?? [] as $term ) :
|
96 |
/**
|
97 |
* @augmented This if-statement prevents including the term in the sitemap when conditions apply.
|
98 |
*/
|
99 |
-
if ( ! $main->is_term_included_in_sitemap( $term, $taxonomy ) )
|
100 |
continue;
|
101 |
|
102 |
$term_link = \get_term_link( $term, $taxonomy );
|
@@ -112,12 +114,14 @@ class Taxonomies extends \WP_Sitemaps_Taxonomies {
|
|
112 |
* Filters the sitemap entry for an individual term.
|
113 |
*
|
114 |
* @since WP Core 5.5.0
|
|
|
115 |
*
|
116 |
* @param array $sitemap_entry Sitemap entry for the term.
|
117 |
-
* @param
|
118 |
* @param string $taxonomy Taxonomy name.
|
|
|
119 |
*/
|
120 |
-
$sitemap_entry = \apply_filters( 'wp_sitemaps_taxonomies_entry', $sitemap_entry, $term, $taxonomy );
|
121 |
$url_list[] = $sitemap_entry;
|
122 |
endforeach;
|
123 |
|
40 |
* @since 4.1.2
|
41 |
* @since 4.2.0 Renamed `$taxonomy` to `$object_subtype` to match parent class
|
42 |
* for PHP 8 named parameter support. (Backport WP 5.9)
|
43 |
+
* @since 4.2.5 Added 'all' fields to the query, allowing caching of terms (Backport WP 6.0).
|
44 |
* @source \WP_Sitemaps_Taxonomies\get_url_list()
|
45 |
* @TEMP https://wordpress.slack.com/archives/CTKTGNJJW/p1604995479019700
|
46 |
* @link <https://core.trac.wordpress.org/ticket/51860>
|
83 |
|
84 |
$url_list = [];
|
85 |
|
|
|
|
|
86 |
// Offset by how many terms should be included in previous pages.
|
87 |
$offset = ( $page_num - 1 ) * \wp_sitemaps_get_max_urls( $this->object_type );
|
88 |
|
89 |
$args = $this->get_taxonomies_query_args( $taxonomy );
|
90 |
+
$args['fields'] = 'all'; // On WP<6.0 this is 'ids'; overwrite it. This line is a mirror of WPv6.0, too.
|
91 |
$args['offset'] = $offset;
|
92 |
|
93 |
$taxonomy_terms = new \WP_Term_Query( $args );
|
94 |
|
95 |
+
$main = Main::get_instance();
|
96 |
+
|
97 |
foreach ( $taxonomy_terms->terms ?? [] as $term ) :
|
98 |
/**
|
99 |
* @augmented This if-statement prevents including the term in the sitemap when conditions apply.
|
100 |
*/
|
101 |
+
if ( ! $main->is_term_included_in_sitemap( $term->term_id, $taxonomy ) )
|
102 |
continue;
|
103 |
|
104 |
$term_link = \get_term_link( $term, $taxonomy );
|
114 |
* Filters the sitemap entry for an individual term.
|
115 |
*
|
116 |
* @since WP Core 5.5.0
|
117 |
+
* @since WP Core 6.0.0 Added `$term` argument containing the term object.
|
118 |
*
|
119 |
* @param array $sitemap_entry Sitemap entry for the term.
|
120 |
+
* @param int $term_id Term ID.
|
121 |
* @param string $taxonomy Taxonomy name.
|
122 |
+
* @param WP_Term $term Term object.
|
123 |
*/
|
124 |
+
$sitemap_entry = \apply_filters( 'wp_sitemaps_taxonomies_entry', $sitemap_entry, $term->term_id, $taxonomy, $term );
|
125 |
$url_list[] = $sitemap_entry;
|
126 |
endforeach;
|
127 |
|
inc/classes/builders/robots/query.class.php
CHANGED
@@ -44,6 +44,8 @@ final class Query extends Factory {
|
|
44 |
* Yields true when "noindex/noarchive/nofollow", yields false when "index/archive/follow".
|
45 |
*
|
46 |
* @since 4.2.0
|
|
|
|
|
47 |
* @generator
|
48 |
*
|
49 |
* @param string $type The robots generator type (noindex, nofollow...).
|
@@ -93,16 +95,9 @@ final class Query extends Factory {
|
|
93 |
|
94 |
if ( $tsf->is_real_front_page() ) {
|
95 |
yield 'globals_homepage' => (bool) $tsf->get_option( "homepage_$type" );
|
96 |
-
|
97 |
-
if ( ! ( static::$options & \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION ) )
|
98 |
-
$asserting_noindex and yield from static::assert_noindex_query_pass( 'paged_home' );
|
99 |
} else {
|
100 |
$asserting_noindex and yield from static::assert_noindex_query_pass( '404' );
|
101 |
|
102 |
-
if ( ! ( static::$options & \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION ) )
|
103 |
-
if ( $asserting_noindex && ( $tsf->is_archive() || $tsf->is_singular_archive() ) )
|
104 |
-
yield from static::assert_noindex_query_pass( 'paged' );
|
105 |
-
|
106 |
if ( $tsf->is_archive() ) {
|
107 |
if ( $tsf->is_author() ) {
|
108 |
yield 'globals_author' => (bool) $tsf->get_option( "author_$type" );
|
@@ -134,21 +129,22 @@ final class Query extends Factory {
|
|
134 |
|
135 |
// We assert options here for a jump to index_protection might be unaware.
|
136 |
index_protection: if ( $asserting_noindex && ! ( static::$options & \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION ) ) {
|
137 |
-
if ( $tsf->
|
138 |
-
|
139 |
-
|
140 |
-
if ( $tsf->
|
141 |
-
yield from static::assert_noindex_query_pass( '
|
142 |
-
|
143 |
-
|
144 |
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
|
|
152 |
}
|
153 |
}
|
154 |
|
44 |
* Yields true when "noindex/noarchive/nofollow", yields false when "index/archive/follow".
|
45 |
*
|
46 |
* @since 4.2.0
|
47 |
+
* @since 4.2.5 1. Removed needlessly duplicate homepage test.
|
48 |
+
* 2. Moved archive pagination check to index_protection.
|
49 |
* @generator
|
50 |
*
|
51 |
* @param string $type The robots generator type (noindex, nofollow...).
|
95 |
|
96 |
if ( $tsf->is_real_front_page() ) {
|
97 |
yield 'globals_homepage' => (bool) $tsf->get_option( "homepage_$type" );
|
|
|
|
|
|
|
98 |
} else {
|
99 |
$asserting_noindex and yield from static::assert_noindex_query_pass( '404' );
|
100 |
|
|
|
|
|
|
|
|
|
101 |
if ( $tsf->is_archive() ) {
|
102 |
if ( $tsf->is_author() ) {
|
103 |
yield 'globals_author' => (bool) $tsf->get_option( "author_$type" );
|
129 |
|
130 |
// We assert options here for a jump to index_protection might be unaware.
|
131 |
index_protection: if ( $asserting_noindex && ! ( static::$options & \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION ) ) {
|
132 |
+
if ( $tsf->is_real_front_page() ) {
|
133 |
+
yield from static::assert_noindex_query_pass( 'paged_home' );
|
134 |
+
} else {
|
135 |
+
if ( $tsf->is_archive() || $tsf->is_singular_archive() ) {
|
136 |
+
yield from static::assert_noindex_query_pass( 'paged' );
|
137 |
+
} elseif ( $tsf->is_singular() ) {
|
138 |
+
yield from static::assert_noindex_query_pass( 'protected' );
|
139 |
|
140 |
+
/**
|
141 |
+
* N.B. WordPress protects this query variable with options 'page_comments'
|
142 |
+
* and 'default_comments_page' via `redirect_canonical()`, so we don't have to.
|
143 |
+
* For reference, it fires `remove_query_arg( 'cpage', $redirect['query'] )`;
|
144 |
+
*/
|
145 |
+
if ( (int) \get_query_var( 'cpage', 0 ) > 0 )
|
146 |
+
yield from static::assert_noindex_query_pass( 'cpage' );
|
147 |
+
}
|
148 |
}
|
149 |
}
|
150 |
|
inc/classes/builders/sitemap/base.class.php
CHANGED
@@ -153,6 +153,11 @@ class Base extends Main {
|
|
153 |
* 3. Renamed method from "generate_sitemap" to abstract extension "build_sitemap".
|
154 |
* 4. Moved to \The_SEO_Framework\Builders\Sitemap\Base
|
155 |
* @abstract
|
|
|
|
|
|
|
|
|
|
|
156 |
*
|
157 |
* @return string The sitemap content.
|
158 |
*/
|
@@ -223,6 +228,7 @@ class Base extends Main {
|
|
223 |
/**
|
224 |
* @since 4.0.0
|
225 |
* @param array $args The query arguments.
|
|
|
226 |
*/
|
227 |
$_args = (array) \apply_filters(
|
228 |
'the_seo_framework_sitemap_hpt_query_args',
|
153 |
* 3. Renamed method from "generate_sitemap" to abstract extension "build_sitemap".
|
154 |
* 4. Moved to \The_SEO_Framework\Builders\Sitemap\Base
|
155 |
* @abstract
|
156 |
+
* @slow The queried results are not stored in WP Post's cache, which would allow direct access
|
157 |
+
* to all values of the post (if requested). This is because we're using
|
158 |
+
* `'fields' => 'ids'` instead of `'fields' => 'all'`. However, this would fill RAM
|
159 |
+
* linearly: at 1000 posts, we'd hit 28MB already, 10 000 would be ~280MB, exceeding max.
|
160 |
+
* @link <https://w.org/support/topic/sitemap-and-memory-exhaustion/#post-13331896>
|
161 |
*
|
162 |
* @return string The sitemap content.
|
163 |
*/
|
228 |
/**
|
229 |
* @since 4.0.0
|
230 |
* @param array $args The query arguments.
|
231 |
+
* @link <https://w.org/support/topic/sitemap-and-memory-exhaustion/#post-13331896>
|
232 |
*/
|
233 |
$_args = (array) \apply_filters(
|
234 |
'the_seo_framework_sitemap_hpt_query_args',
|
inc/classes/core.class.php
CHANGED
@@ -188,7 +188,7 @@ class Core {
|
|
188 |
*/
|
189 |
public function get_view( $view, $__args = [], $instance = 'main' ) {
|
190 |
|
191 |
-
|
192 |
foreach ( $__args as $__k => $__v ) $$__k = $__v;
|
193 |
unset( $__k, $__v, $__args );
|
194 |
|
@@ -201,9 +201,11 @@ class Core {
|
|
201 |
/**
|
202 |
* Stores and returns view secret.
|
203 |
*
|
204 |
-
* This is not cryptographically secure, but it's enough to fend others off
|
205 |
-
*
|
206 |
-
*
|
|
|
|
|
207 |
*
|
208 |
* @since 4.1.1
|
209 |
*
|
@@ -632,7 +634,7 @@ class Core {
|
|
632 |
$gg = round( $g * .5870 / 8 * $rl );
|
633 |
$gb = round( $b * .1140 / 8 * $rl );
|
634 |
|
635 |
-
// Invert grayscela if they
|
636 |
if ( $rl < .5 ) {
|
637 |
$gr ^= 0xFF;
|
638 |
$gg ^= 0xFF;
|
188 |
*/
|
189 |
public function get_view( $view, $__args = [], $instance = 'main' ) {
|
190 |
|
191 |
+
// A faster extract().
|
192 |
foreach ( $__args as $__k => $__v ) $$__k = $__v;
|
193 |
unset( $__k, $__v, $__args );
|
194 |
|
201 |
/**
|
202 |
* Stores and returns view secret.
|
203 |
*
|
204 |
+
* This is not cryptographically secure, but it's enough to fend others off
|
205 |
+
* including our files where they shouldn't. Our view-files have a certain
|
206 |
+
* expectation of inputs to meet. When they don't meet that, we could expose
|
207 |
+
* our users to security issues. We could not measure any meaningful
|
208 |
+
* performance impact by using this (0.02% of 54x get_view() runtime).
|
209 |
*
|
210 |
* @since 4.1.1
|
211 |
*
|
634 |
$gg = round( $g * .5870 / 8 * $rl );
|
635 |
$gb = round( $b * .1140 / 8 * $rl );
|
636 |
|
637 |
+
// Invert grayscela if they pass the relative luminance midpoint.
|
638 |
if ( $rl < .5 ) {
|
639 |
$gr ^= 0xFF;
|
640 |
$gg ^= 0xFF;
|
inc/classes/detect.class.php
CHANGED
@@ -772,6 +772,9 @@ class Detect extends Render {
|
|
772 |
* Memoizes the return value.
|
773 |
*
|
774 |
* @since 4.2.0
|
|
|
|
|
|
|
775 |
*
|
776 |
* @param string $post_type The post type to test.
|
777 |
* @return bool True if a post is found in the archive, false otherwise.
|
@@ -965,7 +968,7 @@ class Detect extends Render {
|
|
965 |
array_unique(
|
966 |
array_merge(
|
967 |
$this->get_forced_supported_post_types(),
|
968 |
-
|
969 |
array_keys( (array) \get_post_types( [ 'public' => true ] ) )
|
970 |
)
|
971 |
),
|
@@ -1039,7 +1042,7 @@ class Detect extends Render {
|
|
1039 |
array_unique(
|
1040 |
array_merge(
|
1041 |
$this->get_forced_supported_taxonomies(),
|
1042 |
-
|
1043 |
array_values( \get_taxonomies( [
|
1044 |
'public' => true,
|
1045 |
'_builtin' => false,
|
772 |
* Memoizes the return value.
|
773 |
*
|
774 |
* @since 4.2.0
|
775 |
+
* @slow The queried result is not stored in WP Post's cache, which would allow
|
776 |
+
* direct access to all values of the post (if requested). This is because
|
777 |
+
* we're using `'fields' => 'ids'` instead of `'fields' => 'all'`.
|
778 |
*
|
779 |
* @param string $post_type The post type to test.
|
780 |
* @return bool True if a post is found in the archive, false otherwise.
|
968 |
array_unique(
|
969 |
array_merge(
|
970 |
$this->get_forced_supported_post_types(),
|
971 |
+
// array_values() because get_post_types() gives a sequential array.
|
972 |
array_keys( (array) \get_post_types( [ 'public' => true ] ) )
|
973 |
)
|
974 |
),
|
1042 |
array_unique(
|
1043 |
array_merge(
|
1044 |
$this->get_forced_supported_taxonomies(),
|
1045 |
+
// array_values() because get_taxonomies() gives a sequential array.
|
1046 |
array_values( \get_taxonomies( [
|
1047 |
'public' => true,
|
1048 |
'_builtin' => false,
|
inc/classes/generate-title.class.php
CHANGED
@@ -1033,7 +1033,7 @@ class Generate_Title extends Generate_Description {
|
|
1033 |
*/
|
1034 |
public function get_generated_single_post_title( $id = 0 ) {
|
1035 |
|
1036 |
-
|
1037 |
$_post = \get_post( $id ?: $this->get_the_real_ID() );
|
1038 |
|
1039 |
if ( isset( $_post->post_title ) ) {
|
@@ -1458,7 +1458,7 @@ class Generate_Title extends Generate_Description {
|
|
1458 |
*/
|
1459 |
public function use_title_pagination( $args = null ) {
|
1460 |
|
1461 |
-
|
1462 |
if ( null === $args ) {
|
1463 |
if ( \is_404() || \is_admin() ) {
|
1464 |
$use = false;
|
1033 |
*/
|
1034 |
public function get_generated_single_post_title( $id = 0 ) {
|
1035 |
|
1036 |
+
// Home queries can be tricky. Use get_the_real_ID to be certain.
|
1037 |
$_post = \get_post( $id ?: $this->get_the_real_ID() );
|
1038 |
|
1039 |
if ( isset( $_post->post_title ) ) {
|
1458 |
*/
|
1459 |
public function use_title_pagination( $args = null ) {
|
1460 |
|
1461 |
+
// Only add pagination if the query is autodetermined, and on a real page.
|
1462 |
if ( null === $args ) {
|
1463 |
if ( \is_404() || \is_admin() ) {
|
1464 |
$use = false;
|
inc/classes/generate-url.class.php
CHANGED
@@ -902,7 +902,7 @@ class Generate_Url extends Generate_Title {
|
|
902 |
$url = \add_query_arg( [ 'page' => $page ], $url );
|
903 |
}
|
904 |
|
905 |
-
|
906 |
if ( $this->is_singular() ) {
|
907 |
$url = $this->append_url_query(
|
908 |
$url,
|
902 |
$url = \add_query_arg( [ 'page' => $page ], $url );
|
903 |
}
|
904 |
|
905 |
+
// Append queries other plugins might've filtered.
|
906 |
if ( $this->is_singular() ) {
|
907 |
$url = $this->append_url_query(
|
908 |
$url,
|
inc/classes/internal/deprecated.class.php
CHANGED
@@ -562,7 +562,7 @@ final class Deprecated {
|
|
562 |
if ( isset( $id ) ) {
|
563 |
$is_shop = (int) \get_option( 'woocommerce_shop_page_id' ) === $id;
|
564 |
} else {
|
565 |
-
$is_shop = ! \is_admin() && \function_exists( 'is_shop' ) && \is_shop();
|
566 |
}
|
567 |
|
568 |
return $is_shop;
|
@@ -595,7 +595,7 @@ final class Deprecated {
|
|
595 |
if ( $post ) {
|
596 |
$is_product = 'product' === \get_post_type( $post );
|
597 |
} else {
|
598 |
-
$is_product = \function_exists( 'is_product' ) && \is_product();
|
599 |
}
|
600 |
|
601 |
return $is_product;
|
562 |
if ( isset( $id ) ) {
|
563 |
$is_shop = (int) \get_option( 'woocommerce_shop_page_id' ) === $id;
|
564 |
} else {
|
565 |
+
$is_shop = ! \is_admin() && \function_exists( '\\is_shop' ) && \is_shop();
|
566 |
}
|
567 |
|
568 |
return $is_shop;
|
595 |
if ( $post ) {
|
596 |
$is_product = 'product' === \get_post_type( $post );
|
597 |
} else {
|
598 |
+
$is_product = \function_exists( '\\is_product' ) && \is_product();
|
599 |
}
|
600 |
|
601 |
return $is_product;
|
inc/classes/post-data.class.php
CHANGED
@@ -202,7 +202,7 @@ class Post_Data extends Detect {
|
|
202 |
protected function get_unfiltered_post_meta_defaults() {
|
203 |
return [
|
204 |
'_genesis_title' => '',
|
205 |
-
'_tsf_title_no_blogname' => 0,
|
206 |
'_genesis_description' => '',
|
207 |
'_genesis_canonical_uri' => '',
|
208 |
'redirect' => '', //! Will be displayed in custom fields when set...
|
@@ -552,6 +552,9 @@ class Post_Data extends Detect {
|
|
552 |
* @since 2.4.3
|
553 |
* @since 2.9.3 1. Removed object caching.
|
554 |
* 2. It now uses WP_Query, instead of wpdb.
|
|
|
|
|
|
|
555 |
*
|
556 |
* @return int Latest Post ID.
|
557 |
*/
|
202 |
protected function get_unfiltered_post_meta_defaults() {
|
203 |
return [
|
204 |
'_genesis_title' => '',
|
205 |
+
'_tsf_title_no_blogname' => 0, // The prefix I should've used from the start...
|
206 |
'_genesis_description' => '',
|
207 |
'_genesis_canonical_uri' => '',
|
208 |
'redirect' => '', //! Will be displayed in custom fields when set...
|
552 |
* @since 2.4.3
|
553 |
* @since 2.9.3 1. Removed object caching.
|
554 |
* 2. It now uses WP_Query, instead of wpdb.
|
555 |
+
* @slow The queried result is not stored in WP Post's cache, which would allow
|
556 |
+
* direct access to all values of the post (if requested). This is because
|
557 |
+
* we're using `'fields' => 'ids'` instead of `'fields' => 'all'`.
|
558 |
*
|
559 |
* @return int Latest Post ID.
|
560 |
*/
|
inc/classes/sanitize.class.php
CHANGED
@@ -99,7 +99,7 @@ class Sanitize extends Admin_Pages {
|
|
99 |
$this->delete_main_cache();
|
100 |
|
101 |
// Set backward compatibility. This runs after the sanitization.
|
102 |
-
\add_filter( 'pre_update_option_' . THE_SEO_FRAMEWORK_SITE_OPTIONS, [ $this, '_set_backward_compatibility' ], 10 );
|
103 |
|
104 |
// Sets that the options are unchanged, preemptively.
|
105 |
$this->update_static_cache( 'settings_notice', 'unchanged' );
|
@@ -141,29 +141,15 @@ class Sanitize extends Admin_Pages {
|
|
141 |
* @since 4.0.0 Emptied and is no longer enqueued.
|
142 |
* @since 4.1.0 1. Added taxonomical robots options backward compat.
|
143 |
* 2. Added the first two parameters.
|
|
|
144 |
* @access private
|
145 |
*
|
146 |
* @param mixed $new_value The new, unserialized, and filtered option value.
|
147 |
* @return mixed $new_value The updated option.
|
148 |
*/
|
149 |
public function _set_backward_compatibility( $new_value ) {
|
150 |
-
|
151 |
-
|
152 |
-
// Category and Tag robots backward compat.
|
153 |
-
foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) :
|
154 |
-
$robots_option_id = $this->get_robots_taxonomy_option_id( $r );
|
155 |
-
$new_robots_options = $new_value[ $robots_option_id ] ?? [];
|
156 |
-
|
157 |
-
$new_category_option = $new_robots_options['category'] ?? 0;
|
158 |
-
$new_tag_option = $new_robots_options['post_tag'] ?? 0;
|
159 |
-
|
160 |
-
// Don't compare to old option--it's never reliably set; it might skip otherwise, although it's always correct.
|
161 |
-
// Do not resanitize. Others might've overwritten that, let's keep their value.
|
162 |
-
$new_value[ "category_$r" ] = $new_category_option;
|
163 |
-
$new_value[ "tag_$r" ] = $new_tag_option;
|
164 |
-
endforeach;
|
165 |
-
|
166 |
-
end:;
|
167 |
return $new_value;
|
168 |
}
|
169 |
|
99 |
$this->delete_main_cache();
|
100 |
|
101 |
// Set backward compatibility. This runs after the sanitization.
|
102 |
+
// \add_filter( 'pre_update_option_' . THE_SEO_FRAMEWORK_SITE_OPTIONS, [ $this, '_set_backward_compatibility' ], 10 );
|
103 |
|
104 |
// Sets that the options are unchanged, preemptively.
|
105 |
$this->update_static_cache( 'settings_notice', 'unchanged' );
|
141 |
* @since 4.0.0 Emptied and is no longer enqueued.
|
142 |
* @since 4.1.0 1. Added taxonomical robots options backward compat.
|
143 |
* 2. Added the first two parameters.
|
144 |
+
* @since 4.2.5 Emptied and is no longer enqueued.
|
145 |
* @access private
|
146 |
*
|
147 |
* @param mixed $new_value The new, unserialized, and filtered option value.
|
148 |
* @return mixed $new_value The updated option.
|
149 |
*/
|
150 |
public function _set_backward_compatibility( $new_value ) {
|
151 |
+
// db_4103:
|
152 |
+
// end:;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
return $new_value;
|
154 |
}
|
155 |
|
inc/classes/term-data.class.php
CHANGED
@@ -402,6 +402,9 @@ class Term_Data extends Post_Data {
|
|
402 |
* Memoizes the return value.
|
403 |
*
|
404 |
* @since 4.1.0
|
|
|
|
|
|
|
405 |
*
|
406 |
* @return int Latest Category ID.
|
407 |
*/
|
402 |
* Memoizes the return value.
|
403 |
*
|
404 |
* @since 4.1.0
|
405 |
+
* @slow The queried result is not stored in WP Term's cache, which would allow
|
406 |
+
* direct access to all values of the term (if requested). This is because
|
407 |
+
* we're using `'fields' => 'ids'` instead of `'fields' => 'all'`.
|
408 |
*
|
409 |
* @return int Latest Category ID.
|
410 |
*/
|
inc/compat/plugin-edd.php
CHANGED
@@ -22,7 +22,7 @@ namespace The_SEO_Framework;
|
|
22 |
function _set_edd_is_product( $is_product, $post ) {
|
23 |
|
24 |
if ( ! $is_product ) {
|
25 |
-
if ( \function_exists( 'edd_get_download' ) ) {
|
26 |
$download = \edd_get_download(
|
27 |
$post ? \get_post( $post ) : \tsf()->get_the_real_ID()
|
28 |
);
|
22 |
function _set_edd_is_product( $is_product, $post ) {
|
23 |
|
24 |
if ( ! $is_product ) {
|
25 |
+
if ( \function_exists( '\\edd_get_download' ) ) {
|
26 |
$download = \edd_get_download(
|
27 |
$post ? \get_post( $post ) : \tsf()->get_the_real_ID()
|
28 |
);
|
inc/compat/plugin-polylang.php
CHANGED
@@ -64,7 +64,7 @@ function _polylang_fix_sitemap_base_bath( $path ) {
|
|
64 |
*/
|
65 |
function _polylang_set_sitemap_language() {
|
66 |
|
67 |
-
if ( ! \function_exists( 'PLL' ) ) return;
|
68 |
if ( ! ( \PLL() instanceof \PLL_Frontend ) ) return;
|
69 |
|
70 |
// phpcs:ignore, WordPress.Security.NonceVerification.Recommended -- Arbitrary input expected.
|
@@ -175,7 +175,7 @@ function _polylang_sitemap_append_non_translatables( $args ) {
|
|
175 |
* @return string
|
176 |
*/
|
177 |
function pll__( $string ) {
|
178 |
-
if ( \function_exists( 'PLL' ) && \function_exists( '\\pll__' ) )
|
179 |
if ( \PLL() instanceof \PLL_Frontend )
|
180 |
return \pll__( $string );
|
181 |
|
@@ -265,8 +265,8 @@ function _polylang_flush_sitemap( $type, $id, $args, $success ) {
|
|
265 |
)
|
266 |
); // No cache OK. DB call ok.
|
267 |
|
268 |
-
|
269 |
-
|
270 |
$wpdb->query(
|
271 |
$wpdb->prepare(
|
272 |
"DELETE FROM $wpdb->options WHERE option_name LIKE %s",
|
64 |
*/
|
65 |
function _polylang_set_sitemap_language() {
|
66 |
|
67 |
+
if ( ! \function_exists( '\\PLL' ) ) return;
|
68 |
if ( ! ( \PLL() instanceof \PLL_Frontend ) ) return;
|
69 |
|
70 |
// phpcs:ignore, WordPress.Security.NonceVerification.Recommended -- Arbitrary input expected.
|
175 |
* @return string
|
176 |
*/
|
177 |
function pll__( $string ) {
|
178 |
+
if ( \function_exists( '\\PLL' ) && \function_exists( '\\pll__' ) )
|
179 |
if ( \PLL() instanceof \PLL_Frontend )
|
180 |
return \pll__( $string );
|
181 |
|
265 |
)
|
266 |
); // No cache OK. DB call ok.
|
267 |
|
268 |
+
// We didn't use a wildcard after "_transient_" to reduce scans.
|
269 |
+
// A second query is faster on saturated sites.
|
270 |
$wpdb->query(
|
271 |
$wpdb->prepare(
|
272 |
"DELETE FROM $wpdb->options WHERE option_name LIKE %s",
|
inc/compat/plugin-woocommerce.php
CHANGED
@@ -69,7 +69,7 @@ function _is_shop( $post = null ) {
|
|
69 |
|
70 |
$is_shop = (int) \get_option( 'woocommerce_shop_page_id' ) === $id;
|
71 |
} else {
|
72 |
-
$is_shop = ! \is_admin() && \function_exists( 'is_shop' ) && \is_shop();
|
73 |
}
|
74 |
|
75 |
return $is_shop;
|
@@ -146,7 +146,7 @@ function _set_wc_is_product( $is_product, $post ) {
|
|
146 |
if ( $post ) {
|
147 |
$is_product = 'product' === \get_post_type( $post );
|
148 |
} else {
|
149 |
-
$is_product = \function_exists( 'is_product' ) && \is_product();
|
150 |
}
|
151 |
|
152 |
return $is_product;
|
69 |
|
70 |
$is_shop = (int) \get_option( 'woocommerce_shop_page_id' ) === $id;
|
71 |
} else {
|
72 |
+
$is_shop = ! \is_admin() && \function_exists( '\\is_shop' ) && \is_shop();
|
73 |
}
|
74 |
|
75 |
return $is_shop;
|
146 |
if ( $post ) {
|
147 |
$is_product = 'product' === \get_post_type( $post );
|
148 |
} else {
|
149 |
+
$is_product = \function_exists( '\\is_product' ) && \is_product();
|
150 |
}
|
151 |
|
152 |
return $is_product;
|
inc/compat/plugin-wpml.php
CHANGED
@@ -78,8 +78,8 @@ function _wpml_flush_sitemap( $type, $id, $args, $success ) {
|
|
78 |
)
|
79 |
); // No cache OK. DB call ok.
|
80 |
|
81 |
-
|
82 |
-
|
83 |
$wpdb->query(
|
84 |
$wpdb->prepare(
|
85 |
"DELETE FROM $wpdb->options WHERE option_name LIKE %s",
|
78 |
)
|
79 |
); // No cache OK. DB call ok.
|
80 |
|
81 |
+
// We didn't use a wildcard after "_transient_" to reduce scans.
|
82 |
+
// A second query is faster on saturated sites.
|
83 |
$wpdb->query(
|
84 |
$wpdb->prepare(
|
85 |
"DELETE FROM $wpdb->options WHERE option_name LIKE %s",
|
inc/functions/upgrade-suggestion.php
CHANGED
@@ -64,12 +64,12 @@ _prepare( $previous_version, $current_version );
|
|
64 |
*/
|
65 |
function _prepare( $previous_version, $current_version ) {
|
66 |
|
67 |
-
|
68 |
// phpcs:ignore, WordPress.PHP.StrictComparisons.LooseComparison -- might be mixed types.
|
69 |
if ( $previous_version == $current_version ) return;
|
70 |
-
|
71 |
if ( \defined( 'TSF_DISABLE_SUGGESTIONS' ) && TSF_DISABLE_SUGGESTIONS ) return;
|
72 |
-
|
73 |
if ( ! \is_main_site() ) return;
|
74 |
|
75 |
// phpcs:disable -- There is no sale, leftover code.
|
@@ -83,13 +83,13 @@ function _prepare( $previous_version, $current_version ) {
|
|
83 |
// }
|
84 |
// phpcs:enable
|
85 |
|
86 |
-
|
87 |
if ( \defined( 'TSF_EXTENSION_MANAGER_VERSION' ) ) return;
|
88 |
|
89 |
if ( ! \function_exists( '\\get_plugins' ) )
|
90 |
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
91 |
|
92 |
-
|
93 |
if ( ! empty( \get_plugins()['the-seo-framework-extension-manager/the-seo-framework-extension-manager.php'] ) ) return;
|
94 |
|
95 |
// phpcs:ignore, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape
|
64 |
*/
|
65 |
function _prepare( $previous_version, $current_version ) {
|
66 |
|
67 |
+
// 0
|
68 |
// phpcs:ignore, WordPress.PHP.StrictComparisons.LooseComparison -- might be mixed types.
|
69 |
if ( $previous_version == $current_version ) return;
|
70 |
+
// 1
|
71 |
if ( \defined( 'TSF_DISABLE_SUGGESTIONS' ) && TSF_DISABLE_SUGGESTIONS ) return;
|
72 |
+
// 2
|
73 |
if ( ! \is_main_site() ) return;
|
74 |
|
75 |
// phpcs:disable -- There is no sale, leftover code.
|
83 |
// }
|
84 |
// phpcs:enable
|
85 |
|
86 |
+
// 3a
|
87 |
if ( \defined( 'TSF_EXTENSION_MANAGER_VERSION' ) ) return;
|
88 |
|
89 |
if ( ! \function_exists( '\\get_plugins' ) )
|
90 |
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
91 |
|
92 |
+
// 3b
|
93 |
if ( ! empty( \get_plugins()['the-seo-framework-extension-manager/the-seo-framework-extension-manager.php'] ) ) return;
|
94 |
|
95 |
// phpcs:ignore, TSF.Performance.Opcodes.ShouldHaveNamespaceEscape
|
inc/views/notice/persistent.php
CHANGED
@@ -13,6 +13,8 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and tsf()->_verify_include_secret( $_secr
|
|
13 |
|
14 |
if ( ! $message ) return;
|
15 |
|
|
|
|
|
16 |
// Make sure the scripts are loaded.
|
17 |
$this->init_admin_scripts();
|
18 |
The_SEO_Framework\Builders\Scripts::footer_enqueue();
|
@@ -26,30 +28,32 @@ $button_js = sprintf(
|
|
26 |
'<a class="hide-if-no-tsf-js tsf-dismiss" href="javascript:;" title="%s" %s></a>',
|
27 |
esc_attr( $dismiss_title ),
|
28 |
HTML::make_data_attributes( [
|
29 |
-
'key' => $
|
30 |
// Is this the best nonce key key? Capability validation already happened. See `output_dismissible_persistent_notices()`.
|
31 |
-
'nonce' => wp_create_nonce( $this->_get_dismiss_notice_nonce_action( $
|
32 |
] )
|
33 |
);
|
34 |
$button_nojs = vsprintf(
|
35 |
-
'<form action="%s" method=
|
36 |
[
|
37 |
// Register this at removable_query_args? Ignore? No one cares, literally? Does anyone even read this? Hello!? HELLO!?!?
|
38 |
-
esc_attr( add_query_arg( [ 'tsf-dismissed-notice' => $
|
39 |
-
|
40 |
implode(
|
41 |
'',
|
42 |
[
|
43 |
-
wp_nonce_field( $this->_get_dismiss_notice_nonce_action( $
|
44 |
-
|
45 |
-
'<button class=
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
|
|
|
|
53 |
),
|
54 |
]
|
55 |
),
|
13 |
|
14 |
if ( ! $message ) return;
|
15 |
|
16 |
+
$sanitized_key = sanitize_key( $key );
|
17 |
+
|
18 |
// Make sure the scripts are loaded.
|
19 |
$this->init_admin_scripts();
|
20 |
The_SEO_Framework\Builders\Scripts::footer_enqueue();
|
28 |
'<a class="hide-if-no-tsf-js tsf-dismiss" href="javascript:;" title="%s" %s></a>',
|
29 |
esc_attr( $dismiss_title ),
|
30 |
HTML::make_data_attributes( [
|
31 |
+
'key' => $sanitized_key,
|
32 |
// Is this the best nonce key key? Capability validation already happened. See `output_dismissible_persistent_notices()`.
|
33 |
+
'nonce' => wp_create_nonce( $this->_get_dismiss_notice_nonce_action( $sanitized_key ) ),
|
34 |
] )
|
35 |
);
|
36 |
$button_nojs = vsprintf(
|
37 |
+
'<form action="%s" method=post id="tsf-dismiss-notice[%s]" class=hide-if-tsf-js>%s</form>',
|
38 |
[
|
39 |
// Register this at removable_query_args? Ignore? No one cares, literally? Does anyone even read this? Hello!? HELLO!?!?
|
40 |
+
esc_attr( add_query_arg( [ 'tsf-dismissed-notice' => $sanitized_key ] ) ),
|
41 |
+
$sanitized_key,
|
42 |
implode(
|
43 |
'',
|
44 |
[
|
45 |
+
wp_nonce_field( $this->_get_dismiss_notice_nonce_action( $sanitized_key ), 'tsf_notice_nonce', true, false ),
|
46 |
+
vsprintf(
|
47 |
+
'<button class=tsf-dismiss type=submit name=tsf-notice-submit id=tsf-notice-submit[%s] value=%s title="%s">%s</button>',
|
48 |
+
[
|
49 |
+
$sanitized_key,
|
50 |
+
$sanitized_key,
|
51 |
+
esc_attr( $dismiss_title ),
|
52 |
+
sprintf(
|
53 |
+
'<span class=screen-reader-text>%s</span>',
|
54 |
+
esc_html( $dismiss_title )
|
55 |
+
),
|
56 |
+
]
|
57 |
),
|
58 |
]
|
59 |
),
|
inc/views/settings/metaboxes/title.php
CHANGED
@@ -25,7 +25,7 @@ switch ( $this->get_view_instance( 'title', $instance ) ) :
|
|
25 |
$latest_cat_id = $this->get_latest_category_id();
|
26 |
|
27 |
// phpcs:ignore, WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- We don't expect users to set scripts in titles.
|
28 |
-
$post_name = strip_tags( get_the_title( $latest_post_id ) ) ?: __( 'Example Post', 'autodescription' );
|
29 |
$post_title = $this->s_title( $this->hellip_if_over( $post_name, 60 ) );
|
30 |
|
31 |
$cat_prefix = $this->s_title( _x( 'Category:', 'category archive title prefix', 'default' ) );
|
@@ -36,7 +36,7 @@ switch ( $this->get_view_instance( 'title', $instance ) ) :
|
|
36 |
) );
|
37 |
$cat_title_full = sprintf(
|
38 |
/* translators: 1: Title prefix. 2: Title. */
|
39 |
-
|
40 |
$cat_prefix,
|
41 |
$cat_title
|
42 |
);
|
25 |
$latest_cat_id = $this->get_latest_category_id();
|
26 |
|
27 |
// phpcs:ignore, WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- We don't expect users to set scripts in titles.
|
28 |
+
$post_name = esc_html( strip_tags( get_the_title( $latest_post_id ) ) ?: __( 'Example Post', 'autodescription' ) );
|
29 |
$post_title = $this->s_title( $this->hellip_if_over( $post_name, 60 ) );
|
30 |
|
31 |
$cat_prefix = $this->s_title( _x( 'Category:', 'category archive title prefix', 'default' ) );
|
36 |
) );
|
37 |
$cat_title_full = sprintf(
|
38 |
/* translators: 1: Title prefix. 2: Title. */
|
39 |
+
esc_html_x( '%1$s %2$s', 'archive title', 'default' ),
|
40 |
$cat_prefix,
|
41 |
$cat_title
|
42 |
);
|
lib/js/pt.js
CHANGED
@@ -313,10 +313,10 @@ window.tsfPT = function( $ ) {
|
|
313 |
} );
|
314 |
|
315 |
if ( primaries[ taxonomy ] ) {
|
316 |
-
|
317 |
makePrimary( taxonomy, primaries[ taxonomy ] );
|
318 |
} else {
|
319 |
-
|
320 |
makeFirstPrimary( taxonomy );
|
321 |
}
|
322 |
}
|
@@ -327,10 +327,10 @@ window.tsfPT = function( $ ) {
|
|
327 |
} );
|
328 |
|
329 |
if ( taxonomies[ taxonomy ].primary ) {
|
330 |
-
|
331 |
makePrimary( taxonomy, taxonomies[ taxonomy ].primary );
|
332 |
} else {
|
333 |
-
|
334 |
makeFirstPrimary( taxonomy );
|
335 |
}
|
336 |
}
|
313 |
} );
|
314 |
|
315 |
if ( primaries[ taxonomy ] ) {
|
316 |
+
// One has been set previously via this script, reselect it.
|
317 |
makePrimary( taxonomy, primaries[ taxonomy ] );
|
318 |
} else {
|
319 |
+
// Select one according to WordPress's term list sorting.
|
320 |
makeFirstPrimary( taxonomy );
|
321 |
}
|
322 |
}
|
327 |
} );
|
328 |
|
329 |
if ( taxonomies[ taxonomy ].primary ) {
|
330 |
+
// One has been saved earlier via this script.
|
331 |
makePrimary( taxonomy, taxonomies[ taxonomy ].primary );
|
332 |
} else {
|
333 |
+
// Select one according to WordPress's term list sorting.
|
334 |
makeFirstPrimary( taxonomy );
|
335 |
}
|
336 |
}
|
lib/js/tabs.js
CHANGED
@@ -115,6 +115,8 @@ window.tsfTabs = function() {
|
|
115 |
const cacheId = target.name;
|
116 |
const stack = getStack( stackId );
|
117 |
|
|
|
|
|
118 |
const fadeOutTimeout = 125;
|
119 |
const fadeInTimeout = 175;
|
120 |
const fadeCSS = {
|
@@ -130,8 +132,7 @@ window.tsfTabs = function() {
|
|
130 |
animationDuration: `${fadeOutTimeout}ms`,
|
131 |
animationTimingFunction: 'cubic-bezier(.54,.12,.90,.60)',
|
132 |
}
|
133 |
-
}
|
134 |
-
|
135 |
const fadeIn = element => { for ( const prop in fadeCSS.fadeIn ) element.style[ prop ] = fadeCSS.fadeIn[ prop ] };
|
136 |
const fadeOut = element => { for ( const prop in fadeCSS.fadeOut ) element.style[ prop ] = fadeCSS.fadeOut[ prop ] };
|
137 |
|
@@ -160,7 +161,7 @@ window.tsfTabs = function() {
|
|
160 |
await new Promise( _resolve => setTimeout( _resolve, fadeInTimeout * 2/3 ) );
|
161 |
|
162 |
return testTab(); // do not pass newContent!
|
163 |
-
}
|
164 |
const testTab = async () => {
|
165 |
// Regain this value from a new query, for the toggle's target-cache might've changed.
|
166 |
let newContent = document.getElementById( `${_toggleCache.get( 'target' ).get( cacheId )}-content` );
|
115 |
const cacheId = target.name;
|
116 |
const stack = getStack( stackId );
|
117 |
|
118 |
+
// TODO make this a public API, and apply to media.js. and tsf.resetAjaxLoader et al.?
|
119 |
+
// Also add show/hide? -> new tsfUI file? tsfUI.fadeIn( element )
|
120 |
const fadeOutTimeout = 125;
|
121 |
const fadeInTimeout = 175;
|
122 |
const fadeCSS = {
|
132 |
animationDuration: `${fadeOutTimeout}ms`,
|
133 |
animationTimingFunction: 'cubic-bezier(.54,.12,.90,.60)',
|
134 |
}
|
135 |
+
}
|
|
|
136 |
const fadeIn = element => { for ( const prop in fadeCSS.fadeIn ) element.style[ prop ] = fadeCSS.fadeIn[ prop ] };
|
137 |
const fadeOut = element => { for ( const prop in fadeCSS.fadeOut ) element.style[ prop ] = fadeCSS.fadeOut[ prop ] };
|
138 |
|
161 |
await new Promise( _resolve => setTimeout( _resolve, fadeInTimeout * 2/3 ) );
|
162 |
|
163 |
return testTab(); // do not pass newContent!
|
164 |
+
}
|
165 |
const testTab = async () => {
|
166 |
// Regain this value from a new query, for the toggle's target-cache might've changed.
|
167 |
let newContent = document.getElementById( `${_toggleCache.get( 'target' ).get( cacheId )}-content` );
|
readme.txt
CHANGED
@@ -2,10 +2,10 @@
|
|
2 |
Contributors: Cybr
|
3 |
Donate link: https://github.com/sponsors/sybrew
|
4 |
Tags: seo, xml sitemap, google search, open graph, schema.org, twitter card, performance, headless
|
5 |
-
Requires at least: 5.5
|
6 |
Tested up to: 6.0
|
7 |
Requires PHP: 7.2.0
|
8 |
-
Stable tag: 4.2.
|
9 |
License: GPLv3
|
10 |
License URI: http://www.gnu.org/licenses/gpl-3.0.html
|
11 |
|
@@ -247,6 +247,10 @@ If you wish to display breadcrumbs, then your theme should provide this. Alterna
|
|
247 |
|
248 |
== Changelog ==
|
249 |
|
|
|
|
|
|
|
|
|
250 |
= 4.2.4 =
|
251 |
|
252 |
This minor update improves image processing, reducing TSF's load impact by roughly 20% when generating metadata. We also added WordPress 6.0 support for image filesizes, making social sharing [even more robust](https://theseoframework.com/?p=3903).
|
2 |
Contributors: Cybr
|
3 |
Donate link: https://github.com/sponsors/sybrew
|
4 |
Tags: seo, xml sitemap, google search, open graph, schema.org, twitter card, performance, headless
|
5 |
+
Requires at least: 5.5
|
6 |
Tested up to: 6.0
|
7 |
Requires PHP: 7.2.0
|
8 |
+
Stable tag: 4.2.5
|
9 |
License: GPLv3
|
10 |
License URI: http://www.gnu.org/licenses/gpl-3.0.html
|
11 |
|
247 |
|
248 |
== Changelog ==
|
249 |
|
250 |
+
= 4.2.5 =
|
251 |
+
|
252 |
+
This minor update addresses a change in WordPress 6.0 that causes taxonomy sitemaps to crash, allows paginated deindexing to supersede forced indexing, and improves image cropping by [preserving metadata](https://theseoframework.com/?p=3929).
|
253 |
+
|
254 |
= 4.2.4 =
|
255 |
|
256 |
This minor update improves image processing, reducing TSF's load impact by roughly 20% when generating metadata. We also added WordPress 6.0 support for image filesizes, making social sharing [even more robust](https://theseoframework.com/?p=3903).
|