Plugin Directory

Changeset 3397669


Ignore:
Timestamp:
11/18/2025 12:10:58 AM (4 months ago)
Author:
ejointjp
Message:

v1.1.0

Location:
tiny-backup
Files:
22 added
8 edited

Legend:

Unmodified
Added
Removed
  • tiny-backup/trunk/includes/class-tnbu-admin-interface.php

    r3397190 r3397669  
    3939                );
    4040            }
    41             // インラインスクリプトでJavaScript変数を設定
    42             $tnbu_files_nonce = wp_create_nonce( 'tnbu_files_ops' );
    43             $tnbu_ajax_nonce  = wp_create_nonce( 'tnbu_start_backup' );
    44             $opts             = TNBU_Core::get_options();
    45             $preset           = isset( $opts['selected_items'] ) && is_array( $opts['selected_items'] ) ? array_values( array_map( 'strval', $opts['selected_items'] ) ) : array();
    46             $inline_script    = sprintf(
    47                 'window.tnbuFilesNonce = %s;' . "\n" .
    48                 'window.tnbuAjaxNonce = %s;' . "\n" .
    49                 'window.ajaxurl = %s;' . "\n" .
    50                 'window.tnbuOptionKey = %s;' . "\n" .
    51                 'window.tnbuPresetItems = %s;',
    52                 wp_json_encode( $tnbu_files_nonce ),
    53                 wp_json_encode( $tnbu_ajax_nonce ),
    54                 wp_json_encode( admin_url( 'admin-ajax.php' ) ),
    55                 wp_json_encode( TNBU_Core::OPTION_KEY ),
    56                 wp_json_encode( $preset )
    57             );
    58             wp_add_inline_script( 'tnbu-admin', $inline_script, 'before' );
    5941        }
    6042    }
     
    162144     */
    163145    public static function field_files_selection() {
     146        $tnbu_files_nonce = wp_create_nonce( 'tnbu_files_ops' );
     147        $tnbu_ajax_nonce  = wp_create_nonce( 'tnbu_start_backup' );
    164148        ?>
    165149        <div id="tnbu-files-ui" class="tnbu-file-related">
     
    168152            </div>
    169153        </div>
     154       
     155        <script>
     156        // JavaScript変数をグローバルに設定
     157        window.tnbuFilesNonce = '<?php echo esc_js( $tnbu_files_nonce ); ?>';
     158        window.tnbuAjaxNonce = '<?php echo esc_js( $tnbu_ajax_nonce ); ?>';
     159        window.ajaxurl = '<?php echo esc_js( admin_url( 'admin-ajax.php' ) ); ?>';
     160        window.tnbuOptionKey = '<?php echo esc_js( TNBU_Core::OPTION_KEY ); ?>';
     161        window.tnbuPresetItems =
     162        <?php
     163            $opts   = TNBU_Core::get_options();
     164            $preset = isset( $opts['selected_items'] ) && is_array( $opts['selected_items'] ) ? array_values( array_map( 'strval', $opts['selected_items'] ) ) : array();
     165            echo wp_json_encode( $preset );
     166        ?>
     167        ;
     168        </script>
    170169        <?php
    171170    }
     
    225224            </p>
    226225            <?php
    227                 $save_dir = TNBU_Core::get_backup_dir();
     226                $opts     = wp_parse_args( get_option( TNBU_Core::OPTION_KEY, array() ), TNBU_Core::defaults() );
     227                $save_dir = trailingslashit( WP_CONTENT_DIR ) . ltrim( $opts['target_dir'], '/' );
    228228            ?>
    229229            <p class="description"><?php echo esc_html( __( 'Backup destination: ', 'tiny-backup' ) ); ?><code><?php echo esc_html( $save_dir ); ?></code></p>
     
    253253     */
    254254    public static function render_backup_list() {
    255         $backup_dir = TNBU_Core::get_backup_dir();
     255        $opts       = wp_parse_args( get_option( TNBU_Core::OPTION_KEY, array() ), TNBU_Core::defaults() );
     256        $backup_dir = trailingslashit( WP_CONTENT_DIR ) . ltrim( $opts['target_dir'], '/' );
    256257
    257258        if ( ! is_dir( $backup_dir ) ) {
  • tiny-backup/trunk/includes/class-tnbu-ajax-handler.php

    r3397190 r3397669  
    177177        }
    178178
    179         $files_text = implode( ', ', $backup_files );
    180         /* translators: %s: backup file names */
     179        $dump_method = get_transient( 'tnbu_last_dump_method' );
     180        $method_text = $dump_method ? " ({$dump_method})" : '';
     181        $files_text  = implode( ', ', $backup_files );
     182        /* translators: 1: backup file names, 2: dump method suffix e.g. (mysqldump) */
    181183        set_transient(
    182184            'tnbu_flash_' . get_current_user_id(),
    183185            array(
    184186                'type' => 'success',
    185                 /* translators: %s: backup file names */
    186                 'text' => sprintf( __( 'Backup completed: %s', 'tiny-backup' ), $files_text ),
     187                /* translators: 1: backup file names, 2: dump method suffix e.g. (mysqldump) */
     188                'text' => sprintf( __( 'Backup completed: %1$s%2$s', 'tiny-backup' ), $files_text, $method_text ),
    187189            ),
    188190            60
     
    306308        }
    307309
    308         // nonce検証(入力処理の前に実行)
    309         if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['tnbu_download_nonce'] ?? '' ) ), 'tnbu_download_backup' ) ) {
    310             wp_die( esc_html__( 'Security check failed.', 'tiny-backup' ) );
    311         }
    312 
    313310        $filename = sanitize_text_field( wp_unslash( $_GET['file'] ?? '' ) );
    314311        if ( empty( $filename ) ) {
     
    321318        }
    322319
    323         $backup_dir = TNBU_Core::get_backup_dir();
    324         $file_path  = trailingslashit( $backup_dir ) . $filename;
     320        // nonce検証
     321        if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['tnbu_download_nonce'] ?? '' ) ), 'tnbu_download_backup' ) ) {
     322            wp_die( esc_html__( 'Security check failed.', 'tiny-backup' ) );
     323        }
     324
     325        $opts      = wp_parse_args( get_option( TNBU_Core::OPTION_KEY, array() ), TNBU_Core::defaults() );
     326        $file_path = trailingslashit( trailingslashit( WP_CONTENT_DIR ) . ltrim( $opts['target_dir'], '/' ) ) . $filename;
    325327
    326328        // ファイルの存在確認
     
    331333        // ファイルがバックアップディレクトリ内にあることを確認(ディレクトリトラバーサル攻撃対策)
    332334        $real_file_path  = realpath( $file_path );
    333         $real_backup_dir = realpath( $backup_dir );
     335        $real_backup_dir = realpath( trailingslashit( WP_CONTENT_DIR ) . ltrim( $opts['target_dir'], '/' ) );
    334336        if ( strpos( $real_file_path, $real_backup_dir ) !== 0 ) {
    335337            wp_die( esc_html__( 'Invalid file path.', 'tiny-backup' ) );
     
    391393            wp_die( esc_html__( 'Security check failed.', 'tiny-backup' ) );
    392394        }
    393         $files = isset( $_POST['files'] ) && is_array( $_POST['files'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['files'] ) ) : array();
    394         $base  = trailingslashit( TNBU_Core::get_backup_dir() );
     395        $files   = isset( $_POST['files'] ) && is_array( $_POST['files'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['files'] ) ) : array();
     396        $opts    = wp_parse_args( get_option( TNBU_Core::OPTION_KEY, array() ), TNBU_Core::defaults() );
     397        $base    = trailingslashit( trailingslashit( WP_CONTENT_DIR ) . ltrim( $opts['target_dir'], '/' ) );
    395398        $deleted = 0;
    396399        foreach ( $files as $filename ) {
  • tiny-backup/trunk/includes/class-tnbu-core.php

    r3397190 r3397669  
    1919    const DEFAULT_DB_FILENAME_TEMPLATE    = '{db}-{date}-db';
    2020    const DEFAULT_FILES_FILENAME_TEMPLATE = '{db}-{date}-files';
    21     const DEFAULT_DIR_SUFFIX              = 'tiny-backup-datas'; // uploadsにこのサブフォルダを作成
     21    const DEFAULT_DIR_SUFFIX              = 'tiny-backup'; // wp-content直下にこのサブフォルダを作成
    2222
    2323    // 圧縮・I/O のチューニング値(フィルタで上書き可能)
     
    3232    public static function get_zip_level() {
    3333        return (int) apply_filters( 'tnbu_zip_level', self::ZIP_LEVEL );
    34     }
    35 
    36     /**
    37      * バックアップディレクトリのパスを取得する
    38      * wp_upload_dir()を使用してアップロードディレクトリに保存する
    39      *
    40      * @return string バックアップディレクトリの絶対パス
    41      */
    42     public static function get_backup_dir() {
    43         $upload_dir = wp_upload_dir();
    44         $backup_dir = trailingslashit( $upload_dir['basedir'] ) . self::DEFAULT_DIR_SUFFIX;
    45         return $backup_dir;
    4634    }
    4735
     
    9684        update_option( self::OPTION_KEY, $opts );
    9785        // Ensure directory exists
    98         $abs_dir = self::get_backup_dir();
    99         wp_mkdir_p( $abs_dir );
     86        if ( ! empty( $opts['target_dir'] ) ) {
     87            $abs_dir = trailingslashit( WP_CONTENT_DIR ) . ltrim( $opts['target_dir'], '/' );
     88            wp_mkdir_p( $abs_dir );
     89        }
    10090    }
    10191
  • tiny-backup/trunk/includes/class-tnbu-database-backup.php

    r3397190 r3397669  
    1414    /**
    1515     * データベースのバックアップを実行する
    16      * PHPによるダンプを実行し、指定された圧縮形式で保存する
     16     * mysqldumpまたはPHPによるダンプを実行し、指定された圧縮形式で保存する
    1717     *
    1818     * @return string|WP_Error 成功時はファイルパス、失敗時はWP_Error
     
    2020    public static function perform_backup() {
    2121        global $wpdb;
    22         $abs_dir   = TNBU_Core::get_backup_dir();
     22        $opts      = wp_parse_args( get_option( TNBU_Core::OPTION_KEY, array() ), TNBU_Core::defaults() );
     23        $abs_dir   = trailingslashit( WP_CONTENT_DIR ) . ltrim( $opts['target_dir'], '/' );
    2324        $dir_check = TNBU_Utilities::ensure_directory( $abs_dir );
    2425        if ( is_wp_error( $dir_check ) ) {
     
    3233        $sql_path      = $base_path . $base_filename . '.sql';
    3334
    34         //export SQL via PHP dumper
     35        // 使用したダンプ方法を記録
     36        $dump_method = '';
     37
     38        // 1) export SQL via mysqldump if available, fallback to PHP dumper
    3539        TNBU_Progress_Manager::set_progress( __( 'Starting database dump', 'tiny-backup' ), false, null );
    36         $php_dump = self::dump_database_with_php( $sql_path );
    37         if ( is_wp_error( $php_dump ) ) {
    38             return $php_dump;
    39         }
    40         TNBU_Progress_Manager::set_progress( __( 'Database dump completed', 'tiny-backup' ), false, null );
     40        $dump_result = self::dump_database_with_mysqldump( $sql_path );
     41        if ( is_wp_error( $dump_result ) ) {
     42            TNBU_Progress_Manager::set_progress( __( 'mysqldump failed, switching to PHP dump', 'tiny-backup' ), false, null );
     43            $php_dump = self::dump_database_with_php( $sql_path );
     44            if ( is_wp_error( $php_dump ) ) {
     45                return $dump_result; // mysqldumpのエラーを返す(詳細がわかりやすい)
     46            }
     47            $dump_method = 'PHPdump';
     48        } else {
     49            TNBU_Progress_Manager::set_progress( __( 'mysqldump completed', 'tiny-backup' ), false, null );
     50            $dump_method = 'mysqldump';
     51        }
     52
     53        // ダンプ方法を一時的に保存(リダイレクト後に表示するため)
     54        set_transient( 'tnbu_last_dump_method', $dump_method, 300 ); // 5分間保存
    4155
    4256        $final_path  = $sql_path;
     
    5670                return new WP_Error( 'sbwp_zip', __( 'ZIP extension is not available', 'tiny-backup' ) );
    5771            }
    58             $zip_path = $base_path . $base_filename . '.sql.zip';
     72            $zip_path = $base_path . $base_filename . '.zip';
    5973            $tmp_path = $zip_path . '.part';
    6074            $zip      = new ZipArchive();
     
    195209    }
    196210
     211    /**
     212     * mysqldumpコマンドを使用してデータベースをダンプする
     213     *
     214     * @param string $sql_path 出力先SQLファイルパス
     215     * @return true|WP_Error 成功時はtrue、失敗時はWP_Error
     216     */
     217    protected static function dump_database_with_mysqldump( $sql_path ) {
     218        // Validate exec availability
     219        if ( ! function_exists( 'escapeshellarg' ) || ! function_exists( 'exec' ) ) {
     220            return new WP_Error( 'sbwp_exec', __( 'Command execution is not allowed on this server', 'tiny-backup' ) );
     221        }
     222        // Build mysqldump command
     223        $db_host = defined( 'DB_HOST' ) ? DB_HOST : 'localhost';
     224        $db_name = defined( 'DB_NAME' ) ? DB_NAME : '';
     225        $db_user = defined( 'DB_USER' ) ? DB_USER : '';
     226        $db_pass = defined( 'DB_PASSWORD' ) ? DB_PASSWORD : '';
     227
     228        $host = $db_host;
     229        $port = '';
     230        if ( strpos( $db_host, ':' ) !== false ) {
     231            list($host_part, $port_part) = explode( ':', $db_host, 2 );
     232            $host                        = $host_part;
     233            $port                        = $port_part;
     234        }
     235
     236        $cmd     = 'mysqldump';
     237        $parts   = array();
     238        $parts[] = $cmd;
     239        $parts[] = '-h' . escapeshellarg( $host );
     240        if ( '' !== $port ) {
     241            $parts[] = '-P' . escapeshellarg( $port );
     242        }
     243        $parts[] = '-u' . escapeshellarg( $db_user );
     244        if ( '' !== $db_pass ) {
     245            $parts[] = '-p' . escapeshellarg( $db_pass );
     246        }
     247        $parts[] = '--single-transaction';
     248        $parts[] = '--quick';
     249        $parts[] = '--hex-blob';
     250        $maxpkt  = apply_filters( 'tnbu_dump_max_allowed_packet', TNBU_Core::DUMP_MAX_ALLOWED_PACKET );
     251        if ( is_string( $maxpkt ) && '' !== $maxpkt ) {
     252            $parts[] = '--max-allowed-packet=' . escapeshellarg( $maxpkt );
     253        }
     254        $parts[] = escapeshellarg( $db_name );
     255
     256        // 標準エラー出力を一時ファイルにリダイレクト(SQLファイルに混入させないため)
     257        $stderr_file = tempnam( sys_get_temp_dir(), 'tnbu_mysqldump_err_' );
     258        $command     = implode( ' ', $parts ) . ' > ' . escapeshellarg( $sql_path ) . ' 2>' . escapeshellarg( $stderr_file );
     259
     260        // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- mysqldump command execution requires error suppression
     261        @exec( $command, $output, $code );
     262
     263        // エラー出力を読み取り
     264        $stderr = '';
     265        if ( file_exists( $stderr_file ) ) {
     266
     267            $stderr = file_get_contents( $stderr_file );
     268            // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink -- Cleanup temporary file
     269            @unlink( $stderr_file );
     270        }
     271
     272        if ( 0 !== (int) $code || ! file_exists( $sql_path ) || 0 === filesize( $sql_path ) ) {
     273            $error_msg = '';
     274            if ( ! empty( $stderr ) ) {
     275                $error_msg = $stderr;
     276            } elseif ( ! empty( $output ) ) {
     277                $error_msg = implode( "\n", (array) $output );
     278            } else {
     279                $error_msg = __( 'Unknown error', 'tiny-backup' );
     280            }
     281            /* translators: %s: error output from mysqldump */
     282            return new WP_Error( 'sbwp_dump', sprintf( __( 'mysqldump failed: %s', 'tiny-backup' ), $error_msg ) );
     283        }
     284        return true;
     285    }
    197286}
  • tiny-backup/trunk/includes/class-tnbu-file-backup.php

    r3397190 r3397669  
    1616     */
    1717    public static function perform_files_backup_selected( array $items ) {
    18         $backup_dir = TNBU_Core::get_backup_dir();
     18        $opts       = TNBU_Core::get_options();
     19        $backup_dir = trailingslashit( WP_CONTENT_DIR ) . ltrim( $opts['target_dir'], '/' );
    1920        $ensure     = TNBU_Utilities::ensure_directory( $backup_dir );
    2021        if ( is_wp_error( $ensure ) ) {
     
    4344        $items      = array_values( array_unique( $items ) );
    4445        $compressed = TNBU_Utilities::optimize_targets_by_parent( $items, $base );
     46        // CLI zip が使える場合は高速ルートを先に試す(成功時はここで完了)
     47        TNBU_Progress_Manager::set_progress( __( 'Trying CLI compression', 'tiny-backup' ), false, null );
     48        $cli_ok = self::try_zip_with_cli( $compressed, $zip_path );
     49        if ( true === $cli_ok ) {
     50            TNBU_Progress_Manager::set_progress( __( 'Files compression completed (CLI)', 'tiny-backup' ), true, null );
     51            return $zip_path;
     52        }
     53        TNBU_Progress_Manager::set_progress( __( 'CLI compression failed, falling back to PHP', 'tiny-backup' ), false, null );
    4554        // ファイルをZIPに追加
    4655        TNBU_Progress_Manager::set_progress( __( 'Starting file scan', 'tiny-backup' ), false, null );
     
    8493        $count           = 0;
    8594        $real_base       = realpath( $base_root );
    86         $real_backup_dir = realpath( TNBU_Core::get_backup_dir() );
     95        $real_backup_dir = realpath( trailingslashit( WP_CONTENT_DIR ) . ltrim( TNBU_Core::get_options()['target_dir'], '/' ) );
    8796
    8897        foreach ( $targets as $rel ) {
     
    139148        }
    140149        return $count;
     150    }
     151
     152    /**
     153     * 可能ならzipコマンドで高速にZIPを作成(fallbackはZipArchive)
     154     * 選択された targets(wp-content 相対)をまとめてZIPする。
     155     */
     156    private static function try_zip_with_cli( array $targets, $dest_zip_path ) {
     157        if ( ! function_exists( 'exec' ) ) {
     158            return false; }
     159        // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- CLI zip command availability check requires error suppression
     160        @exec( 'command -v zip 2>/dev/null', $out, $code );
     161        if ( 0 !== $code || empty( $out ) ) {
     162            return false; }
     163        $zip_bin     = trim( (string) $out[0] );
     164        $dir         = WP_CONTENT_DIR;
     165        $rel_targets = array();
     166        // 親優先最適化を適用
     167        $optimized_targets = TNBU_Utilities::optimize_targets_by_parent( $targets, $dir );
     168
     169        foreach ( $optimized_targets as $rel ) {
     170            $rel = ltrim( (string) $rel, '/' );
     171            if ( '' === $rel ) {
     172                continue;
     173            }
     174            $full_path = $dir . '/' . $rel;
     175            if ( TNBU_Utilities::should_exclude_file( $full_path, $dir ) ) {
     176                continue;
     177            }
     178            $rel_targets[] = escapeshellarg( $rel );
     179        }
     180        if ( empty( $rel_targets ) ) {
     181            return false; }
     182        $dest_tmp = $dest_zip_path . '.part';
     183        $parent   = dirname( $dest_tmp );
     184        if ( ! is_dir( $parent ) ) {
     185            wp_mkdir_p( $parent ); }
     186        // zip -r -9 -q <destTmp> <targets...> (-x で除外)
     187        $exclude      = TNBU_Utilities::get_exclude_patterns();
     188        $exclude[]    = trim( str_replace( WP_CONTENT_DIR . '/', '', trailingslashit( $parent ) ), '/' ) . '/*';
     189        $exclude_args = '';
     190        foreach ( $exclude as $ex ) {
     191            $exclude_args .= ' -x ' . escapeshellarg( $ex );
     192        }
     193        $cmd = 'cd ' . escapeshellarg( $dir ) . ' && ' . escapeshellarg( $zip_bin ) . ' -r -9 -q ' . escapeshellarg( $dest_tmp ) . ' ' . implode( ' ', $rel_targets ) . $exclude_args;
     194        // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- CLI zip command execution requires error suppression
     195        @exec( $cmd . ' 2>&1', $o2, $c2 );
     196        if ( 0 === (int) $c2 && file_exists( $dest_tmp ) && filesize( $dest_tmp ) > 0 ) {
     197            // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename -- wp_move_file() doesn't exist
     198            if ( @rename( $dest_tmp, $dest_zip_path ) ) {
     199                return true; }
     200            wp_delete_file( $dest_tmp );
     201        }
     202        return false;
    141203    }
    142204
  • tiny-backup/trunk/includes/class-tnbu-utilities.php

    r3397190 r3397669  
    3535
    3636        // バックアップディレクトリ除外(自己再帰防止強化)
    37         $backup_dir      = TNBU_Core::get_backup_dir();
     37        $opts            = TNBU_Core::get_options();
     38        $backup_dir      = trailingslashit( WP_CONTENT_DIR ) . ltrim( $opts['target_dir'], '/' );
    3839        $real_backup_dir = realpath( $backup_dir );
    3940        $real_file_path  = realpath( $file_path );
  • tiny-backup/trunk/readme.txt

    r3397190 r3397669  
    11=== Tiny Backup ===
    2 Contributors: ejointjp
     2Contributors: Takashi Fujisaki
    33Tags: backup, database, files, zip, admin
    44Requires at least: 6.0
    55Tested up to: 6.8
    66Requires PHP: 7.4
    7 Stable tag: 1.0.0
     7Stable tag: 0.1.1
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    4646== Screenshots ==
    4747
    48 1. Settings screen - Choose what to backup: database and/or files, and select specific folders/plugins/themes under wp-content
    49 2. Backup management screen - View all backup files with creation date and size, download or delete backups
     481. Backup screen and progress indicator
     492. Backup files list with download and delete options
    5050
    5151== Changelog ==
    5252
    53 = 1.0.0 =
    54 * Initial Release.
     53= 0.1.0 =
     54* Initial release.
    5555
    5656== Upgrade Notice ==
    5757
    58 = 1.0.0 =
     58= 0.1.0 =
    5959Initial release.
    6060
  • tiny-backup/trunk/tiny-backup.php

    r3397190 r3397669  
    33Plugin Name: Tiny Backup
    44Description: Create database and files backups with minimal setup.
    5 Version: 1.0.0
    6 Author: Takashi Fujisaki 
     5Version: 0.1.1
     6Author: Takashi Fujisaki
    77Plugin URI: https://wordpress.org/plugins/tiny-backup/
    88Author URI: https://yuiami.jp
Note: See TracChangeset for help on using the changeset viewer.