Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .release-it.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
"after:bump": [
"sed -i.bak -e \"s/Stable tag: .*/Stable tag: ${version}/\" readme.txt",
"sed -i.bak -e \"s/Version: .*/Version: ${version}/\" bootstrap-shortcodes.php",
"sed -i.bak -e \"s/define( 'BS_SHORTCODES_VERSION', '[^']*' )/define( 'BS_SHORTCODES_VERSION', '${version}' )/\" bootstrap-shortcodes.php",
"rm -f readme.txt.bak bootstrap-shortcodes.php.bak",
"git add readme.txt bootstrap-shortcodes.php"
]
}
}
}
14 changes: 7 additions & 7 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Roadmap: a safe landing for Bootstrap Shortcodes

Status: living document. Last updated 2026-10-31.
Status: living document. Last updated 2026-10-31 (all three phases implemented locally; PRs pending push approval).

This plugin is in **maintenance mode**. It is kept safe and working for the sites
that already depend on it; it is not being grown into a page builder or a Bootstrap
Expand Down Expand Up @@ -89,12 +89,12 @@ string search, never unserialize-and-rewrite).

Branch `feat/migration`. Acceptance criteria:

- [ ] Component-by-component matrix in `docs/migration.md` (destination, WP version, preserved content/interaction, visual differences, retained dependency, fallback, limitations).
- [ ] Converter: shortcode text to block markup or reviewed static HTML, preserving unrelated markup and third-party shortcodes; unknown/malformed/ambiguous occurrences are left in place and listed for manual review.
- [ ] Preview, migrate selected, migrate in batches, review outstanding; explicit confirmation, nonces, capability checks.
- [ ] Originals stored in post meta (not only revisions) with a tested rollback; stale-content protection via `post_modified_gmt` check; bounded, resumable, safe to retry.
- [ ] Legacy assets handoff: a separate, minimal companion plugin ("Bootstrap 3 Legacy Assets") shipped in this repo, installable from the migration page only with explicit consent; works with this plugin deactivated and deleted; avoids double-loading with theme Bootstrap; documents what retaining Bootstrap 3 means.
- [ ] Verified by deactivating this plugin in wp-env after migration: desktop/mobile layouts, keyboard, tabs, collapse, icons, tooltips.
- [x] Component-by-component matrix in `docs/migration.md` (destination, WP version, preserved content/interaction, visual differences, retained dependency, fallback, limitations).
- [x] Converter: shortcode text to block markup or reviewed static HTML, preserving unrelated markup and third-party shortcodes; unknown/malformed/ambiguous occurrences are left in place and listed for manual review.
- [x] Preview, migrate selected, migrate in batches, review outstanding; explicit confirmation, nonces, capability checks.
- [x] Originals stored in post meta (not only revisions) with a tested rollback; stale-content protection via `post_modified_gmt` check; bounded, resumable, safe to retry.
- [x] Legacy assets handoff: a separate, minimal companion plugin ("Bootstrap 3 Legacy Assets") shipped in this repo, installable from the migration page only with explicit consent; works with this plugin deactivated and deleted; avoids double-loading with theme Bootstrap; documents what retaining Bootstrap 3 means.
- [x] Verified by deactivating this plugin in wp-env after migration: desktop/mobile layouts, keyboard, tabs, collapse, icons, tooltips.

## Decisions

Expand Down
18 changes: 14 additions & 4 deletions bootstrap-shortcodes.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@
require_once( 'inc/bs_lead.php' );
require_once( 'inc/bs_tooltip.php' );
require_once( 'inc/class-bs-scanner.php' );
require_once( 'inc/class-bs-migrator.php' );
require_once( 'inc/class-bs-migration.php' );
require_once( 'inc/class-bs-companion.php' );
require_once( 'inc/class-bs-admin-tools.php' );
require_once( 'inc/class-bs-admin-migration.php' );
require_once( 'inc/class-bs-cli.php' );

