From 23b8517df5cee703a569015cc6e6efbf4475052c Mon Sep 17 00:00:00 2001 From: Vikas Singhal Date: Thu, 4 Dec 2025 16:24:29 +0000 Subject: [PATCH 1/2] fix: Symlinked plugin support and local dev URL detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix iwp_get_wp_root_directory() to work with symlinked plugins - Use multiple starting paths including DOCUMENT_ROOT + REQUEST_URI - Add local dev URL detection in InstaWP_Tools CRITICAL FIX: Migrations from symlinked plugin directories (/mnt/plugin-dev/) failed because __DIR__ resolves symlinks, breaking wp-config.php traversal. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- includes/class-instawp-tools.php | 43 ++++++++++++++++++++ includes/functions-pull-push.php | 67 ++++++++++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 4 deletions(-) diff --git a/includes/class-instawp-tools.php b/includes/class-instawp-tools.php index b963bc3b..5c16c5f8 100644 --- a/includes/class-instawp-tools.php +++ b/includes/class-instawp-tools.php @@ -753,6 +753,14 @@ public static function is_migrate_file_accessible( $file_url, $in_details = fals 'file_url' => $file_url, 'error' => false, ); + + // Skip external accessibility check for local development URLs + if ( self::is_local_development_url( $file_url ) ) { + $result['is_accessible'] = true; + $result['message'] = 'Local development - skipping external check'; + return $in_details ? $result : $result['is_accessible']; + } + try { $response = wp_remote_post( INSTAWP_API_DOMAIN_PROD . '/public/check/?url=' . rawurlencode( $file_url ), @@ -790,6 +798,35 @@ public static function is_migrate_file_accessible( $file_url, $in_details = fals return $in_details ? $result : $result['is_accessible']; } + /** + * Check if a URL is for local development (not publicly accessible). + * + * @param string $url The URL to check. + * @return bool True if local development URL. + */ + private static function is_local_development_url( $url ) { + $local_patterns = array( + 'localhost', + '.local', + '.test', + '.dev', + '127.0.0.1', + '10.5.0.', // Docker macvlan network + '172.17.', // Docker bridge network + '172.18.', // Docker custom networks + '192.168.', // Local network + '-local.instawp', // InstaWP local dev pattern (e.g., vik-local.instawp.me) + ); + + foreach ( $local_patterns as $pattern ) { + if ( strpos( $url, $pattern ) !== false ) { + return true; + } + } + + return false; + } + public static function process_migration_settings( $migrate_settings = array() ) { $options = Helper::get_args_option( 'options', $migrate_settings, array() ); @@ -1468,6 +1505,12 @@ public static function get_pull_pre_check_response( $migrate_key, $migrate_setti $is_wp_root_available = self::is_wp_root_available(); $serve_with_wp = false; + // Fallback: If ABSPATH is defined and wp-load.php exists, WordPress root is available + // This handles symlinked plugin directories where __DIR__ walking fails + if ( ! $is_wp_root_available && defined( 'ABSPATH' ) && file_exists( ABSPATH . 'wp-load.php' ) ) { + $is_wp_root_available = true; + } + if ( ! $is_wp_root_available ) { $is_wp_root_available = self::is_wp_root_available( 'wp-config.php' ); } diff --git a/includes/functions-pull-push.php b/includes/functions-pull-push.php index b8942460..e490418f 100644 --- a/includes/functions-pull-push.php +++ b/includes/functions-pull-push.php @@ -15,14 +15,73 @@ function iwp_debug( $data ) { } if ( ! function_exists( 'iwp_get_wp_root_directory' ) ) { + /** + * Find WordPress root directory by traversing up from the current directory. + * + * This function handles symlinked plugin installations by trying multiple + * starting paths: the real path (__DIR__), and the request URI path which + * preserves symlink paths. + * + * @param string $find_with_files File to look for (e.g., 'wp-load.php', 'wp-config.php') + * @param string $find_with_dir Directory to look for (e.g., 'wp', 'flywheel-config') + * @return array Array with 'status' (bool) and 'root_path' (string) + */ function iwp_get_wp_root_directory( $find_with_files = 'wp-load.php', $find_with_dir = '' ) { + // Build list of starting directories to try + // This handles symlinked plugins where __DIR__ resolves to the real path + $starting_dirs = array( __DIR__ ); + + // Try to get the symlink path from REQUEST_URI or SCRIPT_FILENAME + if ( isset( $_SERVER['SCRIPT_FILENAME'] ) ) { + $script_dir = dirname( $_SERVER['SCRIPT_FILENAME'] ); + if ( $script_dir !== __DIR__ && ! in_array( $script_dir, $starting_dirs ) ) { + $starting_dirs[] = $script_dir; + } + } + + // Also try DOCUMENT_ROOT based path construction from REQUEST_URI + if ( isset( $_SERVER['DOCUMENT_ROOT'] ) && isset( $_SERVER['REQUEST_URI'] ) ) { + $request_path = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ); + if ( $request_path ) { + $doc_root_path = $_SERVER['DOCUMENT_ROOT'] . dirname( $request_path ); + if ( is_dir( $doc_root_path ) && ! in_array( $doc_root_path, $starting_dirs ) ) { + $starting_dirs[] = $doc_root_path; + } + } + } + + foreach ( $starting_dirs as $start_dir ) { + $result = iwp_find_wp_root_from_dir( $start_dir, $find_with_files, $find_with_dir ); + if ( $result['status'] ) { + return $result; + } + } + + // If all attempts failed, return the last result + return array( + 'status' => false, + 'root_path' => '', + ); + } +} + +if ( ! function_exists( 'iwp_find_wp_root_from_dir' ) ) { + /** + * Helper function to find WordPress root from a specific starting directory. + * + * @param string $start_dir Starting directory to traverse from + * @param string $find_with_files File to look for + * @param string $find_with_dir Directory to look for + * @return array Array with 'status' (bool) and 'root_path' (string) + */ + function iwp_find_wp_root_from_dir( $start_dir, $find_with_files = '', $find_with_dir = '' ) { $is_find_root_dir = true; $root_path = ''; if ( ! empty( $find_with_files ) ) { $level = 0; - $root_path_dir = __DIR__; - $root_path = __DIR__; + $root_path_dir = $start_dir; + $root_path = $start_dir; $is_find_root_dir = true; while ( ! file_exists( $root_path . DIRECTORY_SEPARATOR . $find_with_files ) ) { @@ -39,8 +98,8 @@ function iwp_get_wp_root_directory( $find_with_files = 'wp-load.php', $find_with if ( ! empty( $find_with_dir ) ) { $level = 0; - $root_path_dir = __DIR__; - $root_path = __DIR__; + $root_path_dir = $start_dir; + $root_path = $start_dir; $is_find_root_dir = true; while ( ! is_dir( $root_path . DIRECTORY_SEPARATOR . $find_with_dir ) ) { From 7fc35b9c43ae805635e85abe80c1b6f5abd83a99 Mon Sep 17 00:00:00 2001 From: Vikas Singhal Date: Mon, 8 Dec 2025 07:58:06 +0000 Subject: [PATCH 2/2] fix: remove pricing display for free plan in staging UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Free plan no longer shows "$0.00/mo" - only paid plans display pricing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- migrate/templates/ajax/part-site-plans.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/migrate/templates/ajax/part-site-plans.php b/migrate/templates/ajax/part-site-plans.php index f28d2043..2f0af0f9 100644 --- a/migrate/templates/ajax/part-site-plans.php +++ b/migrate/templates/ajax/part-site-plans.php @@ -62,9 +62,7 @@ class="plan-selector peer !hidden"
- - /mo - + /mo - /day