The SEO Framework

★︎ My account
  • Extensions
  • Documentation
  • Pricing
  1. Home
  2. Plugin Changelog
  3. 4.1.4

4.1.4

July 20, 2021 by Sybre Waaijer

This minor update packs a major punch. TSF now supports headless mode, cementing itself as a turnkey solution. We defenestrated the pernicious object caching mechanism, and we updated some options’ defaults effective only on new sites. We improved performance iterably, fixed about 12 bugs, and enjoyed the weather. Lastly, we introduced a new API for user meta handling, among other things — developers that wrote software interfacing with TSF are employed well reading the detailed changelog.

Pro tip: If you can no longer crop images via TSF, save your changes, and then try hitting CMD ⌘+OPTION ⌥+R (Safari Mac), CMD ⌘+SHIFT ⇧+R (Chrome/Firefox Mac), or CTRL ^+SHIFT ⇧+R (Windows); these keyboard shortcuts will force-fetch the latest scripts from your server.

Detailed log

> View code changes.
> View closed issues.

For everyone

  • Added:
    • Don’t care about SEO? Now you can use TSF headlessly. Simply add the following snippet to your wp-config.php file or a (mu-)plugin:
      • define( 'THE_SEO_FRAMEWORK_HEADLESS', true );
        • This constant must be defined before action plugins_loaded priority 5.
        • This constant also listens to granular control by setting an array instead of a boolean.
        • This constant denies access to database calls, speeding up your website tremendously. However, your SEO (meta) settings won’t work.
        • (But…) This constant can be used conditionally when used in a (mu-)plugin; for example, only super-admins can modify the settings/meta-data. Then, you must disable headlessness on the front-end for them to have an effect.
        • Learn more about headless mode.
  • Changed:
    • Option defaults:
      • “Apply noindex to every second or later archive page?” is now disabled by default. We found that, even though it helps search engines crawl your website more quickly, blocking paginated archives could actually be deleterious to indexing old content in select scenarios.
        • We may consider reverting this decision once we can discern separate “post type archive” (including a separate “blog”) options for this.
      • “Remove author name?” (from oEmbed) is now enabled by default. We asserted only Discord using this feature in the wild, and found those who care think Discord’s way of handling it undesired.
      • “Sitemap Query Limit” is now lowered to 1000 items, from 3000.
        • In combination with not applying noindex to every second or later archive page, this will improve indexing rate for almost all sites. This change also reduces the strain on your server, especially when you have many other plugins adding post meta to each post.
    • Redirected pages/posts/terms* are no longer included in the sitemap. For real this time.
      • * WordPress Core sitemaps can display terms; our optimized version does not.
  • Improved:
    • Continuing the trend we set since TSF v4.0.0, we reduced the size of the main object of TSF significantly (7.8%) by offloading various methods to flyweight objects. This should improve load times, albeit only a little.
    • The quick-edit column for the Visibiity setting is now 1.5x wider on non-mobile. This change might take some ‘getting used to,’ but now you can read the URLs you put in.
  • Removed:
    • Object caching of the HTML output of TSF.
      • We theorized no benefit having this feature, and thought it rather harmful; later, we found this true empirically.
      • This does not mean we removed basic object caching support! We only purged our very custom implementation for it. You do not need to clear your object cache after updating, and you won’t find performance altered.
  • Updated:
    • [?]/Documentation links to various official Google and Bing resources no longer need to redirect to their latest articles.
  • Other:
    • It’s 2021 now… so, we extended the plugin’s copyright year notes.
    • TSF’s ownership has been transferred from CyberWire to CyberWire B.V… Or, did the company itself transfer its own ownership? Who owns what? Do I own me? Technicalities aside, the new company will ruin everything.
    • “The SEO Framework Title Fix” plugin (from WordPress.org, no longer available) no longer works with The SEO Framework from this version onward.
      • Use our Extension Manager’s Title Fix instead. Or, better yet, update your theme to follow the latest standards at least once every decade.
    • WordPress 5.7 came with improved robots-directives support. The improvements only go so far, and although WordPress seems dogmatic about bringing the editor experience to the front-end, it falls completely flat recognizing that the back-end query-system is disjointed from the front-end, as such is the newly ‘improved’ robots-directives support. Because we reliably rely on reliable queries with reliance, we are left with no choice but to strip some of its conflicting functionality from Core, without changing the fully fledged experience you’ve come to expect from TSF. To summarize our actions:
      • TSF will not integrate with this new Core feature for now.
      • TSF will remove some of WordPress 5.7 robots support on these pages:
        • noindex for search. TSF supports this feature with a toggle already. When TSF doesn’t support the search query’s query (yes, that’s a thing), this directive can be reinstated via Core.
        • The new robots-directive support for maximum-image-preview-directives throughout your site, regardless whether a page is supported by TSF or not. TSF allows you much greater control and supports this directive quintessentially in principle on all pages it supports. We removed this output from WordPress as an enforced opt-out of this directive because there’s a conflict of policy, exempting foreboding legal issues from your potential clients for whatever TSF promises on its settings-pages to the site owners. ELI2: no copyright WP-robots, yes copyright TSF-robots. “But… but…!”, – no, talk to your lawyer.
      • TSF will not remove the newly added noindex for embed pages. WordPress can detect embeds, and plugins or themes might respond differently (unpredictably) when embedded.
      • TSF will not remove the refactored WordPress method for outputting noindex when the blog isn’t set to public. TSF will warn you about this still – very much so.
      • TSF will not remove any implementation from other plugins or themes. First, this is definitely not the domain of themes. Secondly, we cannot predict what others plugin authors might do, and we’re very much inclinded to shift the blame… always.
    • WordPress 5.8 brings no conflicting changes.
  • Fixed:
    • Addressed an issue where “Apply noindex to every second or later page on the homepage?” wasn’t honored when the homepage is force-indexed via the homepage’s post meta.
    • Addressed an issue where the character counter wasn’t aligned pixel-perfectly on RTL-language sites.
    • Addressed an issue where image-tooltips didn’t load correctly when reloading the DOM, such as with our Local extension.
    • Addressed an issue where in quick-edit, the homepage title wasn’t locked correctly, causing it-and its counters to render incorrectly.
    • Removed double-deletion of term meta when deleting a term. That can’t happen of course… so, uh… yeah, we removed a redundant database call during term deletion. Cool.
    • Resolved a memory leak in image-tooltips.
    • Resolved an issue where excluded post types could still have their search-archives adjusted.
      • The following was done to achieve this (bear with me – “excluding posts from excluded post types…” yea, I don’t get it either):
        1. Post meta cannot be fetched from excluded post types. This prevents “on the site”-queries filtering excluded post-type-posts.
        2. A LEFT JOIN … WHERE $wpdb->posts.post_type IN ('post_types[]') query is now executed for fetching excluded posts, but only when post types are excluded. This type of join should minimize the query impact, because we expect users only to exclude few posts, so only a few posts are parsed. The parent query should be ‘slowest’, but ought to be cached, thence actually ‘fastest’.
          • As before, this query is executed only once after saving the SEO settings, or after updating/publishing any post or page. The result of the query is stored in a non-expiring “transient” option.
    • Resolved an issue where excluded taxonomies could still have their archives adjusted.
      • This means your ‘excluded’ posts may still show up in the excluded taxonomies’ terms.
    • Resolved an issue where post-link queries via the Block Editor were filtered by post-search exclusions settings.
    • The expected sitemap URL now generates a correct URL for (WordPress, WordPress Multisite, Polylang, WPML, etc.) subdirectories.
    • TSF now disables WooCommerce’s robots-meta output, and its new WP 5.7 implementation thereof. In turn, TSF will hint noindex as default for the Cart, Checkout, and Profile (My Account) pages, regardless of your SEO settings otherwise.
      • Now, you can also overwrite these settings effectively.
        • Cool: TSF will warn that WooCommerce recommends otherwise via the SEO Bar.
          • When you don’t overwrite its state, TSF will then tell WooCommerce recommended this state.
      • Because of this change, TSF will now remove these pages from the sitemap by default. When you force-index these pages, they’ll get added back to the sitemap.
      • Nota bene: This only works when setting a page ID via WooCommerce’s settings UI. The setting is accessible via wc_get_page_id() (WC 3.0 and later).
    • When using WPML, the main sitemap no longer contains “display translatable” post types, and the translated sitemaps no longer contain “not translatable” post types nor untranslated posts from “display translatable” post types.
  • Not fixed:
    • Polylang transforms (ruins) the home URL, and depending on your Polylang configuration, sitemaps may or may not output as expected. We’ve been beating this same horse iterably, and our spirit is dying, for their developers have been reluctant to communicate about this hitherto – even deleting our comments. Luckily, WordPress will eventually support translatable content natively, after which we can all sigh in relief.
      • Briefly, when using Polylang, do not set “URL Modifications” to “The language is set from content”; use any other setting instead. You’ll encounter fewer issues, but using any other setting is also much better for SEO regardless.

