Skip to content

Instantly share code, notes, and snippets.

@jessedyck
Last active March 10, 2024 14:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jessedyck/c5a318f69680b3a3cacf253f4c6ff040 to your computer and use it in GitHub Desktop.
Save jessedyck/c5a318f69680b3a3cacf253f4c6ff040 to your computer and use it in GitHub Desktop.
Convert from the (abandoned) footnotes plugin to WordPress core footnotes.
<?php
/**
* A script to convert from the (abandoned) footnotes plugin to WP core footnotes.
* This script is intended to be ran from wp-cli.
* Requires WP 6.3. Does not require that the original footnotes plugin still be installed,
* but does require that the options in `wp_options` are still in place.
*
* BACKUP YOUR DATABASE!
* This script is not well-tested. It works for my (small) use-case. It should support
* the original plugin's various short codes, but has only been tested with the
* default `[ref]` and similar default settings.
*
* Proceed with caution!
*
* Usage:
* wp eval-file convert-footnotes.php
*
* @see https://wordpress.org/plugins/footnotes/
*/
// phpcs:set WordPress.NamingConventions.PrefixAllGlobals prefixes[] jdf
/**
* Encapsulate in function to namespace variables.
*/
function jdf_run(): void {
/**
* Ensure we have the footnotes block
*/
if ( ! WP_Block_Type_Registry::get_instance()->is_registered( 'core/footnotes' ) ) {
echo 'Core footnotes block is not registered';
die();
}
/**
* Get the plugin's settings.
*/
$footnotes_options = get_option( 'footnotes_storage' );
if ( ! is_array( $footnotes_options ) ) {
echo "Could not find 'footnotes' plugin options";
die();
}
// Get the short code used by footnotes plugin.
$start_tag = $footnotes_options['footnote_inputfield_placeholder_start'];
$end_tag = $footnotes_options['footnote_inputfield_placeholder_end'];
$escaped_start_tag = preg_quote( $start_tag );
$escaped_end_tag = preg_quote( $end_tag, '/' );
// Get all posts to check.
// Prior to 6.5, footnotes were only supported on core post types.
$post_types = 'any';
if ( ! is_wp_version_compatible( '6.5' ) ) {
echo "Note: Prior to WP 6.5, footnotes were not supported on CPTs. Re-run the script after upgrading to convert footnotes on more post types.\n";
$post_types = array( 'post', 'page' );
}
$args = array(
'post_type' => $post_types,
'post_status' => 'publish',
'posts_per_page' => '-1',
);
$all_posts = new WP_Query( $args );
echo "Found {$all_posts->found_posts} posts\n";
if ( $all_posts->have_posts() ) {
while ( $all_posts->have_posts() ) {
$all_posts->the_post();
$content = get_the_content();
$matches = array();
$regex = "/$escaped_start_tag(.*?)$escaped_end_tag/i";
if ( preg_match_all( $regex, $content, $matches ) ) {
$pid = get_the_ID();
$permalink = get_the_permalink();
echo "Found plugin footnote on post ID: {$pid}, permalink: {$permalink}\n";
/**
* In my case, there are duplicate matches because of the `source` block
* attribute from Jetpack's Markdown block.
* Deduping ensures no extra metadata is created.
* */
$matches[0] = array_unique( $matches[0] );
$matches[1] = array_unique( $matches[1] );
// Get existing core footnotes.
$footnotes = get_post_meta( $pid, 'footnotes', true );
if ( empty( $footnotes ) ) {
$footnotes = array();
} else {
$footnotes = json_decode( $footnotes );
}
foreach ( $matches[0] as $key => $match ) {
$fn = new stdClass();
$uid = strtolower( jdf_GUID() );
$fn->content = $matches[1][ $key ];
$fn->id = $uid;
$footnotes[] = $fn;
$fn_num = count( $footnotes );
// Markup format is as of WP6.4.1.
$content = str_replace(
$matches[0][ $key ],
sprintf( '<sup data-fn="%1$s" class="fn"><a href="#%1$s" id="%1$s-link">%2$d</a></sup>', $uid, $fn_num ),
$content
);
}
// Add the footnotes block, if it doesn't exist.
if ( ! has_block( 'core/footnotes' ) ) {
$content .= '<!-- wp:footnotes /-->';
}
// Confirm before updating.
$prompt = readline( "Update post ID: {$pid}? (y) " );
if ( $prompt === 'y' ) {
echo "Updating $pid...\n";
wp_update_post(
array(
'ID' => $pid,
'post_content' => $content,
'meta_input' => array(
'footnotes' => wp_json_encode( $footnotes, JSON_UNESCAPED_UNICODE ),
),
)
);
} else {
echo "Skipping $pid.\n";
}
}
}
}
}
jdf_run();
/**
* Generates a GUID for use in linking footnotes.
*
* @link https://stackoverflow.com/a/26163679
* @return string
*/
/* phpcs:disable */
function jdf_GUID()
{
if (function_exists('com_create_guid') === true)
{
return trim(com_create_guid(), '{}');
}
return sprintf('%04X%04X-%04X-%04X-%04X-%04X%04X%04X', mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(16384, 20479), mt_rand(32768, 49151), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535));
}
/* phpcs:enable */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment