Skip to content

1.7.0

Choose a tag to compare

@PaulArgoud PaulArgoud released this 07 Nov 22:17
· 18 commits to main since this release
33f2d78

Major rewrite of the Subpage block, the content parser and the frontend rendering. Existing content needs no change: blocks saved by earlier versions stay valid, and the subpage index of every post is rebuilt in the background after the update.

Compatibility

  • Tested up to WordPress 7.1.4 (iframed block editor, block themes such as Twenty Twenty-Five, classic themes such as Twenty Twenty-One) and PHP 7.4 to 8.4
  • The plugin header declares Update URI: https://github.com/PaulArgoud/sgr-nextpage-titles: WordPress.org no longer offers updates from the listing of the same slug (owned by the original author, currently 1.5.12), so a future release there can never overwrite this version. Added after the 1.7.0 tag, and included in the 1.7.0 release archive together with readme.txt
  • Minimum WordPress version raised from 5.0 to 6.3 (Requires at least: 6.3), required by the block.json registration and block apiVersion 3. PHP 7.4 is still the minimum
  • Subpage block rewritten for the current block editor: registered from blocks/subpage/block.json (apiVersion 3, so it works in the iframed editor), plain ES5 script using the wp.* globals with no build step, translatable placeholder. Its save() output is byte-identical to previous versions, so every existing post stays valid (no "This block contains unexpected or invalid content" warning, no block recovery)
  • The block is now registered on every request (init), whatever the editor in use, and still registers on third-party editor screens that do not bootstrap the server-side block definitions
  • Classic Editor plugin detection (mpp_is_block_editor_active()) mirrors Classic Editor 1.7 settings, including the multisite network defaults and the "allow users to switch editors" option
  • SEO plugins: dedicated integrations for Yoast SEO, Rank Math and SEOPress replace the old Yoast string replacement (which loaded the wordpress-seo text domain to replace "Page X of Y"). On subpages 2 and up, the subpage title fills the %%page%% (Yoast, SEOPress) or %page% (Rank Math) title variable. Each integration is only registered when that plugin is active

