Last active
October 30, 2025 13:59
-
-
Save christianwach/1adc33da22515471c749f5ba55e8726a to your computer and use it in GitHub Desktop.
Remove "ms-files.php" dependency from a WordPress Multi-Network install.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh | |
| # ---------------------------------------------------------------------------------------- | |
| # Title: Remove "ms-files.php" dependency. | |
| # Description: Removes the "ms-files.php" dependency from a WordPress Multi-Network install. | |
| # This script will also work on a single WordPress Multisite instance because | |
| # in that case there will only be one record in the `wp_sites` table. | |
| # Author: Christian Wach <[email protected]> | |
| # Props: Austin Ginder <https://anchor.host/removing-legacy-ms-files-php-from-multisite/> | |
| # Version: 1.0.0 | |
| # Gist URL: https://gist.github.com/christianwach/1adc33da22515471c749f5ba55e8726a | |
| # ---------------------------------------------------------------------------------------- | |
| # Where is your WP-CLI? Just put "wp" if it's already aliased. | |
| wp_cli="/path/to/wp-cli.phar" | |
| # WordPress Primary Network URL. | |
| # In WordPress Multisite, this is the Main Site URL. | |
| wp_main_site="https://example.org" | |
| # WordPress database prefix. | |
| wp_db_prefix="wp_" | |
| # Where is your project root directory? | |
| project_root="/path/to/project/root" | |
| # WordPress root directory. | |
| wp_root="${project_root}/httpdocs" | |
| # WordPress directory locations. | |
| wp_content="${wp_root}/wp-content" | |
| wp_mu_plugins="${wp_content}/mu-plugins" | |
| wp_plugins="${wp_content}/plugins" | |
| wp_themes="${wp_content}/themes" | |
| wp_uploads="${wp_content}/uploads" | |
| wp_sites="${wp_uploads}/sites" | |
| wp_blogs_dir="${wp_content}/blogs.dir" | |
| # That's all, stop editing! Happy upgrading. | |
| # ---------------------------------------------------------------------------------------- | |
| # Set default modifier vars. | |
| # ---------------------------------------------------------------------------------------- | |
| # For safety, make "dry run" the default. | |
| dry_run="1" | |
| if [ "$1" = "-live" ]; then | |
| dry_run="0" | |
| fi | |
| # Make feedback the default. | |
| feedback="1" | |
| if [ "$1" = "-quiet" ]; then | |
| feedback="0" | |
| fi | |
| # We need a minimum of the URL param to use WP-CLI. | |
| wp_init="${wp_cli} --url=${wp_main_site}" | |
| # ---------------------------------------------------------------------------------------- | |
| # Update options for all Sites. | |
| # ---------------------------------------------------------------------------------------- | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "--------------------------------------------------------------------------------" | |
| echo "Updating options for all Sites." | |
| echo "--------------------------------------------------------------------------------" | |
| echo "" | |
| echo "Changing directory to: ${wp_root}" | |
| echo "" | |
| fi | |
| cd ${wp_root} | |
| # # Loop through all Sites. | |
| site_urls=$( ${wp_init} site list --field=url ) | |
| for site_url in ${site_urls}; do | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Site URL: ${site_url}" | |
| fi | |
| # | |
| # Clear legacy 'upload_path' option. | |
| # | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Setting 'upload_path' to empty default." | |
| if [[ ${dry_run} == "1" ]]; then | |
| echo "${wp_cli} option set upload_path '' --url=${site_url}" | |
| fi | |
| fi | |
| if [[ ${dry_run} == "0" ]]; then | |
| ${wp_cli} option set upload_path "" --url=${site_url} | |
| fi | |
| # | |
| # Clear legacy 'upload_url_path' option. | |
| # | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Setting 'upload_url_path' to empty default." | |
| if [[ ${dry_run} == "1" ]]; then | |
| echo "${wp_cli} option set upload_url_path '' --url=${site_url}" | |
| fi | |
| fi | |
| if [[ ${dry_run} == "0" ]]; then | |
| ${wp_cli} option set upload_url_path "" --url=${site_url} | |
| fi | |
| # | |
| # Remove legacy 'fileupload_url' option. | |
| # | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Deleting legacy 'fileupload_url' option." | |
| if [[ ${dry_run} == "1" ]]; then | |
| echo "${wp_cli} option delete fileupload_url --url=${site_url}" | |
| fi | |
| fi | |
| if [[ ${dry_run} == "0" ]]; then | |
| ${wp_cli} option delete fileupload_url --url=${site_url} | |
| fi | |
| done | |
| # ---------------------------------------------------------------------------------------- | |
| # Search and replace asset URLs on all Sites. | |
| # ---------------------------------------------------------------------------------------- | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "--------------------------------------------------------------------------------" | |
| echo "Search and replace asset URLs on all Sites." | |
| echo "--------------------------------------------------------------------------------" | |
| echo "" | |
| echo "Changing directory to: ${wp_root}" | |
| echo "" | |
| fi | |
| cd ${wp_root} | |
| # # Correct urls for each site | |
| site_ids=$( ${wp_init} site list --field=blog_id ) | |
| for site_id in ${site_ids}; do | |
| # | |
| # Skip Main Network. | |
| # | |
| # Note: The ID should ideally be checked by calling `is_main_network()` because | |
| # it can be modified via: | |
| # | |
| # * The 'PRIMARY_NETWORK_ID' constant. | |
| # * The 'get_main_network_id' filter. | |
| # | |
| # My install does neither if these things. | |
| # | |
| # @see https://developer.wordpress.org/reference/functions/is_main_network/ | |
| # @see https://developer.wordpress.org/reference/functions/get_main_network_id/ | |
| # | |
| if [[ ${site_id} == "1" ]]; then | |
| continue | |
| fi | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Site ID: ${site_id}" | |
| fi | |
| # Require Home URL to end in a trailing slash. | |
| home_url=$( ${wp_init} db query "SELECT option_value from ${wp_db_prefix}${site_id}_options where option_name = 'home';" --skip-column-names --batch ) | |
| home_url="${home_url%/}/" | |
| # Sanity check. | |
| if [[ ${home_url} == "https"* ]]; then | |
| # Search-and-replace 'files' URLs. | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Search and replace 'files' URLs." | |
| echo "Search for: ${home_url}files/" | |
| echo "Replace with: ${home_url}wp-content/uploads/sites/${site_id}/" | |
| if [[ ${dry_run} == "1" ]]; then | |
| echo "${wp_init} search-replace ${home_url}files/ ${home_url}wp-content/uploads/sites/${site_id}/ --all-tables --report-changed-only" | |
| fi | |
| fi | |
| if [[ ${dry_run} == "0" ]]; then | |
| # Search-and-replace in all tables, because assets can be referenced on any Site. | |
| ${wp_init} search-replace ${home_url}files/ ${home_url}wp-content/uploads/sites/${site_id}/ --all-tables --report-changed-only | |
| fi | |
| # Search-and-replace 'blogs.dir' URLs. | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Search and replace 'blogs.dir' URLs." | |
| echo "Search for: ${home_url}wp-content/blogs.dir/${site_id}/files/" | |
| echo "Replace with: ${home_url}wp-content/uploads/sites/${site_id}/" | |
| if [[ ${dry_run} == "1" ]]; then | |
| echo "${wp_init} search-replace ${home_url}wp-content/blogs.dir/${site_id}/files/ ${home_url}wp-content/uploads/sites/${site_id}/ --all-tables --report-changed-only" | |
| fi | |
| fi | |
| if [[ ${dry_run} == "0" ]]; then | |
| # Search-and-replace in all tables, because assets can be referenced on any Site. | |
| ${wp_init} search-replace ${home_url}wp-content/blogs.dir/${site_id}/files/ ${home_url}wp-content/uploads/sites/${site_id}/ --all-tables --report-changed-only | |
| fi | |
| fi | |
| # Remove all transients. | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Deleting transients." | |
| if [[ ${dry_run} == "1" ]]; then | |
| echo "${wp_init} db query \"DELETE FROM ${wp_db_prefix}${site_id}_options WHERE option_name LIKE '%_transient_%';\"" | |
| fi | |
| fi | |
| if [[ ${dry_run} == "0" ]]; then | |
| ${wp_init} db query "DELETE FROM ${wp_db_prefix}${site_id}_options WHERE option_name LIKE '%_transient_%';" | |
| fi | |
| done | |
| # ---------------------------------------------------------------------------------------- | |
| # Make sure the 'uploads/sites' directory exists. | |
| # ---------------------------------------------------------------------------------------- | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "--------------------------------------------------------------------------------" | |
| echo "Make sure the 'uploads/sites' directory exists." | |
| echo "--------------------------------------------------------------------------------" | |
| echo "" | |
| fi | |
| # Make sure the 'uploads/sites' directory exists. | |
| if [ -d "${wp_sites}" ]; then | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "Directory '${wp_sites}' already exists" | |
| fi | |
| else | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "Creating directory '${wp_sites}'" | |
| if [[ ${dry_run} == "1" ]]; then | |
| echo "mkdir -p ${wp_sites}" | |
| fi | |
| fi | |
| if [[ ${dry_run} == "0" ]]; then | |
| mkdir -p ${wp_sites} | |
| fi | |
| fi | |
| # ---------------------------------------------------------------------------------------- | |
| # Move 'blogs.dir/*/files' directories to 'uploads/sites/*'. | |
| # ---------------------------------------------------------------------------------------- | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "--------------------------------------------------------------------------------" | |
| echo "Move 'blogs.dir/*/files' directories to 'uploads/sites/*'." | |
| echo "--------------------------------------------------------------------------------" | |
| echo "" | |
| echo "Changing directory to: ${wp_blogs_dir}" | |
| echo "" | |
| fi | |
| cd $wp_blogs_dir | |
| # Loop through all the Site IDs that have their assets here. | |
| for site_id in */; do | |
| # Verify that the Site ID is not empty. | |
| if [[ ${site_id} == "" ]]; then | |
| continue | |
| fi | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Site ID: ${site_id}" | |
| echo "" | |
| fi | |
| # | |
| # Main Network: attempt to move files to 'wp-content/uploads'. | |
| # | |
| # Note: The ID should ideally be checked by calling `is_main_network()` because | |
| # it can be modified via: | |
| # | |
| # * The 'PRIMARY_NETWORK_ID' constant. | |
| # * The 'get_main_network_id' filter. | |
| # | |
| # My install does neither if these things. | |
| # | |
| # @see https://developer.wordpress.org/reference/functions/is_main_network/ | |
| # @see https://developer.wordpress.org/reference/functions/get_main_network_id/ | |
| # | |
| if [[ ${site_id} == "1/" ]]; then | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Top level site found. Attempting to move files." | |
| echo "Please verify that '${wp_blogs_dir}/1/' is empty." | |
| fi | |
| # Move any top level files or folders. | |
| for handle in $( ls ${site_id}files/ ); do | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Moving ${site_id}files/${handle} to ${wp_uploads}" | |
| if [[ ${dry_run} == "1" ]]; then | |
| echo "mv ${site_id}files/${handle} ${wp_uploads}/" | |
| fi | |
| fi | |
| if [[ ${dry_run} == "0" ]]; then | |
| mv ${site_id}files/${handle} ${wp_uploads}/ | |
| fi | |
| done | |
| continue | |
| fi | |
| # | |
| # Not Main Network: move files to 'wp-content/uploads/sites/*'. | |
| # | |
| # This is the same regardless of whether the Site is the Main Site for a Network | |
| # or, if not, which Network the Site is assigned to. | |
| # | |
| # Delete the 'uploads/sites/*' directory if it exists. | |
| if [ -d "${wp_sites}/${site_id}" ]; then | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "Directory '${wp_sites}/${site_id}' already exists - deleting it..." | |
| if [[ ${dry_run} == "1" ]]; then | |
| echo "rm -rf ${wp_sites}/${site_id}" | |
| fi | |
| fi | |
| if [[ ${dry_run} == "0" ]]; then | |
| rm -rf ${wp_sites}/${site_id} | |
| fi | |
| else | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "Directory '${wp_sites}/${site_id}' does not exist - okay to proceed..." | |
| fi | |
| fi | |
| # Move the 'blogs.dir/*/files' directory. | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Moving ${site_id}files/ to ${wp_sites}/${site_id}" | |
| if [[ ${dry_run} == "1" ]]; then | |
| echo "mv ${site_id}files/ ${wp_sites}/${site_id}" | |
| fi | |
| fi | |
| if [[ ${dry_run} == "0" ]]; then | |
| mv ${site_id}files/ ${wp_sites}/${site_id} | |
| fi | |
| # | |
| # Move any top-level files or folders. | |
| # | |
| # This shouldn't move anything in a properly configured Network or Site | |
| # but it's good to be thorough. | |
| # | |
| for handle in $( ls ${site_id} ); do | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Moving ${site_id}${handle} to ${wp_sites}/${site_id}" | |
| if [[ ${dry_run} == "1" ]]; then | |
| echo "mv ${site_id}${handle} ${wp_sites}/${site_id}" | |
| fi | |
| fi | |
| if [[ ${dry_run} == "0" ]]; then | |
| mv ${site_id}${handle} ${wp_sites}/${site_id} | |
| fi | |
| done | |
| done | |
| # ---------------------------------------------------------------------------------------- | |
| # Make sure the 'ms_files_rewriting' setting is correct for every Network. | |
| # ---------------------------------------------------------------------------------------- | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "--------------------------------------------------------------------------------" | |
| echo "Ensure 'ms_files_rewriting' setting is correct for every Network." | |
| echo "--------------------------------------------------------------------------------" | |
| echo "" | |
| echo "Changing directory to: ${wp_root}" | |
| echo "" | |
| fi | |
| cd ${wp_root} | |
| # Loop through all Networks. | |
| network_ids=$( ${wp_init} db query "SELECT id FROM ${wp_db_prefix}site ORDER BY id;" --skip-column-names ) | |
| for network_id in ${network_ids}; do | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Network ID: ${network_id}" | |
| fi | |
| # Check for existing 'ms_files_rewriting' record. | |
| rewriting=$( ${wp_init} network meta get ${network_id} ms_files_rewriting ) | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Rewriting: ${rewriting}" | |
| fi | |
| if [ -z ${rewriting} ]; then | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "Adding record to disable ms-files.php" | |
| if [[ ${dry_run} == "1" ]]; then | |
| echo "${wp_init} network meta add ${network_id} ms_files_rewriting '0'" | |
| fi | |
| fi | |
| if [[ ${dry_run} == "0" ]]; then | |
| ${wp_init} network meta add ${network_id} ms_files_rewriting '0' | |
| fi | |
| elif [[ ${rewriting} == "1" ]]; then | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "Updating existing record to disable ms-files.php" | |
| if [[ ${dry_run} == "1" ]]; then | |
| echo "${wp_init} network meta update ${network_id} ms_files_rewriting '0'" | |
| fi | |
| fi | |
| if [[ ${dry_run} == "0" ]]; then | |
| ${wp_init} network meta update ${network_id} ms_files_rewriting '0' | |
| fi | |
| else | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "Existing record already disables ms-files.php" | |
| fi | |
| fi | |
| done | |
| # ---------------------------------------------------------------------------------------- | |
| # Finish up. | |
| # ---------------------------------------------------------------------------------------- | |
| # Remove the 'blogs.dir' directory. | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "Removing ${wp_blogs_dir} directory" | |
| if [[ ${dry_run} == "1" ]]; then | |
| echo "rm -rf ${wp_blogs_dir}" | |
| fi | |
| fi | |
| if [[ ${dry_run} == "0" ]]; then | |
| rm -rf ${wp_blogs_dir} | |
| fi | |
| # Ping! | |
| tput bel | |
| # Final feedback. | |
| if [[ ${feedback} == "1" ]]; then | |
| echo "" | |
| echo "--------------------------------------------------------------------------------" | |
| echo "Done!" | |
| echo "--------------------------------------------------------------------------------" | |
| echo "" | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment