Repository navigation
Releases
2.2.0
Post Chapters becomes Paginova, at the request of the WordPress.org plugin review team ("Post Chapters" is too generic a name for the directory). Content and settings need no change; no database upgrade.
Changed
- New name, slug and text domain
paginova: main filepaginova.php, release archivebuild/paginova.zip, settings pagesoptions-general.php?page=paginovaandpaginova-advanced(option groups renamed the same way), object cache grouppaginova, style handlespaginovaandpaginova-admin, stylesheetscss/paginova*.css,admin/css/paginova-admin*.cssandinc/admin/tinymce/css/paginova*.css, translation fileslanguages/paginova-*, TinyMCE pluginpaginova. Thepchapcode prefix, the hooks and the data (options,_mpp_data,[nextpage],multipage/subpage,.mpp-*classes) are unchanged - WP-CLI:
wp paginova;wp post-chaptersandwp multipagestill work - A theme stylesheet
css/paginova[-rtl][.min].csscomes first; thecss/post-chapters*.cssnames of Post Chapters andcss/multipage*.cssnames of Multipage still work, in this order - The bundled translations (the code that loads
/languagesand prefers it to the installed language packs) moved toinc/pchap-l10n.php, loaded only when present.bin/build-zip.sh --wporgleaves it out, with every file oflanguages/but the.pottemplate: on WordPress.org, translations come from translate.wordpress.org. The GitHub build is unchanged - The filtered HTML of the chapter title (
pchap_page_title_template), the table of contents (pchap_toc) and the navigation (pchap_link_pages) is escaped withwp_kses_post(). The post content is never escaped, as in core
Added
- Paginova stays idle, with an admin notice, while Post Chapters 2.x is active (both declare the same functions and classes; the check runs before Paginova declares anything). Deactivate Post Chapters, then delete its folder rather than using Delete on the Plugins screen, which would erase the shared settings and index
Removed
inc/admin/tinymce/languages.php, not loaded since 1.5 (the TinyMCE strings go through thewp_mce_translationfilter), with itsmultipage_tinymce_plugin_translation()function
2.1.0
SEO compatibility checked on the real plugins, instant chapter turns, a Chapters panel in the editor, a clean uninstall, and posts built with a page builder left alone. Content and settings need no change; no database upgrade.
Added
-
All in One SEO and The SEO Framework integrations, next to Yoast SEO, Rank Math and SEOPress (
classes/seo/, onePChap_SEO_Integrationper plugin, registered only when the plugin is active; thepchap_seo_integrationsfilter adds more) -
bin/check-seo.sh: checks the chapters against the five SEO plugins, installed from wordpress.org on the wp-env site (title, canonical, Open Graph, robots, rel links) -
Instant chapter turns (WordPress 6.8+): on a chapter, a speculation rule prefetches the navigation and table of contents links when the reader hovers or focuses them (
moderate; WordPress only prefetched on the press of the click). WordPress prints speculation rules for visitors who are not logged in, on sites with pretty permalinks. New filterspchap_speculation_ruleandpchap_speculation_mode(prerender). Checked in Chrome: hovering the "Continue" link requests the next chapter without a click -
Tests for custom post types (flat, hierarchical, plain permalinks), and a CI job that runs the suite with a Redis object cache
-
Tests that catch the mistakes of a release: the version of the plugin header,
PCHAP_VERSION,block.json,index.asset.php,readme.txt, the README badge and the changelogs must agree; every stylesheet and script the plugin loads must exist (LTR, RTL, minified or not); the block metadata copied inblocks/subpage/index.jsmust matchblock.json.tests/test-plugin-loaded.phpno longer holds the version, so a release has one place less to change -
Chapters panel in the document sidebar of the block editor: the chapters of the post, in order (click one to select it and scroll to it), and a Turn N headings into chapters button with a heading level (H2, H3 or H4). Each top-level heading becomes a Chapter block where it stands, with a single undo step and a notice. It runs in the same script as the block (
blocks/subpage/index.js), which now depends onwp-plugins,wp-data,wp-components,wp-editorandwp-dom-ready. Checked in the real editor, in English and French -
Block transforms: a heading or a core page break becomes a Chapter block, and a Chapter block becomes a heading, from the block switcher. The saved markup of the block is unchanged
-
Nine strings of the editor interface, translated in French, German, Spanish and Italian; a fourth screenshot of the panel for the WordPress.org listing
Fixed
-
Page builders: a post built with the Elementor editor showed the whole post on every chapter, and a table of contents next to it, because Elementor replaces the content by its own layout and ignores the pages (checked with Elementor 4.4.0). The plugin now leaves posts rendered by Elementor, Divi (
_et_pb_use_builder), Beaver Builder (_fl_builder_enabled) and Bricks (_bricks_editor_mode) alone; only Elementor is tested. New filterpchap_is_builder_post. Posts written in the block or classic editor and displayed with Elementor's Canvas and Full Width templates work as before (checked too) -
Uninstall (deleting the plugin from the Plugins screen) left traces. With a persistent object cache (Redis, Memcached), the
_mpp_dataindex stayed readable from the cache of each post, and the parsed chapters stayed cached; both are now cleared. The events of the background rebuild are removed whatever their arguments.uninstall.phpnow usesdelete_post_meta_by_key(),wp_unschedule_hook()andwp_cache_flush_group(). A test lets the plugin live (activation, settings, posts, a rebuild, its lock), uninstalls it, and checks that no table holds anything of it -
SEO plugins, found by testing the real plugins (Yoast SEO 28.8, Rank Math 1.0.281, SEOPress 10.4, All in One SEO 5.0.5, The SEO Framework 5.2.0):
- All in One SEO set every chapter after the first to
noindex, nofollow(its default "noindex paginated pages"). Chapters now get the robots of the post - SEOPress gave every chapter the post URL as canonical, which tells search engines each chapter duplicates the first page. The canonical is now the chapter URL
- Yoast SEO kept the post URL in
og:urland in the schema WebPageurlof every chapter; both are now the chapter URL - Rank Math and SEOPress, whose default title templates have no page variable, gave every chapter the same
<title>and the same social titles; All in One SEO and The SEO Framework ended the title with "Page N". The chapter title now appears in the<title>and the Open Graph and Twitter titles of any SEO plugin - All in One SEO and The SEO Framework printed their own rel prev/next next to ours
- All in One SEO set every chapter after the first to
-
The admin stylesheet loads again: 2.0.0 enqueued
admin/css/multipage-admin[.min].css, a file renamed topost-chapters-admin[.min].css, so every admin page requested a missing file (404) and the settings screens lost their styles. The style handle is nowpost-chapters-admin
Changed
- The stylesheet of the chapters (about 1 KB minified) is printed inline by WordPress instead of being requested as a file: one request less on each chapter. A stylesheet of the theme (
css/post-chapters.css) gets the same treatment, within the limit of the corestyles_inline_size_limitfilter - Code structure:
PChapno longer holds the SEO and comments code. It moves to two components created with the instance,pchap()->seo(PChap_SEO: document title, rel prev/next links, Yoast SEO / Rank Math / SEOPress) andpchap()->comments(PChap_Comments). The formerPChapmethods (e.g.pchap_rel_links(),filter_wpseo_replacements(),hide_comments()) still work, as deprecated methods that delegate - The settings screens are built from one description per option,
pchap_get_settings_sections()andpchap_get_settings_fields(), rendered bypchap_admin_render_option_field(). The twelvepchap_admin_*_callback_*()field callbacks are deprecated wrappers. The HTML of the screens is unchanged
Development
-
WordPress.org listing assets in
.wordpress-org/: new screenshots (settings, block editor, a post split into chapters, on WordPress 7.1 with Twenty Twenty-Five), banners (772×250, 1544×500) and icons (128×128, 256×256, SVG) -
wp-env runs a single environment (
.wp-env.json,"testsEnvironment": false,WP_DEBUGandSCRIPT_DEBUGoff):composer test:wp-envand CI run PHPUnit in itsclicontainer instead of the deprecatedtests-clicontainer, and the development site at http://localhost:8888 is the same environment (the test run reinstalls its database) -
The documentation uses
npx @wordpress/env@12instead ofnpx wp-env(the npm packagewp-envis unrelated) -
bin/build-zip.sh --wporgbuildsbuild/post-chapters-wporg.zipfor WordPress.org: the same files, without theUpdate URIheader. It passes Plugin Check (mode "new") with no error or warning
Changed
- The internal helpers are prefixed
pchap_instead of_pchap_, which Plugin Check does not accept (e.g._pchap_link_page_url()is nowpchap_link_page_url(),_pchap_get_valid_option()is nowpchap_get_valid_option()). They are private (@access private) and get no deprecated alias - The subpage index rebuild queries are written out as literal prepared statements;
_pchap_rebuild_candidates_where()is replaced bypchap_rebuild_candidates_args() - readme.txt: caption of the third screenshot
2.0.0
Multipage becomes Post Chapters, a plugin of its own: new name, slug post-chapters, text domain post-chapters and code prefix pchap. The WordPress.org listing of Multipage (sgr-nextpage-titles) stays with its original author. Content and settings need no change: everything stored in the database or in the posts keeps its name.
Migrating from Multipage
- Install and activate Post Chapters. While Multipage is active, Post Chapters stays idle and shows an admin notice
- Deactivate Multipage. On the next admin page load, the 2.0 upgrade routine (database version 2000) moves a pending subpage index rebuild from the
mpp_rebuild_postmeta_batchWP-Cron event topchap_rebuild_postmeta_batch - Delete the
sgr-nextpage-titlesfolder over FTP or SSH. Do not use Delete on the Plugins screen: the uninstall routine of Multipage 1.5.15 to 1.7.1 erases the options and the_mpp_datameta that Post Chapters reuses (the uninstall routine of Post Chapters erases them too, and now also clears the old WP-Cron event)
Kept for compatibility
- The
[nextpage]shortcode and themultipage/subpageblock name (stored in the posts; the blocksave()output is unchanged) - The option names (
mpp-*,_mpp-*,_mpp_db_version, the 1.3multipageoption) and the_mpp_datapost meta - The HTML classes (
.mpp-toc-container,.mpp-toc,.mpp-post-navigation...) and thedata-mpp-titleattribute of the classic editor placeholders - Theme stylesheets named
css/multipage[-rtl][.min].css: still loaded, aftercss/post-chapters[-rtl][.min].cssin the same theme (child theme first, then parent theme) - The
wp multipageWP-CLI command, now an alias ofwp post-chapters
Changed
- Plugin name Post Chapters, main file
post-chapters.php, plugin folderpost-chapters, text domainpost-chapters,Update URI: https://github.com/PaulArgoud/post-chapters - Interface wording: subpage becomes chapter everywhere (block title "Chapter", editor buttons, settings, admin notices, WP-CLI output). The fallback title of an untitled page is "Chapter N" (was "Page N"), and the "page #" row labels of the table of contents read "Chapter #" (the stored option value is still
page). Translations updated in French, German, Italian and Spanish - Settings pages:
options-general.php?page=post-chaptersand?page=post-chapters-advanced(werempp-settingsandmpp-advanced-settings); the settings groups were renamed the same way - Object cache group
post-chapters(wasmultipage), frontend style handlepost-chapters/{theme}-post-chapters, TinyMCE pluginpostchapters, TinyMCE commandPCHAP_Subpage - Files renamed:
class-pchap.php,classes/class-pchap-*.php,inc/pchap-*.php,css/post-chapters*.css,admin/css/post-chapters-admin*.css,inc/admin/tinymce/css/post-chapters*.css,languages/post-chapters-*. Release archivepost-chapters.zip
Removed (breaking for custom code)
- Every
mpp_*/_mpp_*function, theMultipage,Multipage_Parser,MPP_Admin,Multipage_Plugin_ShortcodesandMPP_CLI_Commandclasses, themultipage()accessor and theMPP_*constants. They are renamed (pchap_*,_pchap_*,PChap,PChap_Parser,PChap_Admin,PChap_Shortcodes,PChap_CLI_Command,pchap(),PCHAP_*) without aliases: Multipage defines the old names, and both plugins can be installed side by side during the migration. The public$mpp_dataproperty of the main instance is now$pchap_data;mpp_load_multipage_textdomain()is nowpchap_load_textdomain()
Deprecated
- The
mpp_*hooks. Every hook is now fired aspchap_*(e.g.pchap_toc,pchap_the_content,pchap_get_toc_position,pchap_on_update); callbacks attached to the oldmpp_*name still run, after thepchap_*ones and with the same arguments, throughapply_filters_deprecated()/do_action_deprecated()(a notice is logged whenWP_DEBUGis on). New helperspchap_apply_filters()andpchap_do_action()
Added
- Coexistence guard:
pchap_is_multipage_active()detects an active Multipage (an existingsgr-nextpage-titles.phpin any active plugin folder, or theMultipageclass); Post Chapters then loads nothing but an admin notice telling how to switch safely - 2.0 upgrade routine
pchap_update_to_2_0()(database version 2000) - Tests for the deprecated hooks, the guard and the 1.7 to 2.0 update (401 tests)
1.7.1
Translation and object cache fixes. No database change.
Fixed
- Translations ignored when a language pack is installed (user-reported on a production site): the language packs that translate.wordpress.org delivers for the
sgr-nextpage-titlesslug (de_DE, en_GB, es_ES, fr_FR, it_IT, nb_NO, nl_NL, tr_TR) translate the original plugin, without the strings of this version, and WordPress loaded them instead of the bundled files (e.g. "Continue:" and "Subpage navigation" stayed in English in French). For the bundled locales (fr_FR, de_DE, es_ES, it_IT), the bundled.l10n.php/.mofiles and the block editor JSON files now replace the files ofwp-content/languages/plugins; other locales still use their language pack. New filtermpp_prefer_bundled_translations(defaulttrue) to give the language packs precedence back - Persistent object cache (Redis, Memcached): the parse cache key now depends on the parser code, not only on
MPP_VERSION, so a page cut by another build of the same version (e.g. a pre-release 1.7.0) is no longer served for up to a day after an update
1.7.0
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 withreadme.txt - Minimum WordPress version raised from 5.0 to 6.3 (
Requires at least: 6.3), required by theblock.jsonregistration and blockapiVersion3. PHP 7.4 is still the minimum - Subpage block rewritten for the current block editor: registered from
blocks/subpage/block.json(apiVersion3, so it works in the iframed editor), plain ES5 script using thewp.*globals with no build step, translatable placeholder. Itssave()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-seotext 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 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,
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)
- untitled
- The frontend no longer depends on the
_mpp_datapost 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(), directwp_trim_excerpt()calls), descriptions built inwp_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_contentagain to the rendered output, or when a shortcode (box, tabs, accordion…) filters its inner content withthe_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/commentsand related blocks), not only the classiccomments_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, soC++ <T>ora < b & cdisplay 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=nextlinks 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 thelanguages/path, so the bundled.mofiles 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=Nwhenever permalinks are plain (also when thepermalink_structureoption 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
remsizes 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].cssandadmin/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 aReferenceErroror 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_dataindex 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_postindexes and deletes stale entries - The 1.3 → 1.4 migration no longer runs a full synchronous rebuild on
admin_init, which could exceedmax_execution_timeon 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 withregister_setting( 'mpp-settings', … )(or'mpp-advanced-settings'for the Advanced screen) - Settings API: both settings sc...
1.5.15
Fixed
- XSS: subpage title now escaped with
esc_html()inmpp_the_content()output - XSS: translated strings in quicktag JS now escaped with
esc_js()instead of raw_e() - PHP 8.2: re-declared
$adminproperty onMultipageclass to prevent dynamic property deprecation warning mpp_pre_handle_404()returnednullinstead oftrue, failing to short-circuit the 404 handlerversions()calledget_option( null, 'multipage' )with swapped arguments — corrected toget_option( 'multipage' )$comments_linkpotentially undefined before use inmpp_the_content()— now initialized- Admin CSS enqueued without version string, preventing cache busting — now uses
MPP_VERSION save_posthook ran on revisions and autosaves — now filtered withwp_is_post_revision()/wp_is_post_autosave()save_post()could crash ifget_post()returnednull— added guardmpp_the_title()loaded thewordpress-seotext domain even when Yoast SEO was not active — now guarded withdefined( 'WPSEO_VERSION' )- Double slash in
hide_comments()path (MPP_PLUGIN_DIR . '/index.php') - Navigation CSS class not sanitized — now uses
sanitize_html_class() - Missing text domain on
__( 'Next page' ),__( 'Previous page' ), and__( 'Comments' )inmpp-template.php _mpp_link_page_url()could crash ifget_post()returnednull— added guard- Duplicate
$handleassignment inMPP_Admin::enqueue_scripts() - Plugin URI used
http://instead ofhttps:// MPP_VERSIONconstant was'1.5.14'while plugin header declared1.5.15- Comments link in TOC was missing
<li>wrapper, producing invalid HTML multipage_return_array()returned an empty array instead offalsewhen no valid shortcodes were found- "Build Multipage postmetas" only found shortcode posts — now also detects Gutenberg block posts
- Plugin action links detection used fragile
/trunkpath hack — now usesplugin_basename() - Security: missing
exitafterwp_safe_redirect()in both settings save functions — PHP continued executing after redirect esc_html()used instead ofesc_attr()on radio inputvalueattributes in settings page- Operator precedence ambiguity in
versions()—(int)cast applied to condition instead of ternary result - Incorrect
@since 1.6docblock tags corrected to@since 1.5(version 1.6 does not exist) - Copy-paste docblock errors:
mpp_hide_intro_title(),mpp_toc_only_on_the_first_page(), andmpp_hide_toc_header()incorrectly described as "standard WordPress pagination" - SEO:
rel=nextlink on the first page pointed to itself instead of page 2 — normalized page index inmpp_rel_links() - Comments TOC link for
first-page/last-pagewas missing#commentsURL fragment —'comments'was passed as$relinstead of$pparameter to_mpp_link_page() - Swapped file headers:
admin-settings.phpsaid "Admin Advanced" andadmin-advanced-settings.phpsaid "Admin Settings" mpp_get_comments_on_page()docblock declared@return intbut the function returns astringmpp_the_contentfilter docblock had wrong parameter names ($toc_labelsinstead of$toc) and types (arrayinstead ofstring)mpp_link_pages_argsfilter docblock had@since 3.0.0copied from WordPress core — corrected to@since 1.4- Typo « fitler » corrected to « filter » in
setup_actions()comment mpp_toc()default position was'top_right'(underscore) instead of'top-right'(hyphen), producing an invalid CSS classmpp_toc_pages_rowfilter docblock documented 2 parameters but 3 are passed ($row,$title,$i)- Typo « positon » corrected to « position » in
mpp_get_toc_position()filter docblock @param bool|stringcorrected to@param intfor priority option defaults inmpp-options.php- Missing
$multipageparameter documented inmpp_link_pages()andmpp_toc()docblocks - Redundant
$post &&check removed inmpp_pre_handle_404()— already validated above - JS TypeError: cancelling the quicktag subpage prompt caused
null.lengthcrash — now checks fornullexplicitly - CSS handle:
enqueue_styles()used full filesystem path (get_stylesheet_directory()) as handle identifier — replaced withget_stylesheet()/get_template()slug mpp_get_admin_tabs()docblock declared@return stringbut the function returns anarraympp_get_db_version()andmpp_get_db_version_raw()docblocks declared@return stringbut the functions return anint- Self-referencing
@uses enqueue_styles()tag removed fromenqueue_styles()docblock _mpp_link_page_url()docblock@param string $p Paragraph idcorrected to « Fragment identifier »in_array()missing strict mode (true) inmpp_modify_admin_menu_highlight()mpp_toc()docblock for$pagelinkincorrectly described as "Link text for the previous page link" — corrected to "Page number template;%is replaced by the page number"mpp_link_pages()docblock declared wrong defaults:$before=<p> Pages:/$after=</p>/$echo=1|true— corrected to<div>/</div>/0|falsempp_toc()docblock declared$echodefault as1|true— corrected to0|falsempp_toc()docblock for$commentsdeclared@type int|boolbut actually receives an opening<a>tag or0— corrected tostring|int- Unused
global $numpagesdeclaration removed frommpp_toc() - Dead JavaScript variable
shortcoderemoved from quicktagprompt_subtitle()function - Security: missing
current_user_can( 'manage_options' )check inmpp_admin_settings_save()andmpp_admin_advanced_settings_save()— added for defense-in-depth mpp_the_contentfilter docblock started with/*instead of/**, invisible to PHPDochide_standard_pagination()docblock missing@paramand@returntagsmpp_pre_handle_404()docblock missing@returntagmpp_add_post_multipage_meta()docblock missing@paramfor$update_option
Added
- Docblocks for
frontend_init()andmpp_post()methods inMultipageclass - Docblock for
_mpp_link_page()helper function inmpp-template.php uninstall.phpto clean up all plugin options and_mpp_datapost meta on deletionRequires at least: 5.0andRequires PHP: 7.4headers in plugin file- History section in
README.mddocumenting the plugin's origins and fork lineage aria-labelattributes on TOC and navigation<nav>elements for accessibility
Changed
_e()replaced withesc_html_e()in admin settings templates (17 occurrences) for translation security_mpp_link_page_url()calledget_permalink()up to 3 times — now stored in a local variable$this->versionnow reads fromMPP_VERSIONconstant instead of a hardcoded string- All
require()calls replaced withrequire_once(without parentheses) inclass-mpp.phpandclass-mpp-admin.php sanitize_html_class()applied to TOC container position classmpp_is_block_editor_active()simplified — removed unnecessary WP < 5.0 and Gutenberg plugin checks- TinyMCE disable setting description updated (no longer references WordPress 3.9)
mpp_get_continue_or_prev_next()called once and stored instead of twicempp_get_toc_position()called once and stored instead of three times inmpp_the_content()mpp_get_comments_on_page()called once and stored instead of twice inmpp_post()constant('MPP_PLUGIN_DIR')/constant('MPP_PLUGIN_URL')replaced with direct constant access insetup_globals()strip_tags()string allowlist updated to array form (recommended since PHP 7.4) inMultipage_Parser- Redundant
trailingslashit()removed fromsetup_globals()—plugin_dir_path()/plugin_dir_url()already add a trailing slash - Redundant
is_null( $post ) || empty( $post )simplified toempty( $post )inmpp_post() json_encode()replaced withwp_json_encode()in TinyMCElanguages.php- Minor formatting: added missing space in
$this->page - 1arithmetic expression - Removed trailing semicolons (
};) after ABSPATH guard closing braces inclass-mpp.phpandmpp-update.php - Gutenberg block description replaced (was "lorem ipsum" placeholder)
- Gutenberg block script dependency changed from deprecated
wp-editortowp-block-editor - Frontend CSS header updated to current author and HTTPS Plugin URI
Removed
- Obsolete WP < 4.4 title fallback in
mpp_the_title() - Orphaned
<input type="checkbox">and commented-out toggle HTML in TOC template - Dead
mpp_admin_settings_callback_excerpt_on_all_pages()(called non-existentmpp_excerpt_all_pages()) - Dead
mpp_admin_settings_callback_prettylinks()(referenced oldenvire.itURL) - Unused
$noticesproperty fromMPP_Adminclass - Unused
$optionsproperty fromMultipageclass - Dead widget files:
class-mpp-table-of-contents-widget.phpandmpp-widgets.php(never loaded, non-functional) - Dead
mpp_uninstall()function inmpp-update.php(replaced byuninstall.php) - Orphaned
.toctogglespanCSS rule (toggle was removed from PHP) - Dead
mpp_get_default_options()function (never called, option keys used underscores instead of hyphens) - Unused
MPP__MINIMUM_WP_VERSIONconstant (minimum WP version already declared in plugin header) - Redundant
MPP__PLUGIN_DIRconstant (same value asMPP_PLUGIN_DIR, only used once — inlined)
1.5.14
Fixed
- Uninitialized
$custom_introvariable causing PHP 8.4 warnings (class-mpp.php) - Array bounds check missing for
$_mpp_page_keysaccess inmpp_pre_handle_404() - Ambiguous operator precedence in comments display condition (added explicit parentheses)
- Integer settings sanitized with
sanitize_text_field()instead ofintval()in advanced settings - Missing
esc_attr()on<option>values in admin settings forms (4 occurrences) - Options cache never invalidated after saving —
mpp_clear_options_cache()now resets the static cache - Deprecated
current_time('timestamp')replaced withtime()inmpp-update.phpandadmin-advanced-settings.php - Loose
==/!=comparisons replaced with===/!==across all PHP files array_search()withoutfalsecheck in TinyMCE button insertion (class-mpp-admin.php)- Operator precedence ambiguity in TOC active page condition (
mpp-template.php) - Preview link parameters now properly sanitized with
intval()/sanitize_text_field()(mpp-template.php) - Incorrect
@since 1.9.0docblock tags corrected to1.4
Added
LICENSEfile (GPL v3 full text)CONTRIBUTING.mdwith coding standards, project structure, and contribution guidelinesARCHITECTURE.mddescribing the plugin file structure and data flow
Changed
- Replaced
&$thiswith$thisin all callback arrays (unnecessary since PHP 5.4) - Updated Codex link to
developer.wordpress.orgin advanced settings - Extracted content parser into
Multipage_Parserclass (inc/mpp-parser.php) from the mainMultipageclass
Removed
- Dead debug code in table of contents widget
- Unused
admin-premium.phppage placeholder - Dead
is_gutenberg_page()method inclass-mpp.php(replaced bympp_is_block_editor_active())
1.5.13
Fixed
- Quotes in subpage titles (e.g.
"F48") breaking page parsing - PHP 8.2+ deprecated dynamic property warnings
- Catastrophic backtracking in regex patterns
- Page 2+ not accessible due to
redirect_canonicalinterference - Improperly quoted
relattribute in navigation links - "Advaced" typo in admin submenu registration
Added
- SEO
rel=prev/rel=nextlinks in<head>for multipage posts - Input sanitization and escaping across admin settings
- In-memory options caching to reduce database queries
README.mdwith badges, requirements, and usage documentationCHANGELOG.mdfollowing Keep a Changelog format
Changed
- Replaced
$_REQUESTwith explicit$_GET/$_POSTchecks - Updated minimum WordPress version to 5.0
- Tested with PHP 8.4
- Regenerated
.pottranslation template (73 strings) - Default branch renamed from
mastertomain
Removed
- Dead code, unused premium references, Italian comments
readme.txt(replaced byREADME.md)
Full changelog: CHANGELOG.md