The SEO Framework

★︎ My account
  • Extensions
  • Documentation
  • Pricing
  1. Home
  2. Plugin Changelog
  3. 4.0.0 – Multiplex

4.0.0 – Multiplex

September 9, 2019 by Sybre Waaijer

It is our choices, Harry, that show what we truly are, far more than our abilities. – J.K. Rowling

Foreword

We wrote a foreword on 4.0. It’s a story on our efforts making this update, and why we’re exhilarated bringing you this update… finally.

Release highlights

  • We completely refactored the plugin without losing features. This is the fastest full-featured SEO plugin for WordPress… by far.
  • You can now use quick & bulk editing options. They blend perfectly into the interface of WordPress.
  • Now, there are more term options, including redirects and canonical URLs.
  • Want to enable indexing for just one page? Now you can, with qubit robots options.
  • The SEO Bar is completely rewritten. It now lists the issues found so you can easily see what requires attention.
  • The sitemap is also new. It lists your pages more intelligently, making it easier for search engines to find your latest updates.
  • There’s a new and improved API for all the JavaScript, SEO Bar, sitemaps, tooltips, term-and post meta, and more!
  • The character and pixel guidelines can now change depending on your site language.
  • Full keyboard navigation support is now possible for tooltips. The tooltips now also adjust their size intelligently, so they won’t ever fall out of your browser window.
  • …and more than 1000 other noteworthy changes, found below in the detailed log.

Environment upgrade notes

  • PHP 5.4 and PHP 5.5 support has been dropped. Here’s why:
    • PHP 5.5 hasn’t received updates for over 3 years and using it brings a significant security risk to your website.
    • Using PHP 5.6 lowers our time spent on maintenance, and it increases our productivity; so, we can focus on faster, better, and more useful solutions.
    • WordPress no longer supports PHP versions lower than 5.6 since WordPress 5.2. They, too, finally realize we need to move on.
    • Learn more about Upgrading PHP.
  • WordPress 4.6 through 4.8 support has been dropped. Here’s why:
    • More than 75% of all WordPress sites are using version 4.9 or later.
    • The API changes between WordPress versions are massive. We can’t support older versions reliably.
    • Although we wanted to move to WordPress 5.0 or later, many users have opted out because of conflicting interests–to put it lightly. Keep your site updated, and use the Classic Editor plugin if you really can’t stand the block editor.

If, for any reason, you still wish to use PHP 5.4/5.5 and/or WordPress 4.6/4.7/4.8, The SEO Framework 3.2.4 still supports them.

Share your love!

We hope you’ll love this update as much as we do. Please consider sharing it with the world, by giving us an awesome review! We read every one of them. Thanks!

Detailed log

Let’s go the extra mile; or 1,609.347 meters.

For everyone: automatic upgrading

  • Upgrading happens automatically after you update the plugin, on any page but the SEO settings page. It can’t occur during an AJAX request.
    • The upgrading process can be initiated by anyone, even by your visitors.
      • Upgrade requests shouldn’t occur concurrently. Only one visitor can initiate this at a time.
      • The upgrade should complete in the background within 300 seconds. When the upgrader is running, you can’t access the SEO Settings page.
      • When the upgrade fails, it tries to restart itself in the first following server request. This is unlikely to happen.
      • If you are unable to access the SEO settings page after updating (and 300 seconds have passed), you may encounter a caching plugin issue. If you’re using a caching plugin, flush your site’s cache, and The SEO Framework should finalize the upgrade.
    • The upgrade initiator is independent of WordPress’ “updating” action callback. This adds support for WordPress Multisites, and it allows you to update the plugin via FTP.

For everyone: settings upgrades

  • For all current and new users, the database version will be set to 4000.
  • Current users might find their options being altered.
    1. The old sitemap endpoints are removed from the WordPress rewrite rules, and the rewrite rules are flushed for one last time at PHP shutdown.
    2. The title separator dash will be converted to ndash, because that was already the case.
    3. The cronjob-pinging option is set to enabled (default); this will improve post-saving performance by offloading the sitemap pinging requests to a later time.
    4. The homepage’s “Meta Title Additions Location” was not in line with the global title location option. So, we rectified that. This means that “left” is now “right,” and vice versa.
      • This is a downgrade-compatibility breaking change! If you must downgrade, you must reverse this option manually.

For everyone: API changes

  • The plugin’s API has been overhauled almost completely.
  • If you have no coding experience – but you have still implemented filters/snippets acquired from our support forums – please check out the debug logs on your server.
  • If you’re getting deprecation or “doing it wrong” notices on the website or in your logs after updating, don’t be alarmed. Copy the error or notice and paste it in our support forums in a code block. Our support team (Sybre…) will forge new snippets for you, or point you to newly available options.
  • Note that you should never set WP_DEBUG to true (in wp-config.php) on a production website. Set this to false; live sites shouldn’t be constantly debugged.

For developers: About the quantum options

  • To put it simply, the individual robots options can now be set to 1, 0, or -1.
  • The -1 value is new, but it PHP can see it as true-esque. But, instead, you should treat the value as an enforced 0 (false-esque).
  • Use the updated robots-meta API (the_seo_framework()->robots_meta()) to get the real value.

For developers: About the sitemap

  • We removed our dependency on the WordPress Rewrite API. Many developers don’t know how this works, and we took it on ourselves to fix their issues.
  • Some hosting companies misconfigured the Rewrite system as well on NGINX-powered websites.
  • No longer are these our issues, as we now have our own rewrite-API implemented via this plugin. It’s straightforward and shouldn’t conflict with the WordPress Rewrite API. It’s based on Automattic’s sitemap integration in Jetpack.
  • Our new API automatically injects the sitemap after WordPress rewrite system has had a go, and is the only sitemap integration that’s compatible with all translation plugins.
  • Now, you can even add your own sitemaps! In just 15 minutes, we wrote an example sitemap extension plugin that achieves this. The hardest part was coming up with names for the functions.

For developers: About the post meta API

  • Parts of the post meta API is deprecated, in favor of a more robust solution.
  • Now, when fetching a single post meta item, we get all of it, and filter out what The SEO Framework (might have) inserted.
  • We now also prefill the values that aren’t set with the default options. You can filter these.
  • In conclusion, it’s faster and more reliable. You can now obtain the real value, always, asserted via the_seo_framework()->get_post_meta_item().

Super detailed log

> View code changes.
> View closed tickets.

If you think the economy is more important than the environment, try holding your breath while counting your money. – Guy McPherson