For translators

  • Added:
    • A few new strings require translation.
    • You may find a few strings fuzzy because we updated the links to the documentation of Google and Bing.
  • Updated:
    • Translation POT file.

For developers

  • Option notes:
    • For autodescription-site-settings (constant THE_SEO_FRAMEWORK_SITE_OPTIONS):
      • Changed:
        • oembed_remove_author now defaults to 1, from 0.
        • paged_noindex now defaults to 0, from 1.
        • sitemap_query_limit now defaults to 1000, from 3000.
      • Removed:
        • cache_object, this value will not be deleted when upgrading to this version for backward-compatible reasons.
          • During a manual option-save, however, this value will vanish.
  • Object notes:
    • Added:
      • \The_SEO_Framework\Bridges\Ajax, private, internal use only.
        • This class is used to handle AJAX requests for The SEO Framework.
        • The methods it contains were moved from \The_SEO_Famework\Admin_Init class.
        • This class and its methods are static, and cannot be instantiated.
        • All methods in the class are marked private, and should not be integrated in your software.
        • This class is final, and cannot be extended.
        • The class may contain some filters and actions (callbacks), which are public.
        • This class is autoloaded when admin-ajax requests are recorded. Since we do not handle “nopriv” requests, these are thusly ignored.
      • In preparation for the UI redesign project, and to compact the main facade object, these new classes are new and marked as protected; all methods therein can disappear or change behavior without notice:
        • \The_SEO_Framework\Interpreters\HTML
        • \The_SEO_Framework\Interpreters\Form
        • \The_SEO_Framework\Interpreters\Markdown
      • \The_SEO_Framework\Bridges\PluginTable
        • This class is used to take care of our implementations for the WordPress plugins overview table.
        • This class is meant to be used internally only.
        • If you wish to decouple our plugin links, you may need to update your filters now (gone are private methods _add_plugin_action_links() and _add_plugin_row_meta() from the_seo_framework()).
      • \The_SEO_Framework\Bridges\UserSettings
        • This class is used to take care of our implemention for the WordPress profile/user edit page.
        • This class is meant to be used internally only.
        • If you wish to remove the user settings, you may need to update your filters now (gone are private methods _update_user_settings and _add_user_author_fields from the_seo_framework()).
  • Method notes:
    • For object \The_SEO_Framework\Load (the_seo_framework()):
      • Added:
        • advanced_query_protection(), returns the tsf:aqp meta tag.
          • This tag is used to indicate a malformed query was detected. It’s tilt-proof.
        • append_url_query(), replaces append_php_query().
        • array_merge_recursive_distinct(), a glorified array_merge() that allows unlimited multidimensional arrays inputted combobulated by yours truly.
        • do_meta_output(), outputs all meta tags for the current query.
        • generate_robots_meta(), returns generated robots metadata.
        • get_current_post_type(), shorthand for two other methods to (1) get an estimated post type from the front-end query, with page-as-archive support. (2) Also works extensively in the admin area.
        • get_image_uploader_form(), returns image uploader form button.
          • This is an abstraction of the get_social_image_uploader_form() and get_logo_uploader_form() methods, both now call the new one for it has an interpolable API.
        • get_modified_time(), returns the modified time of the current post.
        • get_redirect_url(), returns the redirect URL for the current query. Also accepts arguments.
        • get_seo_settings_page_url(), replaces seo_settings_page_url().
        • is_profile_edit(), tells if (true/false) we’re on a user or profile edit page.
        • og_updated_time(), disjointed from article_modified_time(), outputs og:updated_time meta tag.
        • render_element, do not use. Useful for generating meta tags with few inputs. Escapes for you.
          • This method is marked ‘protected’ for we have other plans with it in the future.
        • s_user_meta(), sanitizes (registered) TSF profile metadata.
        • save_user_meta(), sanitizes and saves the user metadata for TSF.
        • We added these to get the names in sync with post-and term meta:
          • get_current_post_author_meta(), returns all TSF author metadata.
          • get_current_post_author_meta_item(), replaces the now deprecated get_current_author_option().
          • update_single_user_meta_item(), replaces update_user_option, but it now filters through your input, and it doesn’t expect your input to be escaped.
          • get_user_meta_defaults(), replaces the now deprecated get_default_user_data().
          • get_user_meta_item(), replaces the now deprecated get_user_option().
      • Improved:
        • can_i_use(), fixed sorting algorithm from fribbling-me to resolving-me. Nothing changed but legibility.
        • get_excluded_ids_from_cache()
          1. Now tests against post type exclusions.
          2. Now considers headlessness. This method runs only on the front-end.
        • get_post_meta():
          1. Now considers headlessness.
          2. now returns an empty array when post type isn’t supported. This improvement also affects get_post_meta_item(), which will return null.
        • get_timestamp_format():
          1. Added options-override parameter.
          2. Added return value filter.
        • get_term_meta() now considers headlessness.
        • is_blog_page_by_id() should now be faster thanks to bypassing the options when the input ID is 0.
        • is_static_frontpage() now memoizes the front page ID option.
        • s_image_details(), fixed theoretical issue where a different image could be set when width and height are supplied and either over 4K, but no ID is given.
      • Changed:
        • article_modified_time() no longer outputs og:updated_time. Use og_updated_time() instead.
        • delete_cache(), the following $types are no longer supported: front, post, term, author, robots, object.
        • delete_main_cache(), no longer flushes front, robots, and object cache.
        • delete_post_cache(), no longer flushes post object cache.
        • generate_cache_key() no longer returns a key when no $type is supplied.
        • generate_cache_key_by_type(), removed support for author, frontpage, page, post, attachment, singular, and term.
        • is_product() now has its return value memoized.
        • is_product_admin() now has its return value memoized.
        • is_shop() now has its return value memoized.
        • robots_txt(), removed object caching support.
      • Removed:
        • Object caching support: These methods were removed with no alternative available. Calling these methods will output a deprecation notice.
          • delete_author_cache()
          • delete_object_cache()
          • generate_cache_key_by_query()
          • generate_front_page_cache_key()
          • get_meta_output_cache_key_by_query()
          • get_meta_output_cache_key_by_type()
          • get_robots_txt_cache_key()
          • object_cache_set()
          • object_cache_get()
          • object_cache_delete()
        • Deprecated methods:
          • All methods deprecated in TSF v4.0.0 or earlier are no longer available. Calling those will result null and void:
            • doing_ajax()
            • enqueue_gutenberg_compat_scripts()
            • enqueue_media_scripts()
            • enqueue_primaryterm_scripts()
            • get_custom_field()
            • get_default_scripts()
            • get_header_image()
            • get_image_from_woocommerce_gallery()
            • get_schema_image()
            • get_seo_bar()
            • get_site_icon()
            • get_site_logo()
            • get_sitemap_xsl_url()
            • get_sitemap_xml_url()
            • get_social_image()
            • get_social_image_url_from_attachment()
            • get_social_image_url_from_home_meta()
            • get_social_image_url_from_post_meta()
            • get_social_image_url_from_post_thumbnail()
            • get_social_image_url_from_seo_settings()
            • get_taxonomial_canonical_url()
            • initialize_term_meta()
            • metabox_scripts()
            • output_sitemap_xsl_stylesheet()
            • ping_google()
            • ping_bing()
            • ping_searchengines()
            • post_status()
            • post_type_supports_custom_seo()
            • sanitize_field_id()
            • Scripts()
            • taxonomy_supports_custom_seo()
        • Protected:
          • init_term_meta() is now protected; it always should’ve been.
      • Info:
        • The following methods are now marked for deprecation:
          • “Marked” means that you should treat them as deprecated, but we honor them as maintained until the next major update.
          • append_php_query(). Use append_url_query() instead.
          • attention()
          • attention_description()
          • attention_description_noesc()
          • attention_noesc()
          • can_do_sitemap_robots().
          • code_wrap()
          • code_wrap_noesc()
          • description()
          • description_noesc()
          • field_id()
          • field_name()
          • get_author_option(). Use get_user_meta_item() instead.
          • get_current_author_option(). Use get_current_post_author_meta_item() instead.
          • get_default_user_data(). Use get_user_meta_defaults() instead.
          • get_field_id()
          • get_field_name()
          • get_html_output().
          • get_image_uploader_form()
          • get_is_conditional_checked()
          • get_logo_uploader_form(). Use get_logo_uploader_form() instead.
          • get_social_image_uploader_form(). Use get_image_uploader_form() instead.
          • get_user_option(). Use get_user_meta() or get_user_meta_item() instead.
          • is_conditional_checked()
          • is_default_checked()
          • is_robots_meta_noindex_set_by_args(). Use generate_robots_meta() instead.
          • is_warning_checked()
          • nav_tab_wrapper().
          • inpost_flex_nav_tab_wrapper().
          • seo_settings_page_url(). Use get_seo_settings_page_url() instead.
          • make_data_attributes()
          • make_checkbox()
          • make_checkbox_array()
          • make_single_select_form()
          • make_info()
          • output_character_counter_wrap()
          • output_pixel_counter_wrap()
          • robots_meta(), use generate_robots_meta() instead.
          • update_user_option(). Use update_single_user_meta_item() instead.
          • wrap_fields()
        • The following methods are now marked for deletion:
          • proportionate_dimensions().
    • For object The_SEO_Framework\Builders\SeoBar:
      • Added:
        • clear_query_cache(), clears the query cache.
        • get_query_cache(), presents an unalterable form of query cache.
    • For object The_SEO_Framework\Builders\Sitemap:
      • Changed:
        • is_post_included_in_sitemap() now returns false when the post has a redirect set via The SEO Framework. For real this time.
        • is_term_included_in_sitemap() now returns false when the term has a redirect set via The SEO Framework.
    • For object The_SEO_Framework\Bridges\Sitemap:
      • Changed:
        • get_expected_sitemap_endpoint_url() now assimilates the output using the base path, so that filter the_seo_framework_sitemap_base_path also works. Glues the pieces together using the get_home_host value.
  • Function notes:
    • Removed (thank you, WP Directory, for helping us verify):
      • the_seo_framework_active()
      • the_seo_framework_description_from_cache()
      • the_seo_framework_dot_version()
      • the_seo_framework_get_option()
      • the_seo_framework_is_settings_page()
      • the_seo_framework_options_page_slug()
      • the_seo_framework_options_pagehook()
      • the_seo_framework_the_url_from_cache()
      • the_seo_framework_title_from_cache()
      • the_seo_framework_update_option()
      • the_seo_framework_version()
  • Property notes:
    • For object \The_SEO_Framework\Load (the_seo_framework()):
      • Marked for deprecation:
        • $load_options. Use $is_headless['settings'] instead.
          • When you set this method, you’ll find it more restricting. Define THE_SEO_FRAMEWORK_HEADLESS instead.
  • Constant notes:
    • Added:
      • THE_SEO_FRAMEWORK_HEADLESS, accepts boolean (true/false) or array, with boolean indexes 'meta' => true/false, 'settings' => true/false, and 'user' => true/false.
    • Changed:
      • THE_SEO_FRAMEWORK_AUTHOR_INFO_CAP applies now only to the user editable; instead of the editor (user self, or administrator) of the user.
        • This change should not affect anyone; it merely increases sanity in the code.
        • Use the new headless mode to hide fields from users when not admin.
  • Filter notes:
    • Added:
      • the_seo_framework_enable_noindex_no_posts, useful for overriding the 404-protection for “empty” archives.
      • the_seo_framework_kill_core_robots; mind that this filter can run twice per page! Use (our) action-hooks to target one or the other… or both.
      • the_seo_framework_post_meta_defaults, replaces the_seo_framework_inpost_seo_save_defaults.
      • the_seo_framework_timestamp_format, used to change timestamp formats. Example usage. We advise not sending seconds, because small automated atomic-time fixes on your server may cause changes noted unscrupulously.
      • the_seo_framework_save_user_data, allows you to modify or add to the user meta saved by The SEO Framework.
      • the_seo_framework_user_meta_defaults, allows you to adjust the user meta defaults.
    • Changed:
      • the_seo_framework_get_options, the_seo_framework_post_meta, & the_seo_framework_term_meta:
        1. Now considers headlessness.
        2. Now returns a 3rd parameter: boolean $headless.
      • the_seo_framework_is_shop, the_seo_framework_is_product, & the_seo_framework_is_product_admin now have their return values memoized.
      • the_seo_framework_robots_meta_array now affects the sitemap. Be wary of performance issues! This filter will be executed thousands of times in one load.
      • the_seo_framework_sitemap_nhpt_query_args & the_seo_framework_sitemap_hpt_query_args:
        1. No longer pass the superfluously redundant suppress_filters index.
        2. Can now have index post_type set to [] or '' to cancel the query.
    • Deprecated:
      • the_seo_framework_inpost_seo_save_defaults, use filter the_seo_framework_post_meta_defaults instead.
      • the_seo_framework_load_options, use constant THE_SEO_FRAMEWORK_HEADLESS instead.
    • Removed:
      • the_seo_framework_current_term_meta, this filter was deprecated since 4.0.0.
      • the_seo_framework_save_custom_fields, this filter was deprecated since 4.0.0.
      • the_seo_framework_use_object_cache, feature no longer available.
  • Action notes:
    • Added:
      • The following actions are now also available on the front-end (instead of only the back-end), and have their callbacks changed.
        • wp_ajax_tsf_crop_image
        • wp_ajax_tsf_dismiss_notice
        • wp_ajax_tsf_update_counter
        • wp_ajax_tsf_update_post_data
      • the_seo_framework_after_author_fields allows you to do stuff a little later.
      • the_seo_framework_before_author_fields allows you to do stuff here.
    • Removed:
      • wp_ajax_the_seo_framework_update_counter, use wp_ajax_tsf_update_counter instead.
      • wp_ajax_the_seo_framework_update_post_data, use wp_ajax_tsf_update_post_data instead.
      • wp_ajax_tsf-crop-image, use wp_ajax_tsf_crop_image instead (underscore vs hyphens).
      • wp_ajax_tsf-dismiss-notice, use wp_ajax_tsf_dismiss_notice instead (underscore vs hyphens).
  • Other:
    • Cleaned up code, removed dumb quirks.
    • Introduced the tsfLePostData-container. This helps assert post data for list-edit, such as whether the post is the front page.
    • We now use static anonymous functions where appropriate, instead of simple lambda functions, to improve performance and reduce memory consumption.
    • tsf_facebook_page is now tsf-user-meta[facebook_page].
    • tsf_twitter_page is now tsf-user-meta[twitter_page].
    • tsf-dismiss-key is now tsf_dismiss_key
    • tsf-dismiss-nonce is now tsf_dismiss_nonce
    • tsf-notice-nonce is now tsf_notice_nonce
Filed Under: The SEO Framework Changelog

About Sybre Waaijer

Developer of The SEO Framework plugins.
Twitter  GitHub  WordPress

Looking for something?

Do more with extensions

Local

View extension
Get more customers to your restaurant and shop.

Articles

View extension
Google News and structured data.

Get Extension Manager

Commercial

The SEO Framework
Trademark of CyberWire B.V.
Leidse Schouw 2
2408 AE Alphen a/d Rijn
The Netherlands
KvK: 83230076
BTW/VAT: NL862781322B01

Twitter  GitHub

Professional

Pricing
About
Support
Press

Rational

Blog
Privacy Policy
Terms and Conditions
Refund Policy

Practical

Documentation
TSF on WordPress
TSF on GitHub
TSFEM on here
TSFEM on GitHub
Feature Highlights

Anything in 2025 › The SEO Framework