class BootstrapShortcodes{
Expand Down Expand Up @@ -178,13 +182,19 @@ function enqueue_assets() {
}
$this->assets_enqueued = true;
$init_deps = array( 'jquery' );
if ( $this->option_on( 'chk_default_options_css' ) ) {
// The legacy-assets companion may already have queued the same files
// (conditional loading, late render-time load): never load Bootstrap twice.
if ( $this->option_on( 'chk_default_options_css' ) && ! wp_style_is( 'bs-legacy-bootstrap', 'enqueued' ) ) {
wp_enqueue_style( 'bs_bootstrap', plugins_url( 'css/bootstrap.css', __FILE__ ) );
wp_enqueue_style( 'bs_shortcodes', plugins_url( 'css/shortcodes.css', __FILE__ ) );
}
if ( $this->option_on( 'chk_default_options_js' ) ) {
wp_enqueue_script( 'bs_bootstrap', plugins_url( 'js/bootstrap.js', __FILE__ ), array( 'jquery' ) );
$init_deps[] = 'bs_bootstrap';
if ( wp_script_is( 'bs-legacy-bootstrap', 'enqueued' ) ) {
$init_deps[] = 'bs-legacy-bootstrap';
} else {
wp_enqueue_script( 'bs_bootstrap', plugins_url( 'js/bootstrap.js', __FILE__ ), array( 'jquery' ) );
$init_deps[] = 'bs_bootstrap';
}
}
wp_enqueue_script( 'bs_init', plugins_url( 'js/init.js', __FILE__ ), $init_deps );
}
Expand Down Expand Up @@ -302,5 +312,5 @@ function dw_render_form() {

$bscodes = new BootstrapShortcodes();
if ( is_admin() ) {
$bs_admin_tools = new BS_Admin_Tools();
$bs_admin_tools = new BS_Admin_Tools( new BS_Admin_Migration() );
}
149 changes: 149 additions & 0 deletions companion/bootstrap-legacy-assets/bootstrap-legacy-assets.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<?php
/*
Plugin Name: Bootstrap 3 Legacy Assets
Plugin URI: https://github.com/TheWebShop/bootstrap-shortcodes/blob/master/docs/migration.md
Description: Keeps loading the Bootstrap 3.4.1 CSS, JavaScript and Glyphicons font that the Bootstrap Shortcodes plugin used to load, for sites that still have markup relying on them. No shortcodes, no settings beyond three switches. Bootstrap 3 is end-of-life: this is a compatibility bridge, not a supported framework.
Version: 1.0.0
Author: Kevin Attfield
Requires at least: 5.0
Requires PHP: 7.4
License: GPLv2 or later
*/

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

if ( ! class_exists( 'BS_Legacy_Assets' ) ) :

class BS_Legacy_Assets {

const OPTION = 'bs_legacy_assets';
const VERSION = '1.0.0';

public static function defaults() {
return array( 'css' => '1', 'js' => '1', 'tooltips' => '1' );
}

public static function options() {
$o = get_option( self::OPTION );
return is_array( $o ) ? $o : self::defaults();
}

public function __construct() {
register_activation_hook( __FILE__, array( __CLASS__, 'activate' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue' ), 20 );
add_action( 'admin_init', array( $this, 'register_settings' ) );
add_action( 'admin_menu', array( $this, 'menu' ) );
add_action( 'admin_notices', array( $this, 'notices' ) );
}

public static function activate() {
if ( false === get_option( self::OPTION ) ) {
add_option( self::OPTION, self::defaults() );
}
}

/**
* True when Bootstrap Shortcodes is active and loading its own copy on every
* page. With its conditional loading on it only loads where shortcodes
* remain, so this plugin covers the other pages; other_bootstrap() then sees
* the original's files on pages where it did load, and the original stands
* down on late (render-time) loads once this plugin's files are queued.
*/
public static function original_plugin_loads_assets() {
if ( ! class_exists( 'BootstrapShortcodes' ) ) {
return false;
}
$o = get_option( 'bs_options' );
if ( ! is_array( $o ) || ! empty( $o['chk_conditional_assets'] ) ) {
return false;
}
return ! empty( $o['chk_default_options_css'] ) || ! empty( $o['chk_default_options_js'] );
}

/** A theme or another plugin already registered a Bootstrap file of this kind. */
public static function other_bootstrap( $kind ) {
$deps = 'css' === $kind ? wp_styles() : wp_scripts();
foreach ( $deps->registered as $handle => $dep ) {
if ( 0 === strpos( $handle, 'bs-legacy' ) || empty( $dep->src ) ) {
continue;
}
if ( preg_match( '#bootstrap(\.bundle)?(\.min)?\.(css|js)(\?|$)#i', $dep->src ) ) {
return $handle;
}
}
return '';
}

public function enqueue() {
if ( self::original_plugin_loads_assets() ) {
return; // avoid double loading while the original is still active
}
$o = self::options();
$base = plugins_url( '', __FILE__ );
if ( ! empty( $o['css'] ) && ! self::other_bootstrap( 'css' ) ) {
wp_enqueue_style( 'bs-legacy-bootstrap', $base . '/css/bootstrap.css', array(), '3.4.1' );
// The original loaded this alongside: icon-* sprites and small layout fixups.
wp_enqueue_style( 'bs-legacy-shortcodes', $base . '/css/shortcodes.css', array( 'bs-legacy-bootstrap' ), self::VERSION );
}
if ( ! empty( $o['js'] ) && ! self::other_bootstrap( 'js' ) ) {
wp_enqueue_script( 'bs-legacy-bootstrap', $base . '/js/bootstrap.js', array( 'jquery' ), '3.4.1' );
}
if ( ! empty( $o['tooltips'] ) ) {
wp_enqueue_script( 'bs-legacy-init', $base . '/js/init.js', array( 'jquery' ), self::VERSION );
}
}

public function register_settings() {
register_setting( 'bs_legacy_assets', self::OPTION, array( 'sanitize_callback' => array( __CLASS__, 'sanitize' ) ) );
}

public static function sanitize( $input ) {
$clean = array();
foreach ( array_keys( self::defaults() ) as $key ) {
if ( is_array( $input ) && ! empty( $input[ $key ] ) ) {
$clean[ $key ] = '1';
}
}
return $clean;
}

public function menu() {
add_options_page( 'Legacy Bootstrap', 'Legacy Bootstrap', 'manage_options', 'bs-legacy-assets', array( $this, 'page' ) );
}

public function notices() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
if ( self::original_plugin_loads_assets() ) {
echo '<div class="notice notice-warning"><p>' . esc_html__( 'Bootstrap 3 Legacy Assets is waiting: the Bootstrap Shortcodes plugin is still loading its own Bootstrap files. Deactivate Bootstrap Shortcodes (or turn off its two asset settings) and this plugin takes over.', 'bs-legacy-assets' ) . '</p></div>';
}
}

public function page() {
$o = self::options();
?>
<div class="wrap">
<h1>Bootstrap 3 Legacy Assets</h1>
<p><?php esc_html_e( 'This plugin exists so a site can remove Bootstrap Shortcodes without losing the Bootstrap 3 styling its remaining markup depends on. It loads the same files that plugin loaded (Bootstrap 3.4.1 CSS and JavaScript, the Glyphicons font, that plugin\'s small shortcodes.css, and a tooltip initialiser). It registers no shortcodes.', 'bs-legacy-assets' ); ?></p>
<div class="notice notice-warning inline"><p><strong><?php esc_html_e( 'Bootstrap 3 is end-of-life.', 'bs-legacy-assets' ); ?></strong> <?php esc_html_e( 'Version 3.4.1 (2019) is the last release; the Bootstrap project publishes no fixes for it. Keeping it is a deliberate compatibility choice. Treat this plugin as temporary: migrate the remaining markup to your theme or to blocks, then deactivate it.', 'bs-legacy-assets' ); ?></p></div>
<form method="post" action="options.php">
<?php settings_fields( 'bs_legacy_assets' ); ?>
<table class="form-table">
<tr><th scope="row"><?php esc_html_e( 'Bootstrap CSS', 'bs-legacy-assets' ); ?></th><td><label><input type="checkbox" name="<?php echo esc_attr( self::OPTION ); ?>[css]" value="1" <?php checked( ! empty( $o['css'] ) ); ?> /> <?php esc_html_e( 'Load bootstrap.css (grid, buttons, wells, labels, alerts, panels, Glyphicons)', 'bs-legacy-assets' ); ?></label></td></tr>
<tr><th scope="row"><?php esc_html_e( 'Bootstrap JavaScript', 'bs-legacy-assets' ); ?></th><td><label><input type="checkbox" name="<?php echo esc_attr( self::OPTION ); ?>[js]" value="1" <?php checked( ! empty( $o['js'] ) ); ?> /> <?php esc_html_e( 'Load bootstrap.js (tabs, collapse, dropdowns, modals, tooltips; needs jQuery)', 'bs-legacy-assets' ); ?></label></td></tr>
<tr><th scope="row"><?php esc_html_e( 'Tooltips', 'bs-legacy-assets' ); ?></th><td><label><input type="checkbox" name="<?php echo esc_attr( self::OPTION ); ?>[tooltips]" value="1" <?php checked( ! empty( $o['tooltips'] ) ); ?> /> <?php esc_html_e( 'Initialise [data-toggle="tooltip"] elements, as Bootstrap Shortcodes did', 'bs-legacy-assets' ); ?></label></td></tr>
</table>
<p class="description"><?php esc_html_e( 'If your theme registers its own Bootstrap CSS or JavaScript, the matching file here is skipped automatically to avoid loading it twice.', 'bs-legacy-assets' ); ?></p>
<?php submit_button(); ?>
</form>
</div>
<?php
}
}

endif;

new BS_Legacy_Assets();
26 changes: 26 additions & 0 deletions companion/bootstrap-legacy-assets/readme.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
=== Bootstrap 3 Legacy Assets ===
Contributors: sinetheta
Tags: bootstrap, compatibility
Requires at least: 5.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.0.0
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html

Keeps loading Bootstrap 3.4.1 (CSS, JavaScript, Glyphicons) after the Bootstrap Shortcodes plugin is removed.

== Description ==

A compatibility bridge, generated by the Bootstrap Shortcodes plugin's migration page. It loads exactly the frontend files that plugin loaded, and nothing else: no shortcodes, no editor buttons, no admin tooling. Three switches under Settings > Legacy Bootstrap turn the CSS, the JavaScript and the tooltip initialiser on or off.

**Bootstrap 3 is end-of-life.** 3.4.1 (February 2019) is its last release. Keeping it running is a deliberate choice made for content that still depends on it; the plan should be to migrate that content and deactivate this plugin.

It avoids double loading: while Bootstrap Shortcodes is active and loading its assets on every page, this plugin does nothing; with that plugin's conditional loading on, this plugin covers the pages it skips. If the theme registers its own bootstrap.css or bootstrap.js, the matching file here is skipped.

The CSS switch also loads Bootstrap Shortcodes' small shortcodes.css (old icon-* sprite icons and layout fixups), as that plugin did.

== Changelog ==

= 1.0.0 =
* First release, built from Bootstrap Shortcodes' bundled assets.
4 changes: 2 additions & 2 deletions docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ itself still supports.

| WordPress | PHP | What ran | Result |
| --- | --- | --- | --- |
| 7.1.4 | 8.3 | PHPUnit (81 tests), Playwright (17 tests: Classic Editor dialogs, frontend rendering and interaction under plugin-/theme-/no-Bootstrap-JS setups, conditional loading, block-editor Shortcode block) | green, 2026-10-31 |
| 6.4.7 | 7.4 | PHPUnit (81 tests); every component rendered on the frontend with assets present; deactivate/reactivate keeps settings | green, 2026-10-31 |
| 7.1.4 | 8.3 | PHPUnit (151 tests), Playwright (20 tests: Classic Editor dialogs; frontend rendering and interaction under plugin-/theme-/no-Bootstrap-JS setups; conditional loading; block-editor Shortcode block; the Tools report; the full migration run ending with the plugin deactivated and the migrated pages checked on desktop and phone viewports) | green, 2026-10-31 |
| 6.4.7 | 7.4 | PHPUnit (151 tests, including converter, scanner and migration); every component rendered on the frontend with assets present; deactivate/reactivate keeps settings | green, 2026-10-31 |
| latest (wp-env default) | 8.2 | PHPUnit + Playwright in GitHub Actions on every PR | see CI |

Not tested: multisite, PHP 8.4 specifically (nothing in the plugin is version
Expand Down
Loading
Loading