Fixed

  • Broken page layout (user-reported on a production site): a [nextpage] shortcode placed inside <p>, <h2>, <h3 style="…">, <div> or in the middle of a paragraph left unclosed elements, so the table of contents and the navigation ended up nested inside a heading. Every subpage is now a balanced fragment: the blocks and elements open at the cut are closed at the end of the page and reopened on the next one
  • Elements left empty by the cut (e.g. <h3 style="…">[nextpage]</h3>, also with only &nbsp; or <br> next to the shortcode, or a paragraph/heading block holding only the shortcode) are removed instead of rendering as empty headings or paragraphs; whitespace next to the cut is trimmed (except in <pre>)
  • Parser rewrite: titles and pages could get out of step (wrong title on a page, missing or extra subpages). Subpages are now derived from a single parse of the live post content, in document order:
    • untitled [nextpage] shortcodes and attribute-less Subpage blocks (saved without a title) now create their own subpage (fallback title "Page N") instead of being ignored or titled "false"
    • posts mixing Subpage blocks, [nextpage] shortcodes and core page breaks (<!--nextpage--> or the Page Break block) are split in document order; previously the shortcodes were ignored as soon as one block was found, and core breaks shifted every title
    • intro detection: an intro made only of an image, an embed or a dynamic block is recognized; whitespace, &nbsp; and empty paragraphs before the first subpage no longer create an empty "Intro" page, and leading content that is not an intro (scripts, styles, separators) is kept on the first page instead of being lost
    • the self-closing block form and Subpage blocks nested in groups or columns are supported
    • [nextpage] inside an HTML comment, a tag attribute, a <script>/<style>/<textarea> element or a block delimiter is no longer treated as a page break, and the escaped [[nextpage]] is displayed literally
    • duplicate titles always get unique slugs (the 100-duplicate limit is gone)
  • The frontend no longer depends on the _mpp_data post meta: posts imported, or edited while the plugin was inactive, no longer show an outdated table of contents
  • Table of contents injected where it does not belong: the title, TOC and navigation are now only added to the multipage post rendered by the main query — no longer to related posts, Query Loop blocks, widgets, excerpts (get_the_excerpt(), the_excerpt(), direct wp_trim_excerpt() calls), descriptions built in wp_head, or password-protected posts before the password is entered
  • The TOC is no longer added twice when a theme or page builder applies the_content again to the rendered output, or when a shortcode (box, tabs, accordion…) filters its inner content with the_content
  • 1-based page handling: /post/ and /post/1/ are the same first page everywhere. "Display comments on: first page" now shows the comments on /post/1/, and the current TOC item, rel links, navigation and document title use the same page number
  • Block themes: the "first page" / "last page" comments setting now also hides the comment blocks (core/comments and related blocks), not only the classic comments_template(); the standard page links appended by the Post Content block are hidden when "Disable the standard pagination" is checked, and still shown when it is not
  • "Only on the first page" (table of contents) is now honored when the TOC is displayed below the content
  • "Hide intro title" now also applies on /post/1/, and only when the first page is the automatic intro (a first subpage with its own title keeps it)
  • Title escaping: one policy for every output. Block titles (HTML-encoded by the editor) keep their inline formatting (strong, em, code, mark, sub, sup…) in the heading, the TOC and the navigation instead of showing literal tags, and any other markup is stripped. Classic shortcode titles are treated as text, so C++ <T> or a < b & c display literally instead of being truncated or emptied. The document title and the SEO plugins receive plain text, without tags or entities
  • SEO: with Yoast SEO, subpages 2 and up declared the first page as their canonical URL (which tells search engines to drop them); they now use their own URL, and a canonical set in Yoast is kept. Yoast SEO and Rank Math no longer print their own rel=prev / rel=next links on multipage posts (computed from the raw content, they could be wrong or duplicate the plugin's)
  • Translations were never loaded: load_plugin_textdomain() was called without the languages/ path, so the bundled .mo files were ignored and the plugin always displayed English unless a language pack was installed
  • TOC, navigation and rel link URLs always point to the multipage post (they were built from the global post), and use ?page=N whenever permalinks are plain (also when the permalink_structure option is missing)
  • The navigation no longer prints the <!-- .nav-links --> / <!-- .mpp-post-navigation --> HTML comments, and the internal empty-page placeholder is never printed
  • CSS: the TOC and the navigation use rem sizes and reset the typography (font size, weight and style, line height, text transform, letter spacing), so they no longer inherit the styles of a surrounding heading
  • RTL: RTL sites requested css/multipage-rtl[.min].css and admin/css/multipage-admin-rtl[.min].css, which did not exist (404); both stylesheets are now shipped. The "align left" / "align right" TOC positions keep their physical side
  • TinyMCE and Quicktags buttons: the typed title is now escaped (&, ", <, >, [, ]), so quotes no longer break the shortcode and markup cannot be injected; an empty or cancelled title inserts nothing (it used to throw a ReferenceError or re-insert the previous shortcode)
  • Classic block: the [nextpage] placeholder was a blank image in the Classic block of the block editor; the plugin TinyMCE stylesheet is now loaded there too
  • Backslashes in subpage titles are preserved when the _mpp_data index is saved
  • The subpage index rebuild ("Build Multipage postmetas") loaded every matching post in a single request, skipped drafts and untitled shortcodes, and never removed stale entries; it now runs in batches, indexes every post that save_post indexes and deletes stale entries
  • The 1.3 → 1.4 migration no longer runs a full synchronous rebuild on admin_init, which could exceed max_execution_time on large sites and retry on every admin request
  • The updater treated 1.4–1.6 sites as fresh installs (so no upgrade routine could run); it now also reads the stored _mpp_db_version

Changed

  • Behaviour change: /post/1/ is now redirected (301) to the post permalink by WordPress core instead of being served as a duplicate of the first page
  • Behaviour change: an out-of-range subpage (e.g. /post/99/) is redirected to the permalink, as WordPress does for posts split with core page breaks
  • Behaviour change: in contexts where the content is not split (feeds, REST API, excerpts, other do_shortcode() callers), [nextpage] renders nothing; it used to output a <!--nextpage--> comment
  • Behaviour change: with the bundled translations now loaded, the intro title "Intro" is displayed as "Introduction" in French ("Einleitung" in German, "Introducción" in Spanish, "Introduzione" in Italian)
  • Behaviour change: settings fields added to the Multipage settings screens by third-party code with add_settings_field() are no longer saved automatically: register them with register_setting( 'mpp-settings', … ) (or 'mpp-advanced-settings' for the Advanced screen)
  • Settings API: both settings screens now save through options.php. Every value is validated against its allowed values (an invalid value keeps the previous one and shows an error naming the field), unchecked checkboxes are stored as 0, and custom priorities set in the database are kept (shown as "Custom (N)"). Fields have correct labels (label_for, fieldset legends), and priority labels are translatable
  • "Build Multipage postmetas" is replaced by a Rebuild subpage index button on the Advanced screen: posts are processed in batches for about 15 seconds, the remaining posts continue in the background, and the screen shows the last rebuild date and the background progress
  • After updating to 1.7.0, and on every activation, the subpage index is rebuilt in the background through WP-Cron (batched, resumable, protected by a lock)
  • The X-Pingback header is no longer sent by the plugin (WordPress core sends it since 6.1)
  • uninstall.php now cleans every site of a multisite network, and also removes the new internal options and the scheduled background rebuild
  • Bundled translations: complete and reviewed fr_FR, de_DE, es_ES and it_IT catalogs (following the WordPress glossary of each locale), JavaScript translations for the block (.json) and .l10n.php files (WordPress 6.5+ performant translations). Language packs from translate.wordpress.org always take precedence over the bundled files
  • Translatable strings are escaped (esc_html__(), esc_attr__()), every string with a placeholder has a translators comment, and counts use _n()
  • The current TOC item has aria-current="page"
  • The Subpage block title field: Enter at the end of the title adds a paragraph after the block, and long titles wrap

Added

  • WP-CLI: wp multipage rebuild [--batch-size=<n>] [--dry-run] rebuilds the subpage index (and completes any pending background rebuild); wp multipage status [--format=<format>] reports indexed, missing and stale entries, the background rebuild state and the database version
  • Rebuild subpage index button (Settings > Multipage > Advanced), see Changed
  • RTL stylesheets css/multipage-rtl[.min].css (frontend) and admin/css/multipage-admin-rtl[.min].css (admin)
  • Translated fallback title "Page N" for untitled subpages

Developer

  • Single source of truth: Multipage_Parser::parse( $content ) returns is_multipage, has_intro and the ordered pages (slug, title, content, kind: intro, block, shortcode or core). Multipage_Parser::multipage_return_array() is now derived from it (same signature and return shape). New: Multipage_Parser::build_paginated_content( $pages, $use_block = null ), Multipage_Parser::is_meaningful_content(), constants Multipage_Parser::BLOCK_PAGE_BREAK and Multipage_Parser::EMPTY_PAGE_PLACEHOLDER
  • Request setup: Multipage::setup() (idempotent) parses the main query's post from pre_handle_404, redirect_canonical and wp, so the plugin keeps working when another plugin short-circuits one of them. mpp_post() rebuilds post_content so that WordPress' own splitting returns exactly the parsed pages, and filters content_pagination. The parse result is cached in the object cache group multipage (key: plugin version + content hash). New public members: Multipage::$post_id, $current_page (1-based), $pages, is_multipage_view(), get_current_page(), get_title_format(), comments_allowed_on_current_page(); $page stays the raw page query var
  • New template helpers: mpp_get_subpage_title_html( $title, $format = 'html' ), mpp_get_subpage_title_text( $title, $format = 'html' ), mpp_get_subpage_title_allowed_html(); _mpp_link_page_url() takes an optional third $post argument
  • New options API: mpp_get_option_definitions(), mpp_get_option_definition(), mpp_get_option_choice_labels(), mpp_validate_option_value(), mpp_sanitize_option(). All getters keep their names, signatures and filters, and now return validated values. The options cache is cleared automatically when a Multipage option is added, updated or deleted
  • New rebuild API (inc/mpp-update.php): mpp_run_postmeta_rebuild(), mpp_process_postmeta_batch(), mpp_schedule_postmeta_rebuild(), mpp_get_postmeta_index_stats(), mpp_is_rebuild_locked(). WP-Cron hook mpp_rebuild_postmeta_batch; internal options _mpp-rebuild-state, _mpp-rebuild-last and _mpp-rebuild-lock (not autoloaded); the database version is now 1700
  • New filters: mpp_untitled_subpage_title, mpp_comment_block_names, mpp_subpage_title_allowed_html, mpp_parser_is_meaningful_content, mpp_get_option_definitions, mpp_get_option_choice_labels, mpp_rebuild_postmeta_batch_size, mpp_rebuild_cron_time_limit, mpp_rebuild_admin_time_limit
  • New files: blocks/subpage/ (block.json, index.js, index.asset.php, editor.css), inc/mpp-blocks.php (mpp_register_blocks(), bundled JS translations fallback), inc/mpp-cli.php (MPP_CLI_Command)
  • _mpp_data is kept as an index for third-party code (written on save_post and by the rebuild); untitled subpages are stored with title '' and slug page-N. MPP_PATTERN and MPP_GUTENBERG_PATTERN are kept for backward compatibility only
  • Removed: admin/js/mpp-editor-blocks.js, admin/css/mpp-editor-blocks[.min].css (replaced by blocks/subpage/) and the global prompt_subtitle() JavaScript function of the Quicktags button
  • Tooling: composer.json with dev-only dependencies (WPCS 3, PHPCompatibilityWP, PHPUnit 9.6, Yoast PHPUnit Polyfills) and the lint, phpcs, phpcbf, test and test:wp-env scripts; phpcs.xml.dist (WordPress + PHPCompatibilityWP, PHP 7.4+, WordPress 6.3+), with 0 errors on the codebase
  • Tests: PHPUnit integration suite (about 380 tests) running in wp-env (.wp-env.json: WordPress 7.1.4, PHP 8.4): parser corpus, HTML balance of every split, full requests of every subpage, rendering, titles, SEO, comments, options, rebuild, upgrade, blocks, uninstall
  • CI: GitHub Actions workflow (PHP 7.4–8.4 lint, PHPCS, i18n check, PHPUnit on WordPress 6.3 / PHP 7.4, 7.1.4 / PHP 7.4 and 7.1.4 / PHP 8.4, release zip)
  • Release and i18n scripts: bin/build-zip.sh (zip of the files known to git, filtered by .distignore), bin/php-lint.php, bin/check-i18n.sh (POT, .po, .mo, .json and .l10n.php files in sync with the code); .distignore and .gitattributes keep development files out of the release

Deprecated

  • MPP_Admin::mpp_enqueue_block_editor_assets() — no longer hooked, the block is registered by mpp_register_blocks()
  • mpp_admin_settings_save() and mpp_admin_advanced_settings_save() — no-ops, the settings are saved by the Settings API
  • mpp_admin_advanced_callback_build_mpp_postmeta_data() — replaced by mpp_admin_rebuild_postmeta_section()
  • Multipage::mpp_the_title() — no longer hooked, returns the title unchanged (see the SEO integrations)
  • Multipage::multipage_return_array() (deprecated since 1.5.14) now triggers a _deprecated_function() notice; use Multipage_Parser::multipage_return_array()

Install: download sgr-nextpage-titles.zip below (not the "Source code" archives) and upload it via Plugins > Add New > Upload Plugin.