Plugin Directory

Changeset 3440991


Ignore:
Timestamp:
01/16/2026 12:55:46 PM (2 months ago)
Author:
surflabtech
Message:

v2.3.9

Location:
surflink
Files:
305 added
10 edited

Legend:

Unmodified
Added
Removed
  • surflink/trunk/assets/css/surfl-loginhider.css

    r3437024 r3440991  
    11/* CSS for the login form styling */
    22.surfl-lh-login-form input[type="submit"] {
    3 background: #172124;
     3  background: #172124;
    44  color: white;
    55  border: transparent;
     
    1313
    1414.surfl-lh-login-form input[type="submit"]:hover {
    15      transform: translateY(-1px);
    16     transition: 0.3s;
     15  transform: translateY(-1px);
     16  transition: 0.3s;
    1717}
    18 
    1918
    2019.surfl-lh-login-form input[type="text"],
    2120.surfl-lh-login-form input[type="password"] {
    22      border: 1px solid transparent !important;
     21  border: 1px solid transparent !important;
    2322  background: #42414d;
    2423  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
    2524}
    2625
    27 .surfl-lh-login-form label ,.surfl-lh-login-form h2 {
    28        color: #c8c8c8;
     26.surfl-lh-login-form label,
     27.surfl-lh-login-form h2 {
     28  color: #c8c8c8;
    2929}
    3030
     
    3737/* Full-screen modal overlay */
    3838.surfl-lh-modal-overlay {
    39     position: fixed;
    40     top: 0;
    41     left: 0;
    42     width: 100%;
    43     height: 100%;
    44     background-color: rgba(0, 0, 0, 0.88); /* Dark overlay */
    45     backdrop-filter: blur(5px); /* Add blur effect */
    46     display: flex;
    47     justify-content: center;
    48     align-items: center;
    49     z-index: 9999; /* Ensure it's on top of other content */
     39  position: fixed;
     40  top: 0;
     41  left: 0;
     42  width: 100%;
     43  height: 100%;
     44  background-color: rgba(0, 0, 0, 0.88); /* Dark overlay */
     45  backdrop-filter: blur(5px); /* Add blur effect */
     46  display: flex;
     47  justify-content: center;
     48  align-items: center;
     49  z-index: 9999; /* Ensure it's on top of other content */
    5050}
    5151
    5252/* Additional styling for the login form container */
    5353.surfl-lh-login-form {
    54     width: 338px;
     54  width: 338px;
    5555  padding: 30px;
    5656  border-radius: 8px;
    5757
    5858  margin: 0;
    59   background: #2B2A33;
     59  background: #2b2a33;
    6060  border: 1px solid transparent;
    6161}
     
    6363/* Styling for error messages */
    6464.surfl-lh-login-error {
    65 margin-bottom: 15px;
     65  margin-bottom: 15px;
    6666  padding: 10px;
    6767  background-color: #454444;
     
    7474/* Styling for form elements */
    7575
    76 
    7776.surfl-lh-login-form label {
    78     display: block;
    79     margin-bottom: 5px;
    80     font-size: 16px;
     77  display: block;
     78  margin-bottom: 5px;
     79  font-size: 16px;
    8180}
    8281
     
    8483  text-align: center;
    8584  margin-bottom: 30px;
    86       font-size: 19px;
    87       font-weight: 600;
    88 
     85  font-size: 19px;
     86  font-weight: 600;
    8987}
    9088
    9189.surfl-lh-login-form input[type="text"],
    9290.surfl-lh-login-form input[type="password"] {
    93     width: 100%;
    94     padding: 8px;
    95     box-sizing: border-box;
    96     border-radius: 4px;
     91  width: 100%;
     92  padding: 8px;
     93  box-sizing: border-box;
     94  border-radius: 4px;
    9795}
    9896
    9997.surfl-lh-login-form .submit {
    100     margin-bottom: 0;
     98  margin-bottom: 0;
    10199}
    102100
    103101.surfl-lh-login-form .surfl-lh-login-form input[type="submit"] {
    104     width: 100%;
     102  width: 100%;
    105103}
    106104
    107105.surfl-lh-login-form .powered-by {
    108     text-align: center;
    109     margin-top: 20px;
    110     font-size: 16px;
    111     color: #888;
     106  text-align: center;
     107  margin-top: 20px;
     108  font-size: 16px;
     109  color: #888;
    112110}
    113111.surfl-lh-forgot-password {
     
    116114}
    117115
    118 .surfl-lh-forgot-password a{
    119     text-decoration: none;
     116.surfl-lh-forgot-password a {
     117  text-decoration: none;
    120118}
    121119.surfl-gradient-text {
    122   background: linear-gradient(
    123     to right,
    124     #22d3ee,
    125     #3b82f6
    126   );
     120  background: linear-gradient(to right, #22d3ee, #3b82f6);
    127121  -webkit-background-clip: text;
    128122  -webkit-text-fill-color: transparent;
     
    132126}
    133127
     128/* LIGHT MODE */
    134129
     130@media (prefers-color-scheme: light) {
     131  .surfl-lh-login-form {
     132    background: linear-gradient(to bottom, #cef4fa, #ffffff);
     133  }
     134
     135  .surfl-lh-login-form input[type="submit"] {
     136    color: #172124;
     137    background-color: white;
     138  }
     139
     140  .surfl-lh-login-form input[type="text"],
     141  .surfl-lh-login-form input[type="password"] {
     142    background: #fff !important;
     143    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15) !important;
     144  }
     145.surfl-gradient-text {
     146   background: linear-gradient(to right, #121c1d, #080a90);
     147   -webkit-background-clip: text;
     148  -webkit-text-fill-color: transparent;
     149
     150  background-clip: text;
     151  color: transparent;
     152}
     153
     154.surfl-lh-login-form .powered-by,
     155.surfl-lh-forgot-password ,
     156.surfl-lh-login-form label,
     157.surfl-lh-login-form h2 {
     158  color: rgb(27, 26, 26);
     159}
     160
     161}
  • surflink/trunk/assets/js/surfl.js

    r3432780 r3440991  
    8888    switch (typeName) {
    8989      case "database":
    90         msg = message || (status == "ongoing" ? "processing" : msg);
     90        msg = message || (status == "ongoing" || status == "replace_ongoing" ? "processing" : msg);
    9191        icon = statusDivs.database.find(".dashicons");
    9292        break;
     
    139139      statusType = statusDivs[typeName];
    140140    }
    141     if (status == "ongoing") {
     141    if (status == "ongoing" || status == "replace_ongoing") {
    142142      icon.removeClass("dashicons-yes dashicons-warning");
    143143
     
    219219  };
    220220})(jQuery); // pass jQuery as $
     221
    221222
    222223/*
     
    19061907        success: function (response) {
    19071908          if (response.success) {
    1908             if (response.data?.status === "ongoing") {
     1909            if (response.data?.status === "ongoing" || response.data?.status === "replace_ongoing") {
    19091910              showRestoreSuccess(
    19101911                response.data?.type,
     
    19131914              );
    19141915
    1915               restoreNextFile(response.data?.state);
     1916              if(response.data?.status === "replace_ongoing"){
     1917                databaseReplaceOperation(response.data?.state);
     1918              }
     1919              else restoreNextFile(response.data?.state);
    19161920            } else {
    19171921              showNotification(
     
    19561960      });
    19571961    }
     1962    function databaseReplaceOperation(state = {}) {
     1963
     1964
     1965      const file = filesToRestore[currentFileIndex];
     1966      const fileName = file.filename;
     1967
     1968      $.ajax({
     1969        url: surflJqObj.ajaxurl,
     1970        type: "POST",
     1971        data: {
     1972          action: "surfl_run_db_replace",
     1973          nonce: surflJqObj.nonce,
     1974          filename: file.filename,
     1975          filepath: file.path,
     1976          files_to_backup: JSON.stringify(files_to_backup),
     1977          is_last_uploaded_file: 0,
     1978          state: JSON.stringify(state),
     1979        },
     1980        success: function (response) {
     1981          if (response.success) {
     1982            if (response.data?.status === "replace_ongoing") {
     1983              showRestoreSuccess(
     1984                response.data?.type,
     1985                response.data?.status,
     1986                response.data?.message
     1987              );
     1988
     1989             databaseReplaceOperation(response.data?.state);
     1990            } else {
     1991              showNotification(
     1992                "success",
     1993                `${fileName} restored successfully.`,
     1994                "#surfl-restore-modal-msg"
     1995              );
     1996              showRestoreSuccess(response.data?.type);
     1997              currentFileIndex++;
     1998              restoreNextFile();
     1999            }
     2000          } else {
     2001            failed_backup.push(fileName);
     2002            fail_count++;
     2003            showNotification(
     2004              "error",
     2005              `Failed to restore ${fileName}: ${
     2006                (response.data?.message || "Unknown error.",
     2007                "#surfl-restore-modal-msg")
     2008              }`
     2009            );
     2010            showRestoreSuccess(
     2011              response.data?.type,
     2012              "error",
     2013              response.data?.message || "Unknown error."
     2014            );
     2015            currentFileIndex++;
     2016            restoreNextFile();
     2017          }
     2018        },
     2019        error: function (jqXHR, textStatus, errorThrown) {
     2020          failed_backup.push(fileName);
     2021          fail_count++;
     2022          showNotification(
     2023            "error",
     2024            `An unexpected error occurred while replacing url in ${fileName}. Error: ${textStatus} - ${errorThrown}`,
     2025            "#surfl-restore-modal-msg"
     2026          );
     2027          currentFileIndex++;
     2028          restoreNextFile();
     2029        },
     2030      });
     2031    }
    19582032
    19592033    restoreNextFile();
     
    22102284          const totalFiles = filesToRestore.length;
    22112285
     2286          function databaseReplaceOperation(state = {}) {
     2287   
     2288            const file = filesToRestore[currentFileIndex];
     2289            const fileName = file.filename;
     2290            let is_last_uploaded_file =
     2291              totalFiles - 1 == currentFileIndex ? 1 : 0;
     2292
     2293            $.ajax({
     2294              url: surflJqObj.ajaxurl,
     2295              type: "POST",
     2296              data: {
     2297                action: "surfl_run_db_replace",
     2298                nonce: surflJqObj.nonce,
     2299                filename: file.filename,
     2300                filepath: file.path,
     2301                files_to_backup: JSON.stringify(files_to_backup),
     2302                is_last_uploaded_file: is_last_uploaded_file,
     2303                state: JSON.stringify(state),
     2304              },
     2305              success: function (response) {
     2306                if (response.success) {
     2307                   if (response.data?.status === "replace_ongoing") {
     2308              showUploadRestoreSuccess(
     2309                response.data?.type,
     2310                response.data?.status,
     2311                response.data?.message
     2312              );
     2313
     2314             databaseReplaceOperation(response.data?.state);
     2315            } else {
     2316                    showNotification(
     2317                      "success",
     2318                      `${fileName} restored successfully.`,
     2319                      "#surfl-upload-restore-msg"
     2320                    );
     2321                    showUploadRestoreSuccess(response.data?.type);
     2322
     2323                    currentFileIndex++;
     2324                    restoreNextFile();
     2325                  }
     2326                } else {
     2327                  failed_backup.push(fileName);
     2328                  fail_count++;
     2329                  showNotification(
     2330                    "error",
     2331                    `Failed to restore ${fileName}: ${
     2332                      (response.data?.message || "Unknown error.",
     2333                      "#surfl-upload-restore-msg")
     2334                    }`
     2335                  );
     2336
     2337                  showUploadRestoreSuccess(
     2338                    response.data?.type,
     2339                    "error",
     2340                    response.data?.message || "Unknown error."
     2341                  );
     2342                  currentFileIndex++;
     2343                  restoreNextFile();
     2344                }
     2345              },
     2346              error: function (jqXHR, textStatus, errorThrown) {
     2347                failed_backup.push(fileName);
     2348                fail_count++;
     2349                showNotification(
     2350                  "error",
     2351                  `An unexpected error occurred while replacing url in ${fileName}. Error: ${textStatus} - ${errorThrown}`,
     2352                  "#surfl-upload-restore-msg"
     2353                );
     2354                currentFileIndex++;
     2355                restoreNextFile();
     2356              },
     2357            });
     2358          }
    22122359          function restoreNextFile(state = {}) {
    22132360            if (currentFileIndex >= totalFiles) {
     
    22812428              success: function (response) {
    22822429                if (response.success) {
    2283                   if (response.data?.status === "ongoing") {
    2284                     showUploadRestoreSuccess(
    2285                       response.data?.type,
    2286                       response.data?.status,
    2287                       response.data?.message
    2288                     );
    2289 
    2290                     restoreNextFile(response.data?.state);
    2291                   } else {
     2430             if (response.data?.status === "ongoing" || response.data?.status === "replace_ongoing") {
     2431              showUploadRestoreSuccess(
     2432                response.data?.type,
     2433                response.data?.status,
     2434                response.data?.message
     2435              );
     2436
     2437              if(response.data?.status === "replace_ongoing"){
     2438                databaseReplaceOperation(response.data?.state);
     2439              }
     2440              else restoreNextFile(response.data?.state);
     2441            }  else {
    22922442                    showNotification(
    22932443                      "success",
     
    23332483            });
    23342484          }
    2335 
    23362485          restoreNextFile();
    23372486        } else {
  • surflink/trunk/includes/class-surfl-backup-helper.php

    r3437018 r3440991  
    1313    public $subdir;
    1414    public $errors = [];
     15    public $db_prefix;
     16    public $placeholder = '{temp_prefix_surflink}';
     17
    1518
    1619    public function __construct()
    1720    {
     21        global $wpdb;
     22        $this->db_prefix = $wpdb->prefix;
    1823        $this->start_time = time();
    1924        $this->set_optimal_settings();
     
    158163        $sql_file = $backup_subdir . '/database.sql';
    159164        $zip_file = $backup_subdir . '/database.zip';
     165        $json_file = $backup_subdir . '/db-info.json';
    160166        $temp_file = null;
    161167
     
    171177            $batch_size = $this->calculate_initial_batch_size($avg_row_length);
    172178            $processed_table = null;
    173           //  $batch_size = 1000;
     179            //  $batch_size = 1000;
    174180
    175181
     
    231237                fclose($fp); // Close after writing header, will reopen in append mode
    232238
    233 
     239                $db_info = [
     240                    'operator'      => 'SurfLink',
     241                    'site_url'      => SURFL_SITE_URL,
     242                    'home_url'      => SURFL_HOME_URL,
     243                    'table_prefix'  => $wpdb->prefix,
     244                    // Check if your plugin defines a specific version constant, e.g. SURFLINK_VERSION
     245                    'plugin_version' => SURFL_VERSION
     246                ];
     247
     248                // Write to subdir
     249                if (file_put_contents($json_file, json_encode($db_info, JSON_PRETTY_PRINT)) === false) {
     250                    throw new Exception('Failed to create db-info.json');
     251                }
    234252                $state = [
    235253                    'done' => false,
     
    338356
    339357                // Create zip archive
    340                 if ($this->create_zip_archive($sql_file, $zip_file)) {
     358                if ($this->create_zip_archive($sql_file, $zip_file, $json_file)) {
    341359                    // Verify backup integrity
    342360                    if ($this->verify_backup($zip_file)) {
     
    446464
    447465        fwrite($fp, "\n--\n-- Stored Procedures\n--\n\n");
     466       
     467            $prefix_quoted = preg_quote($this->db_prefix, '/');
    448468        foreach ($procedures as $proc) {
    449469            $procedure_name = $proc->SPECIFIC_NAME;
     
    454474            }
    455475
    456             // 🔹 Remove DEFINER clause
     476            // 1. Remove DEFINER clause
    457477            $clean_sql = preg_replace('/DEFINER=`[^`]+`@`[^`]+`\s*/', '', $create_proc[2]);
     478
     479            // 2. Dynamicize Table Prefix
     480     
     481            // Replace `prefix with `placeholder globally
     482            $clean_sql = preg_replace("/`{$prefix_quoted}/", "`{$this->placeholder}", $clean_sql);
    458483
    459484            $output = "/*!50003 DROP PROCEDURE IF EXISTS `$procedure_name` */;\n";
     
    489514
    490515        fwrite($fp, "\n--\n-- Triggers\n--\n\n");
     516
     517           $prefix_quoted = preg_quote($this->db_prefix, '/');
    491518        foreach ($triggers as $trigger) {
    492519            $trigger_name = $trigger->TRIGGER_NAME;
     
    500527            $clean_sql = preg_replace('/DEFINER=`[^`]+`@`[^`]+`\s*/', '', $create_trigger[2]);
    501528
     529            // Dynamicize Table Prefix (inside the trigger logic)
     530 
     531         
     532            // Replace `prefix with `placeholder globally
     533            $clean_sql = preg_replace("/`{$prefix_quoted}/", "`{$this->placeholder}", $clean_sql);
     534
    502535            $output = "/*!50003 DROP TRIGGER IF EXISTS `$trigger_name` */;\n";
    503536            $output .= "DELIMITER $$\n";
     
    521554        }
    522555
    523         $output = "\n--\n-- Table structure for table `$table_name`\n--\n\n";
    524         $output .= "DROP TABLE IF EXISTS `$table_name`;\n";
    525         $output .= $create_table[1] . ";\n\n";
     556        $prefix_quoted = preg_quote($this->db_prefix, '/');
     557
     558        // Example: CREATE TABLE `wp_options` -> CREATE TABLE `{prefix}options`
     559        $structure_sql = preg_replace(
     560            "/`{$prefix_quoted}/",
     561            "`{$this->placeholder}",
     562            $create_table[1],
     563            1
     564        );
     565        if (strpos($table_name, $this->db_prefix) === 0) {
     566            $dynamic_table_name = $this->placeholder . substr($table_name, strlen($this->db_prefix));
     567        } else {
     568            // Backup external tables as-is if they don't share the WP prefix
     569            $dynamic_table_name = $table_name;
     570        }
     571
     572        $output = "\n--\n-- Table structure for table `$dynamic_table_name`\n--\n\n";
     573        $output .= "DROP TABLE IF EXISTS `$dynamic_table_name`;\n";
     574        $output .= $structure_sql . ";\n\n";
    526575        fwrite($fp, $output);
    527576    }
     
    544593            return 0;
    545594        }
    546 
     595        if (strpos($table_name, $this->db_prefix) === 0) {
     596            $dynamic_table_name = $this->placeholder . substr($table_name, strlen($this->db_prefix));
     597        } else {
     598            $dynamic_table_name = $table_name;
     599        }
    547600        // Stream each row directly to the file
    548601        foreach ($rows as $row) {
     
    550603            $column_list = '`' . implode('`, `', $columns) . '`';
    551604            $values = [];
     605
     606
    552607
    553608            foreach ($row as $value) {
     
    561616                }
    562617            }
    563 
    564             $sql = "INSERT INTO `$table_name` ($column_list) VALUES (" . implode(', ', $values) . ");\n";
     618            // We only change the table name in the INSERT statement.
     619            $sql = "INSERT INTO `$dynamic_table_name` ($column_list) VALUES (" . implode(', ', $values) . ");\n";
    565620            fwrite($fp, $sql);
    566621        }
     
    620675
    621676
    622     public function create_zip_archive($sql_file, $zip_file)
     677    public function create_zip_archive($sql_file, $zip_file, $json_file = null)
    623678    {
    624679        $zip = new ZipArchive();
     
    628683
    629684        $zip->addFile($sql_file, basename($sql_file));
     685
     686        // Add JSON file if provided and exists
     687        if ($json_file && file_exists($json_file)) {
     688            $zip->addFile($json_file, basename($json_file));
     689        }
     690
    630691        return $zip->close();
    631692    }
    632 
    633693    public function verify_backup($zip_file)
    634694    {
     
    692752    public function write_backup_header($fp, $mysql_version)
    693753    {
    694        global $wpdb;
    695        
     754        global $wpdb;
     755
    696756        $header = "-- SurfLink\n";
    697757        $header .= "-- site_url: " . site_url() . "\n";
    698         $header .= "-- prefix: " . $wpdb->prefix . "\n";
     758        $header .= "-- home_url: " . home_url() . "\n";
     759        $header .= "-- prefix: " . $wpdb->prefix . "\n";
    699760        $header .= "-- WordPress Database Backup\n";
    700761        $header .= "-- Version " . get_bloginfo('version') . "\n";
     
    732793    public function log_error($message)
    733794    {
    734        // error_log("Backup Error: " . $message);
     795        // error_log("Backup Error: " . $message);
    735796        $this->errors[] = $message;
    736797    }
     
    11231184    }
    11241185
    1125    /**
     1186    /**
    11261187     * Check if a file matches any of the exclusion rules.
    11271188     *
     
    11441205            // Clean the rule for matching (remove slashes from both ends)
    11451206            // Example: "/backup*/" becomes "backup*"
    1146             $clean_rule = trim($rule, '/'); 
     1207            $clean_rule = trim($rule, '/');
    11471208
    11481209            if ($is_anchored) {
     
    11591220                // Matches Rule "/backup*" against File "backup-2025/file.txt"
    11601221                // But ignores File "test2/backup-2025/file.txt"
    1161                
     1222
    11621223                // Get the top-level folder name from the file path
    11631224                $first_slash = strpos($relative_path, '/');
     
    11691230
    11701231                // If anchored rule didn't match the root, stop here.
    1171                 continue; 
     1232                continue;
    11721233            }
    11731234
     
    14581519        $safe_subdir = trim(str_replace('\\', '/', $subdir));
    14591520
    1460     //    error_log("Checking if continue backup event is scheduled for subdir: " . $safe_subdir);
     1521        //    error_log("Checking if continue backup event is scheduled for subdir: " . $safe_subdir);
    14611522
    14621523        if (empty($crons)) {
     
    15401601
    15411602
    1542         public function is_module_enabled($slug)
     1603    public function is_module_enabled($slug)
    15431604    {
    15441605        $options = get_option('surfl_module_settings', []);
     
    15571618        $options = get_option('surfl_module_settings', []);
    15581619        if (isset($options[$slug]) && isset($options[$slug]['disable_background'])) {
    1559            
     1620
    15601621            return  (int) $options[$slug]['disable_background'];
    15611622        }
     
    15731634        return false;
    15741635    }
    1575 
    1576 
    15771636}
  • surflink/trunk/includes/class-surfl-br-loader.php

    r3429296 r3440991  
    1111require_once SURFL_PATH . 'includes/class-surfl-backup-helper.php';
    1212require_once SURFL_PATH . 'includes/class-surfl-restore-files.php';
     13require_once SURFL_PATH . 'includes/class-surfl-br-replace-engine.php';
    1314
    1415
     
    5253        add_action('wp_ajax_surfl_upload_backup_file_chunk', [$this, 'ajax_upload_backup_file_chunk']);
    5354        add_action('wp_ajax_surfl_restore_uploaded_backup', [$this, 'run_restore']);
     55        add_action('wp_ajax_surfl_run_db_replace', [$this, 'run_db_replace']);
    5456
    5557
     
    466468            if (strpos($filename, 'database') !== false) {
    467469
     470                $dir = dirname($filepath);
     471                $json_info_file = $dir . '/db-info.json';
    468472
    469473
    470474                $restore = new SURFL_Database_Restore();
    471                 $result = $restore->restore_database_stream($filepath, $state);
     475
     476                if (!file_exists($json_info_file)) {
     477                 
     478                    $result = $restore->old_restore_database_stream($filepath, $state);
     479                } else {
     480                 
     481                    $result = $restore->restore_database_stream($filepath, $state);
     482                }
     483
    472484                if ($result === false) {
    473485                    throw new Exception('Database restore failed: ' . implode(', ', $restore->get_errors()));
     
    475487                if (!$result['done']) {
    476488                    wp_send_json_success([
    477                         'message' => "Restoring " . $result['current_table_name'] . ': ' . $result['rows_done'] . ' rows are restored.',
     489                        'message' => $result['current_table_name'] . ': ' . $result['rows_done'] . ' rows are restored.',
    478490                        'type' => $type,
    479491                        'status' => 'ongoing',
     
    481493                    ]);
    482494                } else {
     495
     496
     497
     498                    if ($result['site_url_differed'] == '1' || $result['home_url_differed'] == '1') {
     499
     500                        wp_send_json_success([
     501                            'message' => "database imported. Now updating site URLs...",
     502                            'type' => $type,
     503                            'status' => 'replace_ongoing',
     504                            'state' => $result,
     505                        ]);
     506                    }
     507
    483508                    $is_last_uploaded_file && $this->cleanup_surflink_upload_dir();
    484509
    485                     // error_log('Restoring ' . $type . ' completed.');
     510
    486511
    487512
     
    552577
    553578
    554 
     579    public function run_db_replace()
     580    {
     581
     582        $this->check_nonce();
     583        if (!current_user_can('manage_options')) {
     584            wp_send_json_error(['message' => 'Unauthorized']);
     585        }
     586
     587        $filename = isset($_POST['filename']) ? sanitize_file_name($_POST['filename']) : '';
     588        $filepath = isset($_POST['filepath']) ? sanitize_text_field($_POST['filepath']) : '';
     589
     590
     591        $state_raw = $_POST['state'] ?? [];
     592        $state = is_string($state_raw) ? json_decode(stripslashes($state_raw), true) : (is_array($state_raw) ? $state_raw : []);
     593
     594
     595
     596        if (empty($filename) || empty($filepath)) {
     597            wp_send_json_error(['message' => 'Missing filename or filepath.']);
     598        }
     599
     600        $type = 'database';
     601        $is_last_uploaded_file = isset($_POST['is_last_uploaded_file']) ? intval($_POST['is_last_uploaded_file']) : 0;
     602
     603
     604
     605
     606
     607
     608
     609
     610        try {
     611
     612            if (strpos($filename, 'database') === false) {
     613
     614
     615                throw new Exception('database replace operation failed');
     616            }
     617            $replace = new SURFL_BR_REPLACE_ENGINE();
     618            $result = $replace->replace_database($filepath, $state);
     619            if ($result === false) {
     620                throw new Exception('Database URL replace failed.');
     621            }
     622            if (!$result['done']) {
     623                wp_send_json_success([
     624                    'message' => $result['message'] ?? 'Searching and replacing URLs...',
     625                    'type' => $type,
     626                    'status' => 'replace_ongoing',
     627                    'state' => $result,
     628                ]);
     629            } else {
     630
     631                $is_last_uploaded_file && $this->cleanup_surflink_upload_dir();
     632
     633                wp_send_json_success([
     634                    'message' => "Database URLs restored and replaced successfully.",
     635                    'type' => $type,
     636                    'status' => 'success',
     637                    'state' => $result,
     638                ]);
     639            }
     640        } catch (Exception $e) {
     641
     642            $is_last_uploaded_file && $this->cleanup_surflink_upload_dir();
     643            wp_send_json_error(['message' => $e->getMessage(), 'type' => $type]);
     644        }
     645    }
    555646
    556647    public function ajax_upload_backup_file_chunk()
     
    782873        $backup_dir = $this->backup_dir;
    783874        $file_path  = $backup_dir . '/' . $subdir . '/' . $file;
     875        $db_json    = $backup_dir . '/' . $subdir . '/db-info.json';
    784876        $file_name = str_replace('.zip', '', $file);
    785877
     
    799891        // Add the original backup file into the surfl_backup.zip
    800892        $zip->addFile($file_path, basename($file_path));
     893
     894        // Add the db-info.json file into the surfl_backup.zip
     895        if (file_exists($db_json)) {
     896            $zip->addFile($db_json, basename($db_json));
     897        }
    801898        $zip->close();
    802899
     
    12091306                // status is 'completed'
    12101307
    1211      
     1308
    12121309                $msg = 'Backup completed for database.';
    12131310                $status = 'success'; // Set status to success if no Google Drive upload
  • surflink/trunk/includes/class-surfl-loginhider.php

    r3437024 r3440991  
    159159     * Blocks access to wp-login.php and redirects to 404 or Home.
    160160     */
    161     public function block_default_login()
    162     {
    163         // Don't block if we are actually on the custom login page (handled by rewrite)
    164         if (get_query_var('surfl_custom_login')) {
    165             return;
    166         }
    167 
    168         $pagenow = basename($_SERVER['SCRIPT_NAME']);
    169 
    170         // Check if attempting to access wp-login.php directly
    171         if ($pagenow === 'wp-login.php' && $_SERVER['REQUEST_METHOD'] === 'GET') {
    172 
    173             // Allow logout and post-password actions
    174             if (isset($_GET['action']) && in_array($_GET['action'], ['logout', 'postpass'])) {
    175                 return;
    176             }
    177 
    178             wp_safe_redirect(home_url());
    179             exit();
    180         }
    181     }
     161public function block_default_login()
     162{
     163    // Allow custom login page
     164    if (get_query_var('surfl_custom_login')) {
     165        return;
     166    }
     167
     168    if (basename($_SERVER['SCRIPT_NAME']) !== 'wp-login.php') {
     169        return;
     170    }
     171
     172
     173    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
     174         if (
     175        ! isset($_POST['surfl_login'])
     176    ) {
     177        wp_die(__('Forbidden', 'surfl'), 403);
     178    }
     179
     180    return;
     181    }
     182
     183    $action = isset($_GET['action'])
     184        ? sanitize_text_field($_GET['action'])
     185        : '';
     186
     187    // Allow core WordPress actions
     188    $allowed_actions = [
     189        'logout',
     190        'postpass',
     191        'lostpassword',
     192        'resetpass',
     193        'rp',
     194    ];
     195
     196    // Allow password reset links with key
     197    if ($action && in_array($action, $allowed_actions, true)) {
     198        return;
     199    }
     200
     201    if (isset($_GET['key'])) {
     202        return;
     203    }
     204
     205    // Block direct access
     206    wp_safe_redirect(home_url('/404'), 302);
     207    exit;
     208}
     209
    182210    /**
    183211     * Adds rewrite rules for the custom login URL
  • surflink/trunk/includes/class-surfl-restore-db.php

    r3429296 r3440991  
    99
    1010    private $start_time;
     11
     12    private $placeholder = '{temp_prefix_surflink}';
    1113
    1214    public function __construct() {
     
    5052
    5153    public function restore_database_stream( $filename, $state = [] ) {
     54        global $wpdb;
     55        $fp = null;
     56        $temp_path = WP_CONTENT_DIR . '/surflink/backup/restore/temp';
     57        $target_prefix = $wpdb->prefix;
     58        // Disable query monitor during restore
     59        add_filter( 'qm/enable', '__return_false', PHP_INT_MAX );
     60        try {
     61            if ( empty( $filename ) || !file_exists( $filename ) ) {
     62                $this->log( 'Backup file not found or empty filename: ' . $filename );
     63                throw new Exception('Backup file does not exist');
     64            }
     65            $wpdb->suppress_errors( false );
     66            if ( $wpdb->query( 'SET AUTOCOMMIT=0' ) === false ) {
     67                $this->log( 'Failed SET AUTOCOMMIT=0: ' . $wpdb->last_error );
     68                throw new Exception('Failed to set AUTOCOMMIT=0: ' . $wpdb->last_error);
     69            }
     70            if ( $wpdb->query( 'SET FOREIGN_KEY_CHECKS=0' ) === false ) {
     71                $this->log( 'Failed SET FOREIGN_KEY_CHECKS=0: ' . $wpdb->last_error );
     72                throw new Exception('Failed to disable FK checks: ' . $wpdb->last_error);
     73            }
     74            if ( $wpdb->query( 'SET sql_notes=0' ) === false ) {
     75                $this->log( 'Failed SET sql_notes=0: ' . $wpdb->last_error );
     76                throw new Exception('Failed to disable sql_notes: ' . $wpdb->last_error);
     77            }
     78            if ( $wpdb->query( 'START TRANSACTION' ) === false ) {
     79                $this->log( 'Failed START TRANSACTION: ' . $wpdb->last_error );
     80                throw new Exception('Failed to start transaction: ' . $wpdb->last_error);
     81            }
     82            $this->log( 'Database session settings and transaction started successfully.', 'success' );
     83            if ( empty( $state ) ) {
     84                $unique_slug = 'db-' . time();
     85                $temp_dir = $temp_path . '-' . $unique_slug;
     86                if ( !SURFL_FS_Helper::mkdir( $temp_dir ) ) {
     87                    $this->log( 'Failed to create temporary directory: ' . $temp_dir );
     88                    throw new Exception('Failed to create temporary directory');
     89                }
     90                $free_space = @disk_free_space( WP_CONTENT_DIR );
     91                if ( $free_space !== false && $free_space < filesize( $filename ) * 2 ) {
     92                    $this->log( "❌ Not enough free space to extract zip." );
     93                    throw new Exception("❌ Not enough free space to extract zip.");
     94                }
     95                try {
     96                    SURFL_FS_Helper::catch_disc_quota_error();
     97                    $zip = new ZipArchive();
     98                    if ( $zip->open( $filename ) !== true ) {
     99                        throw new Exception('Failed to open backup file');
     100                    }
     101                    if ( !$zip->extractTo( $temp_dir ) ) {
     102                        $zip->close();
     103                        throw new Exception('Failed to extract ZIP file');
     104                    }
     105                    $zip->close();
     106                } finally {
     107                    restore_error_handler();
     108                }
     109                $state = [
     110                    'done'                  => false,
     111                    'unique_slug'           => $unique_slug,
     112                    'position'              => 0,
     113                    'current_table_name'    => '',
     114                    'rows_done'             => 0,
     115                    'table_prefix_differed' => 0,
     116                    'site_url_differed'     => 0,
     117                    'home_url_differed'     => 0,
     118                    'old_site_url'          => '',
     119                    'old_home_url'          => '',
     120                    'current_site_url'      => SURFL_SITE_URL,
     121                    'current_home_url'      => SURFL_HOME_URL,
     122                    'old_table_prefix'      => '',
     123                    'current_table_prefix'  => $target_prefix,
     124                ];
     125            }
     126            $temp_dir = $temp_path . '-' . $state['unique_slug'];
     127            $position = intval( $state['position'] );
     128            $current_table_name = $state['current_table_name'];
     129            $rows_done = intval( $state['rows_done'] );
     130            $sql_files = glob( $temp_dir . '/*.sql' );
     131            if ( empty( $sql_files ) ) {
     132                throw new Exception('No SQL file found in backup');
     133            }
     134            $sql_file = $sql_files[0];
     135            $this->log( 'SQL file for import: ' . $sql_file, 'success' );
     136            $fp = fopen( $sql_file, 'r' );
     137            if ( !$fp ) {
     138                throw new Exception('Failed to open SQL file for reading');
     139            }
     140            $site_url_differed = absint( $state['site_url_differed'] );
     141            $table_prefix_differed = absint( $state['table_prefix_differed'] );
     142            $home_url_differed = absint( $state['home_url_differed'] );
     143            if ( $position === 0 ) {
     144                // --- MODIFIED: Read from db-info.json instead of parsing SQL header ---
     145                $json_info_file = $temp_dir . '/db-info.json';
     146                if ( !file_exists( $json_info_file ) ) {
     147                    fclose( $fp );
     148                    throw new Exception('Invalid backup: db-info.json not found in the backup archive.');
     149                }
     150                $db_info = json_decode( file_get_contents( $json_info_file ), true );
     151                if ( json_last_error() !== JSON_ERROR_NONE || empty( $db_info ) ) {
     152                    fclose( $fp );
     153                    throw new Exception('Invalid backup: db-info.json is corrupt or empty.');
     154                }
     155                // 1. Site URL
     156                if ( empty( $db_info['site_url'] ) ) {
     157                    fclose( $fp );
     158                    throw new Exception('Invalid backup: Missing site_url in db-info.json');
     159                }
     160                $backup_site_url = untrailingslashit( str_replace( '\\/', '/', $db_info['site_url'] ) );
     161                $current_site_url = untrailingslashit( site_url() );
     162                // 2. Home URL
     163                if ( empty( $db_info['home_url'] ) ) {
     164                    fclose( $fp );
     165                    throw new Exception('Invalid backup: Missing home_url in db-info.json');
     166                }
     167                $backup_home_url = untrailingslashit( str_replace( '\\/', '/', $db_info['home_url'] ) );
     168                $current_home_url = untrailingslashit( home_url() );
     169                // 3. Table Prefix
     170                if ( empty( $db_info['table_prefix'] ) ) {
     171                    fclose( $fp );
     172                    throw new Exception('Invalid backup: Missing table_prefix in db-info.json');
     173                }
     174                $backup_prefix = $db_info['table_prefix'];
     175                $current_prefix = $wpdb->base_prefix;
     176                // --- Comparisons ---
     177                if ( $backup_site_url !== $current_site_url ) {
     178                    $site_url_differed = 1;
     179                }
     180                if ( $backup_home_url !== $current_home_url ) {
     181                    $home_url_differed = 1;
     182                }
     183                if ( $backup_prefix !== $current_prefix ) {
     184                    $table_prefix_differed = 1;
     185                }
     186                // Store in State
     187                $state['old_site_url'] = $backup_site_url;
     188                $state['old_home_url'] = $backup_home_url;
     189                $state['old_table_prefix'] = $backup_prefix;
     190            }
     191            if ( $position > 0 ) {
     192                fseek( $fp, $position );
     193            }
     194            $current_query = '';
     195            $current_delimiter = ';';
     196            $line_num = 0;
     197            $query_count = 0;
     198            $row_count = 0;
     199            $table_started = false;
     200            $next_position = -1;
     201            $is_sensitive_table = false;
     202            $chunk_limit_reached = false;
     203            while ( true ) {
     204                $line_start_pos = ftell( $fp );
     205                $line = fgets( $fp );
     206                if ( $line === false ) {
     207                    // End of file
     208                    break;
     209                }
     210                $line_num++;
     211                $trim_line = trim( $line );
     212                if ( stripos( $trim_line, 'DROP TABLE IF EXISTS' ) === 0 ) {
     213                    // Extract the potential new table name first
     214                    preg_match( '/DROP TABLE IF EXISTS `?([^`\\s]+)`?/i', $trim_line, $matches );
     215                    $raw_table_name = ( !empty( $matches[1] ) ? $matches[1] : '' );
     216                    if ( $raw_table_name == $this->placeholder . 'users' || $raw_table_name == $this->placeholder . 'usermeta' || $raw_table_name == $this->placeholder . 'options' ) {
     217                        $is_sensitive_table = true;
     218                    }
     219                    $new_table_name = ( $is_sensitive_table ? $raw_table_name : str_replace( $this->placeholder, $target_prefix, $raw_table_name ) );
     220                    if ( $table_started ) {
     221                        $next_position = $line_start_pos;
     222                        break;
     223                    }
     224                    $table_started = true;
     225                    $current_table_name = $new_table_name;
     226                    // Update current_table_name to the newly found table
     227                    $rows_done = 0;
     228                    if ( !empty( $current_table_name ) ) {
     229                        $this->log( 'Processing table: ' . $current_table_name, 'success' );
     230                    }
     231                }
     232                if ( preg_match( '/^\\/\\*!\\d+\\s+(.*?)\\*\\/;?\\s*$/s', $trim_line, $m ) ) {
     233                    $stmt = trim( $m[1], " ;\r\n\t" );
     234                    if ( $stmt !== '' ) {
     235                        if ( $wpdb->query( $stmt ) === false ) {
     236                            throw new Exception('Versioned comment query failed: ' . $wpdb->last_error);
     237                        }
     238                        $query_count++;
     239                    }
     240                    continue;
     241                }
     242                $current_query .= $line;
     243                if ( strlen( $current_query ) > 5 * 1024 * 1024 ) {
     244                    // 5 MB
     245                    $this->log( 'Query too large, skipping to prevent memory exhaustion' );
     246                    throw new Exception('Oversized query detected');
     247                }
     248                if ( stripos( $trim_line, 'DELIMITER ' ) === 0 ) {
     249                    $parts = preg_split( '/\\s+/', trim( substr( $trim_line, 9 ) ), 2 );
     250                    if ( !empty( $parts[0] ) ) {
     251                        $current_delimiter = $parts[0];
     252                        $current_query = '';
     253                        continue;
     254                    }
     255                }
     256                // Robust parser loop inspired by split_sql_file
     257                $in_string = false;
     258                $in_backtick = false;
     259                // Added for backtick quoted identifiers
     260                $in_multiline_comment = false;
     261                $query_buffer = '';
     262                $i = 0;
     263                $len = strlen( $current_query );
     264                $delim_len = strlen( $current_delimiter );
     265                while ( $i < $len ) {
     266                    $char = $current_query[$i];
     267                    $next_char = ( $i + 1 < $len ? $current_query[$i + 1] : '' );
     268                    $next_next_char = ( $i + 2 < $len ? $current_query[$i + 2] : '' );
     269                    // Handle multiline comments
     270                    if ( !$in_string && !$in_backtick ) {
     271                        if ( !$in_multiline_comment && $char == '/' && $next_char == '*' ) {
     272                            $in_multiline_comment = true;
     273                            $i += 2;
     274                            continue;
     275                        }
     276                        if ( $in_multiline_comment && $char == '*' && $next_char == '/' ) {
     277                            $in_multiline_comment = false;
     278                            $i += 2;
     279                            continue;
     280                        }
     281                        if ( $in_multiline_comment ) {
     282                            $i++;
     283                            continue;
     284                        }
     285                    }
     286                    // Handle strings and backticks
     287                    if ( $in_string ) {
     288                        if ( $char === $in_string && $current_query[$i - 1] !== '\\' ) {
     289                            // Check for unescaped quote
     290                            $in_string = false;
     291                        }
     292                    } elseif ( $in_backtick ) {
     293                        if ( $char === '`' && $current_query[$i - 1] !== '\\' ) {
     294                            // Check for unescaped backtick
     295                            $in_backtick = false;
     296                        }
     297                    } else {
     298                        if ( $char === "'" || $char === '"' ) {
     299                            $in_string = $char;
     300                        } elseif ( $char === '`' ) {
     301                            $in_backtick = true;
     302                        }
     303                    }
     304                    // Check for DELIMITER command
     305                    if ( !$in_string && !$in_backtick && !$in_multiline_comment && strtoupper( substr( $current_query, $i, 9 ) ) === 'DELIMITER' ) {
     306                        $i += 9;
     307                        $new_delim = '';
     308                        while ( $i < $len && ($current_query[$i] === ' ' || $current_query[$i] === "\t") ) {
     309                            $i++;
     310                        }
     311                        while ( $i < $len && $current_query[$i] !== "\n" && $current_query[$i] !== "\r" ) {
     312                            $new_delim .= $current_query[$i];
     313                            $i++;
     314                        }
     315                        $new_delim = trim( $new_delim );
     316                        if ( !empty( $new_delim ) ) {
     317                            if ( !empty( trim( $query_buffer ) ) ) {
     318                                $q = ( $is_sensitive_table ? trim( $query_buffer ) : str_replace( $this->placeholder, $target_prefix, trim( $query_buffer ) ) );
     319                                if ( $wpdb->query( $q ) === false ) {
     320                                    $this->log( 'Query failed (line ' . $line_num . '): ' . $wpdb->last_error . ' | Snippet: ' . substr( $q, 0, 500 ) );
     321                                    throw new Exception('Query failed: ' . $wpdb->last_error);
     322                                }
     323                                $query_count++;
     324                            }
     325                            $query_buffer = '';
     326                            $current_delimiter = $new_delim;
     327                            $delim_len = strlen( $current_delimiter );
     328                            // Update delimiter length
     329                        }
     330                        while ( $i < $len && ($current_query[$i] === "\n" || $current_query[$i] === "\r") ) {
     331                            $i++;
     332                        }
     333                        continue;
     334                    }
     335                    // Check for current delimiter
     336                    if ( !$in_string && !$in_backtick && !$in_multiline_comment && substr( $current_query, $i, $delim_len ) === $current_delimiter ) {
     337                        $query_to_run = trim( $query_buffer );
     338                        if ( !empty( $query_to_run ) ) {
     339                            $query_to_run = ( $is_sensitive_table ? trim( $query_to_run ) : str_replace( $this->placeholder, $target_prefix, $query_to_run ) );
     340                            if ( $wpdb->query( $query_to_run ) === false ) {
     341                                $this->log( 'Query failed (line ' . $line_num . '): ' . $wpdb->last_error . ' | Snippet: ' . substr( $query_to_run, 0, 500 ) );
     342                                throw new Exception('Query failed: ' . $wpdb->last_error);
     343                            }
     344                            $query_count++;
     345                            if ( stripos( ltrim( $query_to_run ), 'INSERT INTO' ) === 0 ) {
     346                                $row_count++;
     347                                // Break after 1000 rows
     348                                if ( $row_count >= 1000 || $this->time_exceeded() ) {
     349                                    $next_position = ftell( $fp );
     350                                    // Save current file position
     351                                    $chunk_limit_reached = true;
     352                                    break 2;
     353                                    // break out of while + parsing loop
     354                                }
     355                            }
     356                        }
     357                        $query_buffer = '';
     358                        $i += $delim_len;
     359                        continue;
     360                        // Skip adding delimiter to buffer
     361                    }
     362                    $query_buffer .= $char;
     363                    $i++;
     364                }
     365                // After processing the current_query, move any remaining buffer to the next chunk
     366                $current_query = $query_buffer;
     367                $query_buffer = '';
     368                // Clear for next iteration
     369            }
     370            // Process any final query remaining in the buffer after EOF, but only if chunk limit was not reached
     371            if ( !$chunk_limit_reached && !empty( trim( $current_query ) ) ) {
     372                $cq = ( $is_sensitive_table ? trim( $current_query ) : str_replace( $this->placeholder, $target_prefix, trim( $current_query ) ) );
     373                if ( $wpdb->query( $cq ) === false ) {
     374                    $this->log( 'Final query failed: ' . $wpdb->last_error . ' | Snippet: ' . substr( $cq, 0, 500 ) );
     375                    throw new Exception('Final query failed: ' . $wpdb->last_error);
     376                }
     377                $query_count++;
     378            }
     379            fclose( $fp );
     380            if ( $wpdb->query( 'COMMIT' ) === false ) {
     381                throw new Exception('Failed to commit transaction: ' . $wpdb->last_error);
     382            }
     383            if ( $wpdb->query( 'SET FOREIGN_KEY_CHECKS=1' ) === false ) {
     384                $this->log( 'Failed SET FOREIGN_KEY_CHECKS=1: ' . $wpdb->last_error );
     385            }
     386            if ( $wpdb->query( 'SET sql_notes=1' ) === false ) {
     387                $this->log( 'Failed SET sql_notes=1: ' . $wpdb->last_error );
     388            }
     389            if ( $wpdb->query( 'SET AUTOCOMMIT=1' ) === false ) {
     390                $this->log( 'Failed SET AUTOCOMMIT=1: ' . $wpdb->last_error );
     391            }
     392            $latest_state = [
     393                'done'                  => false,
     394                'unique_slug'           => $state['unique_slug'],
     395                'position'              => $next_position,
     396                'current_table_name'    => $current_table_name,
     397                'rows_done'             => $row_count + $rows_done,
     398                'current_site_url'      => $state['current_site_url'],
     399                'current_home_url'      => $state['current_home_url'],
     400                'current_table_prefix'  => $state['current_table_prefix'],
     401                'old_site_url'          => $state['old_site_url'],
     402                'old_home_url'          => $state['old_home_url'],
     403                'old_table_prefix'      => $state['old_table_prefix'],
     404                'site_url_differed'     => $site_url_differed,
     405                'table_prefix_differed' => $table_prefix_differed,
     406                'home_url_differed'     => $home_url_differed,
     407            ];
     408            if ( $next_position !== -1 ) {
     409                return $latest_state;
     410            } else {
     411                if ( $state['table_prefix_differed'] == 1 ) {
     412                    error_log( "Table prefix differed: {$state['old_table_prefix']} -> {$state['current_table_prefix']}" );
     413                    $old_prefix = $state['old_table_prefix'];
     414                    $new_prefix = $state['current_table_prefix'];
     415                    $prefix_length_offset = strlen( $old_prefix ) + 1;
     416                    // 1. UPDATE USERMETA ({temp}usermeta)
     417                    // Targets: wp_capabilities, wp_user_level, etc.
     418                    // Note: Usermeta is still in the temporary table named "{temp}usermeta"
     419                    $temp_usermeta_table = $this->placeholder . 'usermeta';
     420                    $wpdb->query( $wpdb->prepare(
     421                        "UPDATE `{$temp_usermeta_table}` \r\n                         SET meta_key = CONCAT(%s, SUBSTRING(meta_key, %d)) \r\n                         WHERE meta_key LIKE %s",
     422                        $new_prefix,
     423                        $prefix_length_offset,
     424                        $wpdb->esc_like( $old_prefix ) . '%'
     425                    ) );
     426                    // 2. UPDATE OPTIONS (temp options table)
     427                    $temp_options_table = $this->placeholder . 'options';
     428                    $wpdb->query( $wpdb->prepare(
     429                        "UPDATE `{$temp_options_table}` \r\n                         SET option_name = CONCAT(%s, SUBSTRING(option_name, %d)) \r\n                         WHERE option_name LIKE %s",
     430                        $new_prefix,
     431                        $prefix_length_offset,
     432                        $wpdb->esc_like( $old_prefix ) . '%'
     433                    ) );
     434                }
     435                if ( $state['site_url_differed'] != 1 && $state['home_url_differed'] != 1 ) {
     436                    error_log( "state['site_url_differed'] != 1 && state['home_url_differed'] != 1" );
     437                    //DROP and RENAME
     438                    $temp_prefix = $this->placeholder;
     439                    $target_prefix = $state['current_table_prefix'];
     440                    $wpdb->query( "DROP TABLE IF EXISTS `{$target_prefix}options`" );
     441                    $wpdb->query( "RENAME TABLE `{$temp_prefix}options` TO `{$target_prefix}options`" );
     442                    $wpdb->query( "DROP TABLE IF EXISTS `{$target_prefix}users`" );
     443                    $wpdb->query( "RENAME TABLE `{$temp_prefix}users` TO `{$target_prefix}users`" );
     444                    $wpdb->query( "DROP TABLE IF EXISTS `{$target_prefix}usermeta`" );
     445                    $wpdb->query( "RENAME TABLE `{$temp_prefix}usermeta` TO `{$target_prefix}usermeta`" );
     446                } else {
     447                    error_log( "state['site_url_differed'] == 1 || state['home_url_differed'] == 1" );
     448                    // 2. UPDATE OPTIONS (temp options table)
     449                    $temp_options_table = $this->placeholder . 'options';
     450                    $wpdb->query( $wpdb->prepare( "UPDATE `{$temp_options_table}`\r\n                                                SET option_value = %s\r\n                                                WHERE option_name = 'siteurl'", $state['current_site_url'] ) );
     451                    $wpdb->query( $wpdb->prepare( "UPDATE `{$temp_options_table}`\r\n                                                SET option_value = %s\r\n                                                WHERE option_name = 'home'", $state['current_home_url'] ) );
     452                }
     453            }
     454            $this->cleanup_temp_dir( $temp_dir );
     455            $latest_state['done'] = true;
     456            return $latest_state;
     457            // Signal completion
     458        } catch ( Exception $e ) {
     459            $this->log( $e->getMessage() );
     460            $wpdb->query( 'ROLLBACK' );
     461            $wpdb->query( 'SET FOREIGN_KEY_CHECKS=1' );
     462            $wpdb->query( 'SET sql_notes=1' );
     463            $wpdb->query( 'SET AUTOCOMMIT=1' );
     464            $this->log( 'Database transaction rolled back and settings reset due to error.' );
     465            if ( isset( $temp_dir ) && is_dir( $temp_dir ) ) {
     466                if ( is_resource( $fp ) ) {
     467                    fclose( $fp );
     468                }
     469                $this->cleanup_temp_dir( $temp_dir );
     470            }
     471            return false;
     472        } finally {
     473            // Always run: restore Query Monitor
     474            remove_filter( 'qm/enable', '__return_false', PHP_INT_MAX );
     475        }
     476    }
     477
     478    public function old_restore_database_stream( $filename, $state = [] ) {
    52479        global $wpdb;
    53480        $fp = null;
     
    112539                    'site_url'           => SURFL_SITE_URL,
    113540                    'home_url'           => SURFL_HOME_URL,
     541                    'site_url_differed'  => '0',
     542                    'home_url_differed'  => '0',
    114543                ];
    115544            }
     
    372801                'site_url'           => $state['site_url'],
    373802                'home_url'           => $state['home_url'],
     803                'site_url_differed'  => $state['site_url_differed'],
     804                'home_url_differed'  => $state['home_url_differed'],
    374805            ];
    375806            if ( $next_position !== -1 ) {
  • surflink/trunk/readme.txt

    r3437024 r3440991  
    66**Requires PHP:** 7.4   
    77**Tested up to:** 6.9 
    8 **Stable tag:** 2.3.8
     8**Stable tag:** 2.3.9
    99**License:** GPLv3 or later 
    1010**License URI:** https://opensource.org/licenses/GPL-3.0 
     
    115115Yes! Database operations are powerful. Entering a wrong search or replace string could break your site. **Always** perform a Backup (using Module 3) before running a Search & Replace operation. Use the "Dry Run" feature first to verify what will be changed.
    116116
    117 **Why can’t I restore the database backup on a different domain or table prefix?**
    118 At the moment, SurfLink database restore only works on the same domain where the backup was created.This is a current technical limitation, not a permanent restriction. Support for restoring backups on a different domain or prefix(for example: staging, localhost, or a new production domain) is planned for a future release.
    119 Current recommendations:
    120 1. Restore the database on the original domain and table prefix.
    121 2. Use a site migration plugin if you are moving to a new domain
     117**Can I restore the database backup on a different domain?**
     118Yes, SurfLink supports restoring datbase on a different domain (Multisite not supported).
     119Recommendations:
     1201.Upload the backup file from your old site.               
     1212.Some plugins may be deactivated after restore, especially login security plugins. Reactivate them if needed.
     1223.The system automatically handles differences in database table prefixes.
     1234.If a plugin doesn’t work correctly after restore, try deactivating it and reactivating it.
     1245.Log in with the credentials from the old site.
     125
     126**Can I restore the database backup on a different table prefix?**
     127Yes, the system automatically handles differences in database table prefixes.
    122128
    123129**How does the Login Hider work?**
     
    141147
    142148== Changelog ==
     149
     150= 2.3.9 =
     151* Fixed: Critical bug fixed in loginhider.
     152* New Feature: SurfLink now support cross domain restoration.
    143153
    144154= 2.3.8 =
  • surflink/trunk/surf-link.php

    r3437024 r3440991  
    77 * Author: SurfLab
    88 * Author URI: https://surflabtech.com
    9  * Version: 2.3.8
     9 * Version: 2.3.9
    1010 * Text Domain: surflink
    1111 * License: GPL-3.0-or-later
     
    6767    }
    6868    if ( !defined( 'SURFL_VERSION' ) ) {
    69         define( 'SURFL_VERSION', '2.3.8' );
     69        define( 'SURFL_VERSION', '2.3.9' );
    7070    }
    7171    if ( !defined( 'SURFL_PLUGIN' ) ) {
     
    9999        }
    100100        if ( !defined( 'SURFL_VERSION' ) ) {
    101             define( 'SURFL_VERSION', '2.3.8' );
     101            define( 'SURFL_VERSION', '2.3.9' );
    102102        }
    103103        if ( !defined( 'SURFL_SITE_URL' ) ) {
  • surflink/trunk/templates/login-template.php

    r3437024 r3440991  
    3232            <?php if (!$is_banned): ?>
    3333                <form name="loginform" id="loginform" action="<?php echo esc_url(home_url("/{$this->custom_login_slug}")); ?>" method="post">
    34                     <p>
     34                  <input type="hidden" name="surfl_login" value="1">
     35
     36
     37               
     38               
     39                <p>
    3540                        <label for="user_login"><?php esc_html_e('Username or Email', 'surflink'); ?></label>
    3641                        <input type="text" name="log" id="user_login" class="input" value="" size="20" autocapitalize="off">
  • surflink/trunk/templates/surfl-restore-backup-html.php

    r3429296 r3440991  
    1414           
    1515
    16             <p class="surfl-flex-center" style="margin-top: 1rem;margin-bottom: 1rem;"> <span class="dashicons dashicons-warning"></span> Database restore works only on the original domain and table prefix(cross-domain support coming soon).</p>
    17         <?php require_once SURFL_PATH . 'templates/surfl-backup-table.php' ?>
     16      <?php require_once SURFL_PATH . 'templates/surfl-backup-table.php' ?>
     17  <div class="surfl-instructions-wrapper" style="margin-top: 15px;">
     18                <span class="surfl-toggle-help">
     19                    <span class="dashicons dashicons-info-outline"></span> <span class="surfl-toggle-text-span">How to migrate database (cross domain database restoration) ?</span>
     20                </span>
     21               
     22                <!-- NEW: HIDDEN INSTRUCTIONS BOX -->
     23                <div id="surfl-csv-instructions" style="display:none;">
     24           
     25                        <ol class="surfl-flex-col" style="gap:3px;">
     26         <li>It is not compitable with multisite, not yet.</li>             
     27     <li>Upload the backup file from your old site.</li>               
     28 <li>Some plugins may be deactivated after restore, especially login security plugins. Reactivate them if needed.</li>
     29            <li>The system automatically handles differences in database table prefixes.</li>
     30            <li>If a plugin doesn’t work correctly after restore, try deactivating it and reactivating it.</li>
     31            <li>Log in with the credentials from the old site.</li>
    1832
     33
     34                    </ol>
     35                     
     36                </div>
     37            </div> 
    1938
    2039        <!-- db file upload  -->
Note: See TracChangeset for help on using the changeset viewer.