For everyone

  • Added:
    • New term settings:
      • Blog name removal.
      • Open Graph title and description inputs.
      • Twitter title and description inputs.
      • Social image input.
      • 301 Redirect input.
      • Canonical URL input.
    • New quantum robot options:
      • For all public posts, pages, and terms, you can now flip states of overlapping robot directives. Forcing an indexing or noindexing state, for example.
    • New automated robot directives:
      • Search engines no longer have to guess your intent for indexing, as we now deindex the following endpoints:
        • Feeds, via an X-Robots-Tag header.
        • Comment pagination, via a forced noindex robots’ meta tag.
        • robots.txt, via an X-Robots-Tag header.
          • Only when outputted via PHP. Web servers don’t launch WordPress otherwise.
    • New ways to edit in bulk:
      • Using the WordPress native bulk-editor, you can now change the following SEO settings for all public posts, pages, and terms:
        • Indexing
        • Link following
        • Archiving
      • Tip: Leave the values as — No Change — to keep the value unchanged.
      • Note: We can’t assert the expected value of Default, because no two pages are alike.
    • New ways to edit quickly:
      • Using the WordPress native quick-editor, you can now change the following SEO settings for all public posts, pages, and terms:
        • Canonical URL
        • Indexing
        • Link following
        • Archiving
        • Redirecting
    • New accessibility features:
      • Tooltips can now be accessed using your keyboard.
      • For the SEO Bar, you can now highlight warnings with symbols, so to discern issues more quickly.
        • We’re not completely satisfied on this integration, but we’ll revisit that after we get enough feedback.
      • Image fields now have an image icon nearby. Hover over it (or tap it, or use keyboard navigation) to preview the image.
      • The term meta inputs now have the “are you sure you want to leave this page?”-listener attached.
    • New installation notifications:
      • For new users, after installing The SEO Framework, users who have update_plugins capabilities may now see a confirmation that the plugin’s set up, and that there are installation instructions available.
      • For older users, after upgrading The SEO Framework’s database, users who have update_plugins capabilities may now see a confirmation notice that the site has been upgraded.
    • New sitemap pinging features:
      • Search engine pinging for the sitemap can now be offloaded to the WordPress cron-scheduler; this feature is enabled by default.
    • New HTML comments:
      • The estimated plugin-boot time is added to the closing HTML comment; which adds the bulk of the page-loading time of this plugin. In this update, we decreased that time, substantially–and we’re proudly showing it.
    • New social sharing features:
      • An option to have multiple social images to be outputted. How this affects sharing depends on the social network.
        • We disabled multiple images for Twitter Cards because Twitter doesn’t handle this well: They grab the final (less favorable) image found by default, they don’t allow you to select an image, etc.
        • Sites that upgraded from TSF v3.2.4 or below have this option disabled by default. New installations have this enabled.
      • Fallback images are now always appended; you can no longer overwrite them.
      • Social images may now be obtained from your post or page’s content.
      • Alt-tags are now provided with social images, which help with accessibility when sharing your page.
      • For WooCommerce, the Product Category Thumbnail is now considered for social images.
    • New editing experience:
      • When using the Block Editor, AKA WordPress 5.0+’s Gutenberg, on post update (and for unpublished posts, also on preview and autosave):
        1. A new SEO Bar is inserted (when enabled).
        2. All description placeholders will refresh.
        3. The social image placeholder will refresh.
  • Improved:
    • Accessibility:
      • The homepage settings may now reveal more information on where this data can be altered.
      • Screen-reader support has been reimplemented for the SEO Bar; it’s now completely written out and accurate.
      • You can now, again, navigate to the SEO Bar with your keyboard. This was disabled previously as it messed with the browser cache due to a wrong implementation.
      • When clicking the title prefix, like “Category: ” or “Private: “, the text selection now goes to the beginning, instead of the end.
        • When double-clicking on the title prefix, you’ll select the first word (or piece of whitespace).
        • When triple-clicking on the title prefix, you’ll select the whole input.
      • When clicking the title addition, like “- My Site”, the text selection no longer just goes to the end regardless of its position, but now goes to the beginning if it’s prefixed.
        • When double-clicking on the title addition, you’ll now select the last word (or first, if prefixed) (or piece of whitespace).
        • When triple-clicking on the title addition, you’ll select the whole input.
      • When updating the SEO settings, but when no settings have been changed, you now get a proper notification.
      • The SEO Settings update notifications now also state that caches are flushed.
      • HTML entities are now converted from your input to the examples and placeholders. Like in the Homepage SEO Settings’ title-additions examples, and in the Open Graph and Twitter placeholders.
      • Various sentences have been updated to be more easily interpretable.
      • Informational links to Google pages no longer predict your language; Google does this for you automatically based on your locations or preferences.
      • AJAX failures are now handled gracefully.
    • AI Generators:
      • Descriptions:
        • They now work better with RTL languages, like Arabic and Hebrew; however, due to ambiguity in language construction, it will read from top to bottom (like the web is built), instead of bottom to top (like books are).
        • It no longer accidentally slices the last sentence off when they’re a full sentence.
        • The excerpt-clause trimming is now at least twice (to infinitely) as fast, by utilizing simpler algorithms for text lengths.
        • They now work better with non-Roman languages, by using updated string length guidelines.
          • See the Guidelines (character) section below for details.
          • This only works when your site-language is set to these, not via translation plugins.
            • Translation plugins are capable of supporting these during loops; this has been supported since WordPress version 4.7 (2016) thanks to the new user-locale functionality.
            • For instance, in the admin term-view loop, they could dynamically change the WordPress locale, without affecting the interface.
            • And, for instance, when loading the post or term editor, they could set the locale, also without affecting the interface.
            • We preemptively added support for these situations by relying on WordPress. I implore them to address this because until then, it’ll be an irregular experience.
        • They now account for German’s extra capitalization width.
      • Canonical URL:
        • The automatic scheme determination now uses the “Site Address” scheme in “General Settings” to fall back on.
          • With that, we also updated the settings’ documentation.
    • Guidelines:
      • Titles/Descriptions:
        • The character guidelines have been updated for these languages (annotation: Language (language) @ adjusted/Roman):
          • Assamese (অসমীয়া) @ 148/160
          • Austrian German (Österreichisch Deutsch) @ 158/160
          • Swiss German (Schweiz Deutsch) @ 158/160
          • German (Deutsch) @ 158/160
          • Gujarati (ગુજરાતી) @ 148/160
          • Malayalam (മലയാളം) @ 100/160
          • Japanese (日本語) @ 70/160
          • Korean (한국어) @ 82/160
          • Talim (தமிழ்) @ 120/160
          • Taiwanese Mandarin (Traditional Chinese) (繁體中文) @ 70/160
          • Hong Kong (Chinese version) (香港中文版) @ 70/160
          • Mandarin (Simplified Chinese) (简体中文) @ 70/160
        • The pixel guidelines have been updated for these languages (annotation: Language (language) @ adjusted/Roman):
          • Arabic (العربية) @ 760/910
          • Moroccan Arabic (العربية المغربية) @ 760/910
          • South Azerbaijani (گؤنئی آذربایجان) @ 760/910
          • Iran Farsi (فارسی) @ 760/910
          • Hazaragi (هزاره گی) @ 760/910
          • Central Kurdish (كوردی) @ 760/910
        • The pixel and character counters now parse your input as HTML, giving you a more accurate rating. For example, HTML entities are now decoded on-the-fly.
    • Internationalization:
      • Sites set to the Assamese, German, Gujarati, Malayalam, Japanese, Korean, Talim, Traditional Chinese, or Simplified Chinese languages now have adjusted character guidelines.
      • Sites set to the Arabic, Azerbaijani, Farsi, Hazaragi, or Kurdish languages now have adjusted pixel guidelines.
      • UI strings that were hard to translate in other locales have been rewritten. Yes, this takes some time to get used to.
    • Layout:
      • Reordered the Homepage Settings meta box tabs to be more in line with the Post Meta Settings meta box.
      • Some option headers are now better aligned to the center.
      • We now thoroughly exclaim what the sitemap is and isn’t for.
      • Google+ is deceased, so we added an icon indicating that, cleared the placeholder, and removed the assumed profile redirection link.
        • We didn’t remove the option, because you may practically add a link to a non-Google+ profile. We’ll transform these options into an array of inputs when anything goes for Google’s Knowledge Graph.
    • Performance:
      • The SEO Bar now loads quicker, as redundant checks have been removed.
      • Moving your mouse over a tooltip item now requires less processing by removing inefficient jQuery dependencies, reducing the restyling time by 32% (based on a single test).
      • Tooltip creation also no longer uses inefficient jQuery dependencies anymore, reducing the generation time by 50% (based on a single test).
      • We offloaded a large portion of the admin-sided PHP scripts to different objects, freeing up RAM and lowering plugin boot-time.
      • The admin helper-scripts have been optimized to execute no unnecessary paint-jobs, lowering browser-CPU usage.
      • The SEO Settings notifications no longer get dragged around your page on-load and instead are preemptively placed. This prevents painting the whole page three times.
      • We now mostly use WordPress’ built-in window-resize debounce-handler, which reduces some processing required.
      • We now default all URL escaping to check for HTTPS first, instead of HTTP. If you’re not on the TLS protocol already… do it! It’s free and also good for SEO.
      • We split the main JavaScript and CSS files into multiple, dedicated files. Improving performance and lowering power consumption on all pages where TSF is active.
        • HTTP/2: To take full advantage of this change, multiplexing support (like in HTTP/2) is advised.
        • CSS: This significantly lowers class and ID lookups on every page load and DOM update, which drastically lowers processing time.
        • JS: The main advantage of splitting these files is that if one script fails, the other may still continue. The performance should remain roughly the same; however, further optimizations have been implemented.
        • PR: Love using WordPress on the go? Now you can use it for longer! Also, lower power consumption is great for the environment, times 100,000 sites.
    • Sitemap:
      • The blog page (not as homepage) now chooses a better lastmod value; based on whether the blog page was edited, or a new post was recently published.
        • Before it was only when a post was most recently published.
    • SEO Bar:
      1. Other states are now shown when “noindex” is set, regardless.
        • However, now, when the post is redirected, you’ll now only see that.
      2. Assessments are now listed, instead of written out.
      3. Perfect screen-reader support has been implemented.
      4. When a part of the SEO Bar is red, it’ll now halt and clear assessment, and it’ll tell you what to fix first.
        • Now, you may need to fix multiple things, but you’ll be guided through what needs immediate attention until it’s blue, yellow, or green.
      5. The SEO Bar may now be altered and extended by other plugins and themes.
      6. The SEO Bar is now regenerated in full when performing a quick or bulk edit.
      7. Changed the assessments:
        • Title:
          • Added absent title checks.
          • Added absent branding checks.
          • Added page protective state prefix checks.
          • Added term prefix checks.
          • Added a disclaimer to the title length calculation, as it’s not using pixels.
          • Improved duplicated branding checks:
            1. It now always asserts–even if the branding has been disabled.
            2. It now supports Unicode and ignoring capitalization thereof.
        • Description:
          • Added page excerpt generation checks.
          • Added page builder state checks.
          • Added page protective state empty checks.
          • Added a disclaimer to the description length calculation, as it’s not using pixels.
          • Tweaked the duplicated-word checks, by bothering you less often on shorter words.
        • Indexing:
          • Added meta override checks.
          • Added literal exclaiming when WordPress overrules the SEO settings.
          • Added term multiple post type checks.
          • Added term zero post count checks.
          • Added for non-standard robots.txt file checks.
          • Added externally-pointing canonical URL tests.
        • Following:
          • Added indexing conflict checks.
          • Added meta override checks.
          • Added literal exclaiming when WordPress overrules the SEO settings.
          • Added term multiple post type checks.
          • Added for non-standard robots.txt file checks.
        • Archiving:
          • Added indexing conflict checks.
          • Added meta override checks.
          • Added literal exclaiming when WordPress overrules the SEO settings.
          • Added term multiple post type checks.
          • Added for non-standard robots.txt file checks.
        • Redirect:
          • This test has been added to terms.
    • Spam control:
      • Added more index.php files which prevent nasty backlinks and crawlers to index this plugin’s files when Indexes option isn’t disabled in Apache.
        • Add Options -Indexes to your .htaccess file to prevent this for all plugins that do not honor this behavior.
    • Tooltips:
      • Their text is no longer bolded by default, so you may now see different bolding in the tooltip.
      • They now intelligently determines the position of themselves, much like a self-driving car.
      • They now intelligently makes themselves wider or slimmer based on their contents and surrounding.
        • When you make text wider or slimmer, it becomes taller. So, it also calculates whether it will overflow to the top or bottom of its container.
      • Using Gutenberg with the Post SEO settings meta box in the sidebar, tooltips will no longer overflow.
  • Changed:
    • We now support WordPress v4.9 and later, instead of WordPress v4.6 and later.
    • We now support PHP v5.6 and later, instead of PHP v5.4 and later.
    • Backslashes (back-solidus) are no longer stripped from post and term meta. Slash away!
      • This also resolves an issue where the number of slashes is halved every time you update the post.
      • Note that the site settings don’t support this functionality, because WordPress implements PHP 4.x compatibility on options that strip them (this is explained as “not fixed” below).
    • We switched the homepage title option name from left to right, and right to left.
      • This doesn’t affect your titles, it’s only semantics.
    • Schema.org logos may now be of any proportion, instead of just square, and cropping them must exceed 112px squared.
    • We removed the “recommended” title separator highlighting, RSS parsers are great at rendering HTML entities, so this shouldn’t be an issue.
    • We no longer automatically resize images when they’re deemed too large.
      • Although this worked as intended, we highly doubt users will be uploading images over 4096x4096px, and it’s a waste of resources to test each image.
      • Moreover, these resized images weren’t registered with WordPress.
      • When images are found to be over 4096 pixels in width or height, they’ll be discarded.
    • SEO meta generation no longer occurs when using Customizer.
    • Redirects may now be set and performed on protocols other than http and https.
    • Term meta:
      • When a term is disabled via the post type settings, saving it won’t erase the custom SEO term meta.
        • The same behavior already applied to singular post items.
    • Redirects: When a post is redirected, it will no longer be included in the sitemap. So, you no longer have to fiddle with the “index” setting to get the expected result.
    • Sitemap:
      • The sitemap post limit now counts all posts, pages, and custom post types; instead of them separately.
        • Note: Because we can’t guess your intent when you set the option (or left it unchanged), we aren’t updating this.
        • New users will have this setting set to 3000 posts, from 1200 previously.
      • The post query has changed:
        1. First, we find the blog and front-page ID. We add these on top of the sitemap.
        2. Then, we query all public hierarchical post types (pages). The sitemap query limit is used here, and we query-sort the items by published date, ascending.
        3. Then, we query all public non-hierarchical post types (posts), but no attachments–the sitemap query limit is used again, and we query-sort the items by last updated, descending.
        4. Finally, we combine the pages and posts and add them to the file.
          • Note that, at most, 50000 items will be displayed, or 49998 if the homepage is a blog.
      • The blog page’s priority is now 1.0, from 0.9. Note, however, that this feature is disabled by default and deprecated by some search engines.
      • The page and post priorities now deduce based on item’s position from 0.9, instead of that pages always has a priority of 0.9.
  • Removed:
    • DOWNGRADE COMPATIBILITY! – Global options.
      • When you upgrade to this version or later, you can’t downgrade to v3.0.6 or lower without running into issues.
        • Downgrading to v3.1.x and v3.2.x is still supported.
        • Upgrading from v2.7.0 and higher to this version is still supported.
        • Upgrading from all the way below v2.7.0 (December 2016!) is untested and might cause issues. Double-check your settings!
    • DOWNGRADE COMPATIBILITY! – Post, Page, Term meta options.
      • We added multi-dimensional options in the form of qubits: -1, 0, 1.
      • When you upgrade to this version, robots meta-settings for posts, pages, and terms may have a new, previously unknown value entered: -1.
        • This value means “override and disable the default robots-setting”.
          • For example, if you set noindex to all tags, but set -1 to a specific tag, the tag may be indexed.
        • Because the options were simpler before, -1 will be treated as 1. So, when you downgrade, this setting will yield the opposite effect.
    • DOWNGRADE COMPATIBILITY! – Homepage title options.
      • The title additions locations (left and right) are now switched.
      • This was necessary to flatten and simplify the title-API, so home-specific checks no longer need to be reversed.
    • Webkit flexbox vendor prefixes in all CSS files. All browsers that relied on these have been updated to the latest spec since.
    • Image support for breadcrumbs has been removed, search engines don’t seem to use these, and documentation for it disappeared.
  • Fixed:
    • Accessibility:
      • Global:
        • Our input-select elements no longer overflow to the right (or left, on RTL) of your screen. Most notably, with our Focus extension‘s homonymous example selector.
        • Dismissible notices can now be dismissed using keyboard navigation on all browsers.
        • You can now focus the SEO Bar items. Screen readers will exclaim what’s written.
        • In extent, tooltips are now displayed when focusing an element that has one.
        • And, in extent to that, you can now focus on [?] marks that aren’t hyperlinks, and the pixel counter.
        • Fixed an inexplicable text rendering issue for tooltips on iOS.
        • Added extra padding to the notifications, so that the dismiss-button won’t overlap the text.
      • Post edit:
        • When using the Block Editor, after saving the page, setting changes are registered consistently again to the “Are you sure you wish to leave with unsaved changes?” notification handler.
        • When you’re editing the homepage via the post settings, the disabled “Remove the blog name?” option’s tooltip now states it’s handled elsewhere.
      • Settings page:
        • When pasting a webmaster code tag in the respective settings field, no change listener was invoked, and you didn’t get an “Are you sure?”-message when navigating from the settings page with unchanged settings.
        • When you clear a sitemap color input field, the default color is now displayed correctly.
        • Trailing, leading, and double spaces are now trimmed from the homepage title examples.
        • We moved the Meta Title Additions for the Homepage Settings to the Additions tab. This way, users are less prone to fill these in the wrong order.
    • Meta tags:
      • We now correctly strip HTML tags from the “Biographical Info” section for the description-generator on author archives.
    • Usability:
      • Settings:
        • The global site-wide noindex option no longer sets nofollow automatically, too. Albeit, that’s implied.
        • The global site-wide nofollow option now has an effect.
        • The global category and tag noarchive options now have an effect.
        • The global category and tag noindex options no longer set noarchive automatically, too. Albeit, that’s implied.
        • The post type robots-meta and disable-SEO settings now only apply to taxonomies and terms that have all their shared post types set, instead of just the post type of the most recent post published.
          • The settings screens’ contextual information reflect this change.
        • When setting merely space-characters in any Twitter Profile fields, no longer a lone @ will be set.
      • Meta:
        • When you re-save a pre-escaped HTML input in the title or meta-fields, they will no longer be reparsed.
          • Before: — -> — -> —
          • Now: — -> — -> — -> to infinity and beyond.
        • The dash - separator selection has been removed, because it was converted to – via WordPress’ texturization.
          • All sites using this separator will be upgraded to use the ndash option now.
          • We couldn’t leave this separator in as it was obstructing a security feature. To secure the title’s output, without losing any information you put it, we relied on WordPress texturization options.
            • We could extrude that from WordPress’ texturization, but that’d cause discrepancy, and, as such, it wouldn’t be sustainable.
        • The post status is now correctly refreshed after using quick-edit.
        • The primary term ID is now correctly visually preassigned for posts that have been created when this plugin wasn’t active.
    • Nitpicking:
      • The sitemap’s XSL stylesheet no longer uses the latest post ID to determine the title generation; instead, it always adds your blog name to the right.
      • The sitemap now can’t exceed the imposed 50,000 entry-limit; unless you use custom filters to extend the sitemap beyond that.
      • The plugin is no longer booted again when a compatibility file is loaded.
        • This issue couldn’t propagate thanks to “Just In Time” checks, but since we now emit warnings, it’s been discovered.
      • If the homepage is a page, and it’s private or protected, the homepage Meta Title field on the SEO Settings page now reflects that.
      • Our upgrader is fast. However, just in case, we increased the PHP execution-timeout to 300 seconds, instead of the standard 30~60 seconds.
        • This may not yield useful on every site, depending on the PHP configuration.
      • Upgrading can no longer incur race conditions, where upgrades run simultaneously.
      • Redirects no longer occur when using the Customizer.
        • WordPress already has protection against this by bouncing you back; but, you can now preview the page.
      • The notice-dismiss button color is now the same as WordPress’ default.
    • Compatibility:
      • We took our hands off the WordPress Rewrite system. All plugin conflicts related to this are no longer our problem–albeit, it never was our fault.
        • Now, we can implement Polylang’s broken system for the sitemap finally. As such, all sitemaps now work with Polylang.
          • Keep in mind, however, that you may wish to enable the “Hide URL language information for default language” option to remove the base language requirement to output the main language’s sitemap.
        • Thanks to Yoast’s broken and misrepresenting NGINX rules, we received too many support inquiries and negative responses as it’s not interchangeable; so, we went out of our way to make them compatible.
      • When using Jetpack’s Publicize, their Twitter Card and Open Graph tags are now outputted only when TSF’s are disabled.
        • When using TSF’s Open Graph tags, Jetpack’s Twitter Card meta tags are removed as well. As for why; see this issue comment.
  • Not fixed:
    • Note: These bugs are edge-cases that were found during “breaking sessions.” The overhead required to fix these is currently too high for further consideration.
    • Titles:
      • When you empty your site name in the general settings (which you should never do), title examples in the settings area may fail to process correctly.
      • Some WordPress character texturizations aren’t converted in the title inputs, only conditionally in the placeholders, and always in the tags. For example, -- should become — (—).
        • WordPress is becoming more JavaScript-powered every release, we may see compatibility forwarded in the future.
      • Back-solidi (\) are not stripped from the autogenerated titles and descriptions.
        • Because WordPress applies PHP 4 compatibility on some settings by stripping back-solidi, and then not on others, this leads to inconsistent behavior we’d have to fix independently for each case.
        • Takeaway: Don’t use backward solidi in WordPress, use \ if you must instead.
      • The “Remove the blog name?” option is not hidden when doing so globally.
        • We highly discourage using the global option to achieve the implied behavior. We left this option visible to let users recognize there is an alternative since.
    • Interface:
      • When navigating away from the settings page, after changing the “category prefix” setting, the related example no longer reflects the adjusted setting.
      • This bug is introduced in this version. Using EdgeHTML, when hovering (without moving) or focusing (either via keyboard navigation or via touching) an object with a tooltip, both our tooltip as the browser-native tooltip is shown.
        • This is something we can’t fix. See https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6793560/.
        • Bootstrap has the same issue and denied a fix for it. See https://github.com/twbs/bootstrap/issues/18692 & https://github.com/twbs/bootstrap/pull/19434.
        • This will be fixed when EdgeHTML phases out in favor for Chromium (Blink). This will be sooner than later.
        • Workaround: Refocus the element or move your mouse, and the browser-tooltip should be gone.
      • This bug is introduced in this version. When loading in the new image-preview tooltip in the sidebar of Gutenberg for the first time, the browser needs to paint the image in the GPU memory. This takes too long (yes, anything above 15ms is too long), and causes our calculations to fail.
        • For us to account for this, we need to know when the image finishes painting, and then show (or readjust) the tooltip. This is a janky experience.
          • We already preload the image in your browser-cache (either disk or RAM), but we don’t paint it preemptively.
        • The workaround for this issue is to load in the tooltip once, remove your pointer, and then reload it.
        • The horrendously small working area in the Gutenberg sidebar is unwelcomed by many among us. We still are not confident in moving our meta box there permanently.
    • Terms:
      • When all posts attached to a term have been excluded from being shown in archives, the term may be empty, and noindex will be set. The SEO Bar isn’t aware of this, because it doesn’t run such a query.
        • Making it aware may slow your admin queries down significantly. We haven’t found the time to see if this can be integrated via available queries.
    • Settings:
      • Back-solidi (\) are not parsed correctly for the SEO Settings. This is because WordPress handles our sanitization for those settings, and they process slashes before sending the data to us.
        • So, input this: \\z, and you get this: \z. Save again, and you get this: z. In other words, try using \, instead.
    • Images:
      • When default social images are found to be too large, they’ll be discarded, and no fallbacks will be used instead.
        • Your input images must either be intentionally malformed, or the images are far too large (over 4096 pixels in width or height).
    • Filters:
      • When using custom meta title or meta description filters, they may not be processed for the meta input fields.
        • Fixing this requires restructuring the custom title and description methods with flags.
    • Block editor:
      • When you edit the homepage via the block editor, and add or remove any of the homepage social descriptions via the SEO Settings page, and then hit “Update” in the block editor, the social-locks won’t be altered.
        • Fixing this would require us to send out new locks.
        • This is such an edge-case that would be “fun” if we made that seamless, but “practical” is another word that stops us here. If we didn’t mention it, we’d dare to bet none of our 100,000 users would’ve ever noticed in a million years.
        • Moreover, WordPress is far from ready when it comes to a REST-driven admin interface. Ignoring Calypso, Gutenberg is the only first-party WP interface that is JS driven. There are still no tools available for us that would allow us to integrate this easily.
    • Redirects:
      • We’re not detecting whether a redirect will loop back to the current page.
        • To do this accurately, we need to predict whether the endpoint leads to any of the plausible endpoints WordPress supports. This is expensive on your CPU.
        • WordPress’ rewrite system also has autocompletion, among other features, that will force us to follow the redirect internally before sending it out.
        • And last but not least, we can’t detect whether a query parameter is handled further by other systems embedded, like translation plugins.
        • To resolve all of the above, we could send a custom “Redirect by” and “Redirecting to” headers, and test for that to prevent a loop. Unfortunately, these headers are volatile when WordPress or other plugins take over (multiple endpoints, autocompletion, etc.), nullifying this effort.
        • To conclude, just make sure you don’t point the redirect URL to the page itself.
      • We’re not detecting whether redirects are initiated on post-as-archive pages, like the blog page, or the WooCommerce product page.
        • Redirecting such pages may cause unexpected behavior on subsequent endpoints that rely on these pages as their base points.
        • This also accounts for many other pages, like those, which are based on a custom field or shortcode.

For translators

  • Added:
    • Many, many sentences. Sorry!
  • Changed:
    • For the nth time: everything.
    • Notable changes:
      • Fewer %s, more Markdown. This means that you’ll see fewer fragmented sentences, but do mind the Markdown markup!
      • home page is now consistently homepage.
      • Removed and replaced (most) instances of grammatical gender-(in)sensitive pronouns which would make it impossible to translate correctly, like this %s, that %s, and the %s.
  • Updated:
    • Translation object; POT file.

For developers

Note: Only public changes are listed; internal functionality changes are listed as global “improvements.”

  • General changes:
    • Improved:
      • The class autoloader now considers folder structure automatically, based on the namespace used.
      • The script loader now discerns between posts and taxonomies, and can now prevent loading scripts when SEO is disabled for the post type or taxonomy.
      • In the debug interface, the JSON+LD scripts are now more readable.
      • The script loader now accepts and concatenates inline JS.
    • Changed:
      • Custom setting tabs and their content-callbacks should no longer have their output be returned, but always be printed instead.
      • The helper tooltip item for primary-term selection using the classic editor may now be repositioned differently, to account for the new keyboard-navigation-supported tooltips, making the positioning more natural.
    • Removed:
      • PHP 5.4 and 5.5 support. The plugin now requires PHP 5.6 and higher.
        • With that, we no longer require using wp_json_encode(), wp_parse_url(), and other backward-compatible functionality.
        • You’ll also find that our API has changed. For example, we now use generators to yield images.
    • Fixed:
      • Registering inline scripts now correctly converts {{$rel_color}}.
  • Option notes:
    • Added:
      • Under THE_SEO_FRAMEWORK_SITE_OPTIONS:
        • seo_bar_symbols
          • Values: either 1 or 0.
          • Default: 0, for all sites.
          • Location: General Settings -> Layout -> SEO Bar Settings.
          • Use: Converts SEO Bar item symbols based on their state.
        • multi_og_image
          • Values: either 1 or 0.
          • Default: 1 for new sites. 0 for upgraded sites.
          • Location: Social Meta Settings -> General -> Social Image Settings.
          • Use: Allows TSF to output multiple OG image tags. This does NOT affect the image parser.
        • ping_use_cron
          • Values: either 1 or 0.
          • Default: 0, for all sites.
          • Location: Sitemap Settings -> Ping -> Ping Settings.
          • Use: Enables pinging via cron-jobs.
      • Under THE_SEO_FRAMEWORK_SITE_CACHE:
        • settings_notice
          • Values: Any simple string.
          • Default: '' (empty string).
          • Use: Holds the last-update action resolution state, so proper notifications can be send.
    • Changed:
      • Under THE_SEO_FRAMEWORK_SITE_OPTIONS:
        • home_title_location
          • Values: either left or right, as before.
          • Default: left when RTL (Arabic, Hebrew, etc.), right when LTR (Roman, Latin, etc.).
          • Location: Homepage Settings -> Additions -> Meta Title Additions Location.
          • Use: Sets the location of the Home Page Title and the Additions.
          • What’s changed: When upgrading, left becomes right, and vice versa. This simplifies the API.
    • Removed:
      • attachment_noindex and sanitization thereof, since 3.1 it’s changed to noindex_post_types['attachment'].
      • attachment_nofollow and sanitization thereof, since 3.1 it’s changed to nofollow_post_types['attachment'].
      • attachment_noarchive and sanitization thereof, since 3.1 it’s changed to noarchive_post_types['attachment'].
      • title_seperator, since 3.1 it’s changed to title_separator (note the previous typo).
    • Changed: add_option() is no longer called every admin request, only when the options aren’t registered yet.
  • Term meta notes:
    • Added:
      • title_no_blog_name, int, one or zero.
      • redirect, URL string.
      • canonical, URL string.
      • og_title, URL string.
      • og_description, URL string.
      • tw_title, URL string.
      • tw_description, URL string.
      • social_image_url, URL string.
      • social_image_id, int.
    • Changed:
      • noindex, it can now be set to -1.
      • nofollow, it can now be set to -1.
      • noarchive, it can now be set to -1.
    • Removed:
      • saved_flag, it’s no longer necessary. If the term is saved with The SEO Framework enabled, the term meta will be auto-filled; whether something’s adjusted or not.
    • Other:
      • Arbitrary keys may no longer be stored. Use the new API instead.
  • Post meta notes:
    • Changed:
      • _genesis_noindex, it can now be set to -1.
      • _genesis_nofollow, it can now be set to -1.
      • _genesis_noarchive, it can now be set to -1.
      • Throughout the plugin, the scalar input types are no longer converted to strings via the_seo_framework()->get_custom_field().
    • Other:
      • Arbitrary keys may no longer be stored. Use the new API instead.
  • Function notes:
    • Removed:
      • These are non-API functions, and were marked private:
        • _activation_setup_sitemap()
        • _deactivation_unset_sitemap()
  • Class notes:
    • Noted:
      • Methods that you shouldn’t use–marked private or aren’t visible–aren’t listed here, unless specifically annotated.
    • Added classes:
      • \The_SEO_Framework\Bridges\ family:
        • \The_SEO_Framework\Bridges\ListEdit, this class extends \The_SEO_framework\Bridges\ListTable.
        • \The_SEO_Framework\Bridges\ListTable, holds basic caller functionality for WordPress list tables.
          • Abstract class. Must be extended.
          • Public methods:
            • __construct(), initializes the callbacks, always.
          • Abstract methods:
            • _add_column()
            • _output_column_contents_for_post()
            • _output_column_contents_for_term()
              • Note that this function must be created and return its first parameter as it’s a filter callback.
          • Shared properties:
            • $post_type
            • $taxonomy
            • $doing_ajax
        • \The_SEO_Framework\Bridges\Ping, holds callback functionality for search engine sitemap-pinging and cron-engaging thereof.
          • This class can’t be instantiated.
          • Public static methods:
            • engage_pinging_cron()
            • ping_search_engines()
            • ping_google()
            • ping_bing()
        • \The_SEO_Framework\Bridges\PostSettings
          • This class can’t be instantiated.
          • This class is marked protected, only for internal use.
        • \The_SEO_Framework\Bridges\Scripts, this file bridges The SEO Framework to the script loaders for WordPress. It registers all scripts based on the query; by default, it is only loaded on the admin screens.
          • Relies on \The_SEO_Framework\Builders\Scripts for registering and enqueuing the scripts.
          • Public static methods:
            • decode_entities()
            • decode_all_entities()
            • prepare_media_scripts()
            • prepare_metabox_scripts()
            • get_tsf_scripts()
            • get_tt_scripts()
            • get_ays_scripts()
            • get_list_edit_scripts()
            • get_seo_settings_scripts()
            • get_post_edit_scripts()
            • get_term_edit_scripts()
            • get_gutenberg_compat_scripts()
            • get_media_scripts()
            • get_title_scripts()
            • get_description_scripts()
            • get_social_scripts()
            • get_primaryterm_scripts()
            • get_counter_scripts()
        • \The_SEO_framework\Bridges\SeoBar, this file bridges The SEO Framework to the SEO Bar loaders for WordPress. It prepares the list table columns and checks for post type and taxonomy compatibility.
          • Relies on \The_SEO_Framework\Builders\SeoBar package for building the SEO Bar via interpreters.
            • This package relies on \The_SEO_framework\InterPreters\SeoBar for interpreting the SEO Bar from PHP to HTML.
          • This is a private class, and should not be called externally.
        • \The_SEO_Framework\Bridges\SeoSettings
          • This class can’t be instantiated.
          • This class is marked protected, only for internal use.
        • \The_SEO_Framework\Bridges\Sitemap, this file initializes the sitemap query, and it’s loaded when the sitemap functionality is enabled.
          • Public methods:
            • get_expected_sitemap_endpoint_url()
            • get_sitemap_endpoint_list()
            • output_base_sitemap()
            • output_stylesheet()
            • output_sitemap_header()
            • output_sitemap_urlset_open_tag()
            • output_sitemap_urlset_close_tag()
          • Public static methods:
            • get_instance()
            • prepare()
        • \The_SEO_Framework\Bridges\TermSettings
          • This class can’t be instantiated.
          • This class is marked protected, only for internal use.
      • \The_SEO_Framework\Builders\ family:
        • \The_SEO_Framework\Builders\Images, this file holds generators for images.
          • Example usage:
            • Get one URL:
              • $url = \The_SEO_Framework\Builders\Images::get_featured_image_details()->current()['url'];
            • Get multiple URLs, if any:
              • foreach ( \The_SEO_Framework\Builders\Images::get_featured_image_details() as $details ) { $url = $details['url']; }
          • Public static methods, all generators:
            • get_attachment_image_details
            • get_featured_image_details
            • get_content_image_details
            • get_fallback_image_details
            • get_theme_header_image_details
            • get_site_logo_image_details
            • get_site_icon_image_details
        • \The_SEO_framework\Builders\SeoBar
          • Abstract class. Must be extended.
          • This class can’t be instantiated, nor can any extending classes.
          • Public static variables:
            • tests, for all registered tests, there must be a corresponding test_%s function created.
          • Public methods:
            • _run_test()
          • Abstract methods: These must be created.
            • has_blocking_redirect()
            • prime_cache()
            • prime_query_cache()
          • Shared variables: All extending classes can modify these variables, and all those classes will be affected once they are.
            • tsf
            • query
            • cache (private)
        • \The_SEO_framework\Builders\SeoBar_Page, this class extends \The_SEO_framework\Builders\SeoBar.
          • Use the_seo_framework()->get_generated_seo_bar() to generate a bar.
        • \The_SEO_framework\Builders\SeoBar_Term, this class extends \The_SEO_framework\Builders\SeoBar.
          • Use the_seo_framework()->get_generated_seo_bar() to generate a bar.
        • \The_SEO_Framework\Builders\Sitemap, this file builds the sitemap, and it’s only loaded on sitemap endpoints.
          • You can extend this class to add your own sitemap interpreters.
          • Public methods:
            • __construct()
            • __destruct()
            • prepare_generation()
            • shutdown_generation()
            • build_sitemap(), abstract.
            • is_post_included_in_sitemap()
            • is_term_included_in_sitemap()
        • \The_SEO_Framework\Builders\Sitemap_Base, extends \The_SEO_Framework\Builders\Sitemap.
          • Public methods:
            • build_sitemap(), abstractly defined.
      • \The_SEO_Framework\InterPreters\ family:
        • \The_SEO_framework\InterPreters\SeoBar, this file interprets the SEO Bar from PHP to HTML.
          • This class can’t be instantiated.
          • Public constants:
            • STATE_UNKNOWN, bitwise 1.
            • STATE_BAD, bitwise 2.
            • STATE_OKAY, bitwise 4.
            • STATE_GOOD, bitwise 8.
          • Public static variables:
            • query, the current SEO Bar query.
          • Public static methods:
            • generate_bar()
            • &collect_seo_bar_items()
            • register_seo_bar_item()
            • &edit_seo_bar_item()
    • Removed classes:
      • \The_SEO_Framework\Compat
        • Loading fewer PHP files is faster, ~0.00001s is saved.
        • The two methods therein were moved to \The_SEO_Framework\Load:
          • load_early_compat_files(), protected.
          • _include_compat(), marked private.
      • \The_SEO_Framework\Metaboxes
        • All but one of the methods therein were moved to \The_SEO_Framework\Bridges\SeoSettings.
        • Method nav_tab_wrapper() was moved to \The_SEO_Framework\Admin_Pages.
      • \The_SEO_Framework\Inpost
        • This is part of the facade object the_seo_framework(), so this class shouldn’t be called externally.
      • \The_SEO_Framework\Sitemap
        • This file has been split over multiple files, to the new Sitemap class family:
          • \The_SEO_framework\Bridges\Sitemap
          • \The_SEO_framework\Builders\Sitemap, extended by:
            • \The_SEO_framework\Builders\Sitemap_Base
      • \The_SEO_Framework\Doing_It_Right
        • Some methods therein have been moved to the new SeoBar class family:
          • \The_SEO_framework\InterPreters\SeoBar
          • \The_SEO_framework\Bridges\SeoBar
          • \The_SEO_framework\Builders\SeoBar, extended by:
            • \The_SEO_framework\Builders\SeoBar_Page
            • \The_SEO_framework\Builders\SeoBar_Term
    • Rmeoved interfaces:
      • \The_SEO_Framework\Debug_Interface
        • Implementors:
          1. \The_SEO_Framework\Load
          2. \The_SEO_Framework\Debug
        • Was redundant, added to load time. Removed.
  • Method notes:
    • For object the_seo_framework():
      • Added:
        • get_filtered_raw_custom_field_title()
        • get_filtered_raw_generated_title()
        • s_qubit(), note: this method is not registered as an option filter!
        • s_bsol_raw(), note: this method is not registered as an option filter!
        • make_data_attributes(), internal use only.
        • make_single_select_form()
        • get_post_types_from_taxonomy()
        • get_generated_seo_bar()
        • is_robots_meta_noindex_set_by_args()
        • init_term_meta()
        • save_term_meta()
        • update_single_term_meta_item()
        • save_post_meta()
        • update_single_post_meta_item()
        • s_term_meta()
        • s_post_meta()
        • detect_page_builder()
        • is_blog_page_by_id()
        • query_supports_seo()
        • is_taxonomy_supported()
        • get_post_meta_defaults()
        • get_taxonomical_canonical_url(), note: this method was called get_taxonomial_canonical_url()
        • get_taxonomical_custom_canonical_url()
        • get_term_meta_item()
        • use_taxonomical_title_branding()
        • is_attachment_admin()
        • is_wc_product_admin()
        • is_customize_preview()
        • get_safe_schema_image()
        • s_image_details()
        • s_image_details_deep()
        • s_field_id()
        • esc_attr_preserve_amp()
        • get_hierarchical_post_types()
        • get_nonhierarchical_post_types()
        • convert_to_url_if_path()
        • detect_site_url_scheme()
      • Changed:
        • __construct() now emits warnings when instantiated twice or more.
        • html_output() is now marked as private.
        • init_admin_scripts() removed deprecated parameter and its notice.
        • set_url_scheme() removed deprecated parameter and its notice.
        • is_preview() now checks the user capabilities, because WordPress blindly agrees with this state.
        • can_do_sitemap_robots() now uses has_robots_txt() and get_robots_txt_url() to determine validity.
        • get_robots_txt_url() now returns the robots.txt URL, even if one’s physically set.
        • sanitize_field_id() now no longer strips square brackets.
        • robots_meta() now has two new parameters.
        • add_option_filter() no longer registers its filter more than once for an option key.
        • register_settings() no longer uses add_option() when the options are already registered.
        • inpost_seo_save()
          1. Renamed to _update_post_meta().
          2. Now coverts _genesis_noindex, _genesis_nofollow, and _genesis_noarchive to qubits.
          3. Now allows updating during WP_CRON, this was to protect against bulk-and quick-edit; which we now handle.
          4. Now allows updating during WP_AJAX, this was to protect against bulk-and quick-edit; which we now handle.
        • _update_term_meta()
          1. Renamed from update_term_meta
          2. noindex, nofollow, noarchive are now converted to qubits.
          3. Added new keys to sanitize.
          4. Now marked as private.
          5. Added more sanity protection. Superfluous: “Does the term exist? Is the user allowed to do this?”
          6. No longer runs when no autodescription-meta POST data is sent.
          7. Now uses the current term meta to set new values.
          8. No longer deletes meta from abstracting plugins on save when they’re deactivated.
          9. Now allows updating during WP_AJAX, this was to protect against bulk-and quick-edit; which we now handle.
        • get_term_meta()
          1. Removed deprecated filter the_seo_framework_get_term_meta.
          2. Now fills in defaults if older term meta isn’t set.
        • get_term_meta_defaults() now returns more values.
        • is_single_admin() now uses is_singular_admin() to check for the correct base; so categories and tags no longer falsely return true.
        • get_generated_single_term_title() no longer redundantly tests the query, but now only uses the term input or queried object.
        • is_taxonomy_disabled() now only returns true if all post types in the taxonomy are disabled, and it uses is_post_type_supported() instead of is_post_type_disabled() to do so.
        • create_canonical_url() and get_canonical_url() can now fetch custom canonical URLs for terms; this is implied via trickling down methods.
        • create_canonical_url() now fixes input args preemptively to be in line with other getters. So you could enter an integer, although you shouldn’t.
        • get_post_type_archive_canonical_url()
          1. Now only accepts strings or null as the first parameter.
          2. Forwarded post type object calling to WordPress’ URL function.
        • _save_inpost_primary_term() now works as intended; and it no longer accidentally falls back on the other post meta handlers.
        • get_separator_list() no longer returns the dash index.
        • get_home_title_seplocation() return value, left is now right, and vice versa.
          • In extent, get_title_seplocation( true ) also yields this behavior.
        • s_twitter_name()
          1. Now returns empty on lone @ entries.
          2. Now returns empty when using only spaces and tabs.
        • s_facebook_profile()
          1. No longer returns a plain Facebook URL when the entry path is sanitized to become empty.
          2. Now returns empty when using only spaces and tabs.
        • s_relative_url() no longer trims the prepending /.
        • s_redirect_url()
          1. Removed rudimentary relative URL testing.
          2. Removed input transformation filters, and with that, removed redundant multisite spam protection.
          3. Now allows all protocols. Enjoy!
          4. Now no longer lets through double-absolute URLs (e.g. https://google.com/https://google.com/path/to/file/) when filter the_seo_framework_allow_external_redirect is set to false.
            • This isn’t a security issue, do_redirect() always prepended (and still does prepend) the local host for sanity.
        • has_robots_txt(), now tries to load wp-admin/includes/file.php to prevent a fatal error.
        • has_sitemap_xml(), now tries to load wp-admin/includes/file.php to prevent a fatal error.
        • is_term_meta_capable() no longer incorrectly determines post type archives as capable; they aren’t considered term taxonomies, because they don’t yield taxonomies, but solely post types.
        • is_category_admin() removed memoization.
        • is_page(), now tests for hierarchical post types, which is more reliable.
        • is_single() now tests for nonhierarchical post types, which is more reliable.
        • is_singular() now no longer processes integers as input.
        • is_singular_admin() removed first parameter.
        • is_tag_admin() removed memoization.
        • is_wc_product()
          1. Added admin support.
          2. Added a parameter for the Post ID or post to test.
        • delete_object_cache() now actually does something: flushes the object cache.
        • get_available_twitter_cards()
          1. Now only asserts the social titles as required.
          2. Now always returns an array, instead of a boolean (false) on failure.
        • nav_tab_wrapper(), deprecated third parameter, silently.
        • get_word_count()
          1. Now expects PCRE UTF-8 encoding support.
          2. Moved filter outside of this function.
          3. Short length now works as intended, instead of comparing as less, it compares as less or equal to.
        • get_social_image_uploader_form() now adds a media preview dispenser.
        • get_logo_uploader_form() now adds a media preview dispenser.
        • strip_tags_cs() now allows emptying the indexes space and clear.
        • generate_dismissible_notice() now adds a tabindex to the dismiss-dashicon, so keyboard naviation is possible.
        • get_home_page_tagline() added memoization.
        • can_run_sitemap() no longer memoizes the return value.
      • Removed:
        • Deprecated methods, these were marked deprecated since 3.1.0 (September 13, 2018):
          • get_meta_output_cache_key()
          • get_prefered_scheme()
          • description_from_cache()
          • title()
          • build_title()
          • generate_home_title()
          • get_the_real_archive_title()
          • use_archive_prefix()
          • untitled()
          • add_title_pagination()
          • process_title_additions()
          • title_from_cache()
          • single_term_title()
          • post_title_from_ID()
          • title_from_custom_field()
          • get_tax_labels()
          • get_supported_post_type()
          • get_paged_post_url()
          • is_checked()
          • is_option_checked()
          • generate_description()
          • description_from_custom_field()
          • generate_description_from_id()
        • Public methods, these were marked private:
          • set_js_nonces()
          • get_js_nonces()
          • inattachment_seo_save()
          • inpost_seo_save()
          • save_custom_fields()
        • Public methods, these were obstructing:
          • is_post_included_in_sitemap(), use new \The_SEO_Framework\Builders\Sitemap()->is_post_included_in_sitemap() instead.
          • load_assets(), this was an internal function that only loaded a few scripts on our admin page.
          • settings_init(), this was an internal function that handles the update state automatically. Is now _settings_init().
          • get_sitemap_xsl_stylesheet_tag(), this is now part of \The_SEO_Framework\Builders\Sitemap::get_instance()->output_sitemap_header().
          • get_sitemap_urlset_open_tag(), we now output it directly.
          • get_sitemap_urlset_close_tag(), we now output it directly.
        • Public methods, these were meant to only be used internally:
          • add_inpost_seo_box_init()
          • add_taxonomy_seo_box_init()
          • general_metabox()
          • title_metabox()
          • description_metabox()
          • robots_metabox()
          • homepage_metabox()
          • social_metabox()
          • webmaster_metabox()
          • sitemaps_metabox()
          • feed_metabox()
          • schema_metabox()
          • add_post_state()
          • post_state()
        • Public methods, are now rendered ineffective:
          • post_type_supports_inpost()
          • enqueue_page_defaults()
          • add_removable_query_args()
          • register_image_dimension()
          • parse_image_args()
          • parse_og_image()
          • rewrite_rule_sitemap()
          • enqueue_sitemap_query_vars()
          • reinitialize_rewrite()
          • enqueue_rewrite_activate()
          • enqueue_rewrite_deactivate()
          • maybe_flush_rewrite()
          • flush_rewrite_rules_activation()
          • flush_rewrite_rules_deactivation()
          • maybe_output_sitemap_stylesheet()
          • seo_bar()
          • seo_bar_ajax()
          • get_seo_bar_ajax()
          • get_taxonomy_seo_bar_ajax()
          • get_taxonomy_seo_bar()
          • get_the_seo_bar_classes()
          • get_the_seo_bar_i18n()
        • Many more public methods were removed:
          • See the “class notes” above.
      • Deprecated:
        • With alternatives, refer to the source (search for your old method) for a relayed alternative:
          • get_default_scripts(),
          • enqueue_gutenberg_compat_scripts()
          • enqueue_media_scripts()
          • enqueue_primaryterm_scripts()
          • get_seo_bar()
          • post_status()
          • metabox_scripts()
          • Scripts()
          • doing_ajax()
          • initialize_term_meta()
          • ping_searchengines()
          • ping_google()
          • ping_bing()
          • get_sitemap_xsl_url()
          • get_sitemap_xml_url()
          • output_sitemap_xsl_stylesheet()
          • post_type_supports_custom_seo()
          • taxonomy_supports_custom_seo()
          • get_taxonomial_canonical_url()
          • get_custom_field()
          • get_schema_image()
          • get_social_image()
          • get_social_image_url_from_home_meta()
          • get_social_image_url_from_post_meta()
          • get_social_image_url_from_seo_settings()
          • get_social_image_url_from_post_thumbnail()
          • get_social_image_url_from_attachment()
          • get_image_from_woocommerce_gallery()
          • get_header_image()
          • get_site_icon()
          • get_site_logo()
          • sanitize_field_id()
        • Without alternatives, go make your own:
          • check_wp_locale()
          • maybe_lowercase_noun()
          • fetch_the_term()
          • current_theme_supports_title_tag()
          • can_use_logo()
  • Property notes:
    • For object the_seo_framework():
      • Removed:
        • profile_settings
        • page_defaults
  • Constant notes:
    • Namespace \The_SEO_Framework\:
      • ROBOTS_IGNORE_PROTECTION, used for the the_seo_framework()->robots_meta() method family, ignores post’s password/privacy settings.
      • ROBOTS_IGNORE_SETTINGS, used for the the_seo_framework()->robots_meta() method family, ignores current post/term’s SEO settings.
  • Action notes:
    • Added:
      • the_seo_framework_sitemap_header, runs in the sitemap HTTP header.
      • the_seo_framework_setting_notices, runs below the settings header.
  • Filter notes:
    • Added:
      • the_seo_framework_query_supports_seo, boolean.
      • the_seo_framework_sitemap_path_prefix, string.
      • the_seo_framework_sitemap_endpoint_list, array.
      • the_seo_framework_sitemap_supported_post_types, array.
      • the_seo_framework_sitemap_hpt_query_args, array. hpt = Hierarchical post types.
      • the_seo_framework_sitemap_nhpt_query_args, array. nhpt = Non-hierarchical post types.
      • the_seo_framework_sitemap_exclude_term_ids, array. Not used internally.
    • Improved:
      • the_seo_framework_robots_meta_array, now has two new parameters, $args and $ignore.
      • the_seo_framework_sitemap_post_limit, now has a new parameter, $hierarchical.
      • the_seo_framework_sitemap_additional_urls, now has a new parameter: $args.
      • the_seo_framework_sitemap_extend, now has a new parameter: $args.
      • the_seo_framework_term_meta_defaults, now has a new parameter: $term_id.
    • Changed:
      • the_seo_framework_scripts:
        1. Now contains all registered scripts.
        2. Now has a new parameter: $bridge.
      • the_seo_framework_term_meta_defaults now holds more values in the first parameter.
      • the_seo_framework_separator_list no longer yields the dash index.
      • the_seo_framework_{$type}_settings_tabs, the callback indexes have been changed for these filters, as the class structure changed:
        • the_seo_framework_inpost_settings_tabs
        • the_seo_framework_general_settings_tabs
        • the_seo_framework_homepage_settings_tabs
        • the_seo_framework_robots_settings_tabs
        • the_seo_framework_schema_settings_tabs
        • the_seo_framework_sitemaps_settings_tabs
        • the_seo_framework_social_settings_tabs
        • the_seo_framework_title_settings_tabs
      • the_seo_framework_inpost_settings_tabs, the second parameter is deprecated, and henceforth yields null.
      • the_seo_framework_bother_me_desc_length
        1. Now moved to the SEO Bar callers, and now only runs once per session.
        2. The entered and default length is now counted as a minimum, instead of a minimum minus one.
      • the_seo_framework_fetched_description_excerpt
        1. Deprecated second parameter.
        2. Added third paramter: $args.
    • Fixed:
      • the_seo_framework_title_from_generation, now works for:
        1. the homepage title in the admin screens.
        2. the homepage title example in the admin screens.
        3. the latest post title example in the admin screens.
        4. singular breadcrumb generation.
        5. blog page’s automated description.
      • the_seo_framework_title_from_custom_field, now works for:
        1. the homepage title example in the admin screens.
        2. all breadcrumb generation.
        3. term title placeholders.
      • the_seo_framework_use_title_branding now works for the homepage title in the admin screens.
      • the_seo_framework_title_separator now works in the admin screens.
    • Deprecated:
      • the_seo_framework_save_custom_fields, use the_seo_framework_save_post_meta instead. Same syntax, same effect, better name.
      • the_seo_framework_current_term_meta, use get_term_metadata instead. See WordPress function get_metadata() for details.
        • We know, the get_term_metadata filter is inconvenient, but so is maintaining and explaining the filter we deprecated.
        • This filter was largely only in place because we didn’t have bulk-editing systems in place. But now we do.
        • Moreover, there are many more hooks through which you can influence this, like when updating the term meta. Those hooks are vastly more reliable, because the term meta may be obtained elsewhere, without using our API.
    • Removed:
      • the_seo_framework_js_l10n, overhauled.
      • the_seo_framework_get_term_meta, this was deprecated since 3.1.0.
        • Use the_seo_framework_term_meta_defaults instead.
      • the_seo_framework_show_seo_column, this is unreliable. Use the options API instead.
      • the_seo_framework_custom_post_type_support, this is unreliable. Use the options API instead or the_seo_framework_supported_post_type.
      • the_seo_framework_sitemap_pages_count, use the options API or the_seo_framework_sitemap_post_limit instead.
      • the_seo_framework_sitemap_posts_count, use the options API or the_seo_framework_sitemap_post_limit instead.
      • the_seo_framework_sitemap_custom_posts_count, use the options API or the_seo_framework_sitemap_post_limit instead.
      • the_seo_framework_sitemap_exclude_cpt, use the_seo_framework_sitemap_supported_post_types instead.
      • the_seo_framework_sitemap_pages_query_args.
      • the_seo_framework_sitemap_posts_query_args.
      • the_seo_framework_sitemap_cpt_query_args.
      • the_seo_framework_admin_page_defaults, we trust our defaults are right.
      • the_seo_framework_metabox_id, this never worked reliably.
      • the_seo_framework_allow_states, use the action handler instead.
      • the_seo_framework_ogimage_output, use the new image generator, instead.
      • the_seo_framework_twitterimage_output, use the new image generator, instead.
      • the_seo_framework_sanitize_redirect_args, this was obstructing sanity.
  • Rewrite notes:
    • Removed:
      • All WordPress rewrite manipulation.
    • Changed:
      • The following query variables have changed, in the format: from -> to
        • the_seo_framework_sitemap=xml -> tsf-sitemap=base
        • the_seo_framework_sitemap=xsl -> tsf-sitemap=xsl-stylesheet
        • Note: Since these endpoints are interpreted outside of the WordPress rewrite system, these endpoints may no longer work based on your permalink settings.
  • Compatibility notes:
    • Unloaded:
      • The mbstring compatibility file (/inc/compat/php-mbstring.php) is no longer loaded automatically because we no longer need the compatibility function mb_strpos().
  • Browser notes:
    • We’ve now officially abandoned support for Internet Explorer. Goodbye, old, annoying friend.
      • To enhance support for other deprecated browsers along the way, we now use a body class we control: tsf-js and tsf-no-js. Now, this body class changes from tsf-no-js to tsf-js only when the browser supports ES2015.
    • With our most excellent users moving to HTTP/2 and beyond, we’re going to take full advantage of multiplexing by sending out more scripting files.
      • More files is good; because, we can selectively send out the files; so there’s less code to parse in your browser on most requests.
      • This also alleviates some strain on your server, as we don’t have to blindly fill states and values for all requests every time.
      • And, most importantly, maintaining the code will be much easier; so, we can deploy faster with fewer errors.
      • Last but not least, WordPress is moving from plain HTML and PHP to JS. We need to get ourselves well prepared for this shift.
      • We cleaned up the RTL-specific scripts, and embedded an RTL body-class check instead in the LTR scripts. This is slightly slower, but decreases the heft of the plugin package significantly.
      • Affected files, both .css and .js (and their *.min.* equivalents):
        • ays – meaning “Are you sure?”, these files handle on-navigation alerts, so to prevent loss of data.
          • Namespaces: window.tsfAys and window.tsfAysL10n
          • Script ID: tsf-ays
        • c, these files handle the character and pixel counters.
          • Namespaces: window.tsfC and window.tsfCL10n.
          • Script ID: tsf-c
          • Fun fact: The proposed name was counter.js, but uBlock blocks this script name by default.
          • Note that this file is loaded conditionally, based on the user’s settings.
        • post, these files handle post SEO settings pages, mostly.
          • Namespaces: window.tsfPost and window.tsfPostL10n.
          • Script ID: tsf-post
        • term, these files handle term SEO settings pages, mostly.
          • Namespaces: window.tsfTerm and window.tsfTermL10n.
          • Script ID: tsf-term
        • tsf, these main files are now trimmed down to the most basic of forms per dependency requirements.
          • JS:
            • Before: 27.7KB minified, 2782 SLOC
            • After: 2.4KB minified, 488 SLOC
          • CSS:
            • Before: 16.0KB minified, 1006 SLOC
            • After: 7.1KB minfified, 480 SLOC
        • settings, these files handle the SEO Settings page, mostly.
          • Namespaces: window.tsfSettings and window.tsfSettingsL10n.
          • Script ID: tsf-settings
        • le – meaning “List Edit”, these files handle list edit page actions.
          • Namespaces: window.tsfLe and window.tsfLeL10n
          • Script ID: tsf-le
      • Affected files, only .js (and their *.min.* equivalents):
        • description, these files handle meta description inputs.
          • Namespaces: window.tsfDescription and window.tsfDescriptionL10n.
          • Script ID: tsf-description
        • title, these files handle meta title inputs.
          • Namespaces: window.tsfTitle and window.tsfTitleL10n.
          • Script ID: tsf-title
        • social, these files handle social meta inputs.
          • Namespaces: window.tsfSocial and window.tsfSocialL10n.
          • Script ID: tsf-social
    • JS notes:
      • Announcement: There’s finally a reliable JS API for The SEO Framework. Enjoy!
      • Note: We added a plethora of events, and some now have added parameters. These are only documented in the code.
      • Added:
        • Object tsfAys:
          • Properties:
            l10n
          • Methods:
            • reset
            • getChangedState
            • registerChange
            • deregisterChange
            • registerChangeListener
            • registerResetListener
            • registerUnloadListener
            • reloadDefaultListeners
          • Related localization & attribution object: tsfAysL10n.
        • Object tsfDescription:
          • Properties:
            • l10n
          • Methods:
            • setInputElement
            • getState
            • updateState
            • triggerCounter
            • triggerInput
            • enqueueTriggerInput
            • triggerUnregisteredInput
            • enqueueUnregisteredInputTrigger
          • Related localization & attribution object: tsfDescriptionL10n.
        • Object tsfLe:
          • Properties:
            • l10n
          • Related localization & attribution object: tsfLeL10n.
        • Object tsfPost:
          • Properties:
            • l10n
          • Related localization & attribution object: tsfPostL10n.
        • Object tsfSettings:
          • Properties:
            • l10n
          • Related localization & attribution object: tsfSettingsL10n
        • Object tsfSocial:
          • Properties:
            • l10n
          • Methods:
            • initTitleInputs
            • initDescriptionInputs
            • getState
            • updateState
          • Related localization & attribution object: tsfSettingsL10n
        • Object tsfTerm:
          • Properties:
            • l10n
          • Related localization & attribution object: tsfTermL10n.
        • Object tsfTitle:
          • Properties:
            • l10n
            • untitledTitle
          • Methods:
            • setInputElement
            • getState
            • updateState
            • triggerCounter
            • triggerInput
            • enqueueTriggerInput
            • triggerUnregisteredInput
            • enqueueUnregisteredInputTrigger
          • Related localization & attribution object: tsfTitleL10n.
        • Object tsfC, including:
          • Properties:
            • counterType
            • counterClasses
            • l10n
          • Methods:
            • updatePixelCounter
            • updateCharacterCounter
            • triggerCounterUpdate
            • resetCounterListener
          • Related localization & attribution object: tsfCL10n.
        • Global:
          • After tsfAys registers or resets its listeners, this jQuery event is triggered:
            • $( document.body ).trigger( 'tsf-registered-ays-listeners' );
          • After the sidebar is closed in Gutenberg:
            • $( document.body ).trigger( 'tsf-gutenberg-sidebar-closed' );
          • After the sidebar is opened in Gutenberg:
            • $( document.body ).trigger( 'tsf-gutenberg-sidebar-opened' );
          • After a list item is updated in a WordPress list overview table:
            • document.dispatchEvent( new Event( 'tsfLeUpdated' ) );
      • Changed:
        • Object tsf, overhauled:
          • Properties:
            l10n
          • Methods:
            • stripTags
            • decodeEntities
            • escapeString
            • ampHTMLtoText
            • sDoubleSpace
            • getStringLength
            • selectByValue
            • convertJSONResponse
            • setAjaxLoader
            • unsetAjaxLoader
            • resetAjaxLoader
          • Related localization & attribution object: tsfL10n.
        • Object tsfGBC now resides in gbc.{.min}.js, instead of tsf-gbc{.min}.js
        • Object tsfTT:
          • We now add the default tooltip boundary (.tsf-tooltip-boundary) to #wpwrap, instead of #wpcontent.
      • Removed:
        • tsf.[...]:
          • tsf.counterType, use tsfC.counterType instead.
          • tsf.counterClasses, use tsfC.counterClasses instead.
          • tsf._initCounters, was marked private.
          • tsf._initWebmastersInput, was marked private.
          • tsf._initCanonicalInput, was marked private.
          • tsf.flexTabToggle.
          • tsf.addNoFocusClass.
          • tsf.setTabsOnload.
          • tsf.tabToggle.
          • tsf._doFlexResizeListener, was marked private.
          • tsf.taglineToggleOnload.
          • tsf.confirmedReset.
          • tsf.setColorOnload.
          • tsf.onLoadUnregisterChange
          • tsf.attachUnsavedChangesListener, use tsfAys.registerListeners instead.
          • tsf.registerChange, use tsfAys.registerChange instead.
        • tsfL10n.[...]:
          • tsfL10n.states.isHome.
          • tsfL10n.states.hasInput.
          • tsfL10n.states.counterType.
          • tsfL10n.states.useTagline.
          • tsfL10n.states.taglineLocked.
          • tsfL10n.states.useTermPrefix.
          • tsfL10n.states.isSettingsPage.
          • tsfL10n.states.isPostEdit.
          • tsfL10n.states.isTermEdit.
          • tsfL10n.states.postType.
          • tsfL10n.states.isPrivate.
          • tsfL10n.states.isPasswordProtected.
          • tsfL10n.states.homeLocks.
          • tsfL10n.states.stripTitleTags.
          • tsfL10n.states.isGutenbergPage.
          • tsfL10n.i18n.saveAlert, use tsfAysL10n.i18n.saveAlert instead.
          • tsfL10n.i18n.confirmReset, use tsfSettingsL10n.i18n.confirmReset instead.
          • tsfL10n.i18n.privateTitle.
          • tsfL10n.i18n.protectedTitle.
          • tsfL10n.i18n.pixelsUsed, use tsfCL10n.i18n.pixelsUsed instead.
          • tsfL10n.i18n.inputGuidelines, use tsfCL10n.i18n.guidelines instead.
          • tsfL10n.params, all all indexes therein.
  • Other:
    • Updated composer.json, now includes developer scripts.
    • Updated phpcs.xml, now uses the newer, non-WordPress.com VIP-exclusive standards.
    • Cleaned up code, removed redundant calls, finished my homework, took a hike, fed the birds.
Filed Under: The SEO Framework Changelog

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

Transport from Yoast SEO in 2025 › The SEO Framework