Plugin Directory

Changeset 3484887


Ignore:
Timestamp:
03/17/2026 03:14:25 PM (13 days ago)
Author:
luzidmedia
Message:

Release 1.3.0

Location:
luzid-backup-to-nextcloud/trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • luzid-backup-to-nextcloud/trunk/admin-page.php

    r3481769 r3484887  
    11<?php
    22/**
    3  * Admin Page Template for Luzid Backup to Nextcloud v1.2.9
     3 * Admin Page Template for Luzid Backup to Nextcloud v1.3.0
    44 *
    5  * This file is included from Luzid_Backup_To_Nextcloud::render_admin_page(),
    6  * which already verifies current_user_can('manage_options').
    75 * Variables $lang, $options, $de_url, $en_url, $t, $active_tab are set by the caller.
    86 */
     
    1311
    1412$luzid_btn_source_names = array(
    15     'updraftplus' => 'UpdraftPlus',
    16     'backwpup'    => 'BackWPup',
    17     'wpvivid'    => 'WPvivid',
    18     'ai1wm'      => 'All-in-One WP Migration',
    19     'duplicator' => 'Duplicator',
    20     'custom'      => 'Custom',
     13    'updraftplus' => 'UpdraftPlus',
     14    'backwpup' => 'BackWPup',
     15    'wpvivid' => 'WPvivid',
     16    'ai1wm' => 'All-in-One WP Migration',
     17    'duplicator' => 'Duplicator',
     18    'custom' => 'Custom'
    2119);
    2220
    2321// Check for test result – set via wp_safe_redirect after nonce-verified POST handlers.
    2422// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display flag set by redirect.
    25 $luzid_btn_show_test_modal = isset( $_GET['test_result'] );
     23$luzid_btn_show_test_modal = isset($_GET['test_result']);
    2624// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display flag set by redirect.
    2725$luzid_btn_test_success = isset( $_GET['test_result'] ) && 'success' === sanitize_text_field( wp_unslash( $_GET['test_result'] ) );
    2826
    29 // Check for upload modal.
    30 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display flag.
    31 $luzid_btn_show_upload_modal = isset( $_GET['show_upload_modal'] );
    32 
    33 // Check for settings saved modal.
    34 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display flag set by redirect.
    35 $luzid_btn_show_settings_saved_modal = isset( $_GET['settings_saved'] );
    36 
    37 // Assets (logo + flags).
    38 $luzid_btn_flag_de  = LUZID_BACKUP_PLUGIN_URL . 'assets/img/ger.svg';
    39 $luzid_btn_flag_en  = LUZID_BACKUP_PLUGIN_URL . 'assets/img/uk.svg';
     27// Check for upload modal
     28// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display flag only
     29$luzid_btn_show_upload_modal = isset($_GET['show_upload_modal']);
     30
     31// Check for settings saved modal
     32// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display flag only
     33$luzid_btn_show_settings_saved_modal = isset($_GET['settings_saved']);
     34
     35// Assets (logo + flags)
     36$luzid_btn_flag_de = LUZID_BACKUP_PLUGIN_URL . 'assets/img/ger.svg';
     37$luzid_btn_flag_en = LUZID_BACKUP_PLUGIN_URL . 'assets/img/uk.svg';
    4038$luzid_btn_logo_url = LUZID_BACKUP_PLUGIN_URL . 'assets/img/luzid-media-logo-plugins.svg';
    4139
    4240$luzid_btn_lang_switcher_html  = '<div class="lcs-lang-switcher">';
    43 $luzid_btn_lang_switcher_html .= '<a href="' . esc_url( $de_url ) . '" class="' . ( 'de' === $lang ? 'active' : '' ) . '">' . '<img src="' . esc_url( $luzid_btn_flag_de ) . '" alt="DE" title="Deutsch">' . '</a>';
    44 $luzid_btn_lang_switcher_html .= '<a href="' . esc_url( $en_url ) . '" class="' . ( 'en' === $lang ? 'active' : '' ) . '">' . '<img src="' . esc_url( $luzid_btn_flag_en ) . '" alt="EN" title="English">' . '</a>';
     41$luzid_btn_lang_switcher_html .= '<a href="' . esc_url($de_url) . '" class="' . ($lang === 'de' ? 'active' : '') . '">' . '<img src="' . esc_url($luzid_btn_flag_de) . '" alt="DE" title="Deutsch">' . '</a>';
     42$luzid_btn_lang_switcher_html .= '<a href="' . esc_url($en_url) . '" class="' . ($lang === 'en' ? 'active' : '') . '">' . '<img src="' . esc_url($luzid_btn_flag_en) . '" alt="EN" title="English">' . '</a>';
    4543$luzid_btn_lang_switcher_html .= '</div>';
    4644
    47 // Check for delete confirmation.
    48 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display flag set by redirect.
    49 if ( isset( $_GET['all_deleted'] ) ) {
    50     echo '<div class="lcs-notice success"><p>' . esc_html( $t['all_deleted'] ) . '</p></div>';
     45// Check for delete confirmation
     46// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display flag only
     47if (isset($_GET['all_deleted'])) {
     48    echo '<div class="lcs-notice success"><p>' . esc_html($t['all_deleted']) . '</p></div>';
    5149}
    5250
    53 // Check for logs cleared.
    54 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display flag set by redirect.
    55 if ( isset( $_GET['logs_cleared'] ) ) {
    56     echo '<div class="lcs-notice success"><p>' . esc_html( $t['logs_cleared'] ) . '</p></div>';
     51// Check for logs cleared
     52// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display flag only
     53if (isset($_GET['logs_cleared'])) {
     54    echo '<div class="lcs-notice success"><p>' . esc_html($t['logs_cleared']) . '</p></div>';
     55}
     56
     57// Determine if any backup source is enabled (used for button visibility + hint).
     58$luzid_btn_has_enabled_source = false;
     59if ( ! empty( $options['sources'] ) && is_array( $options['sources'] ) ) {
     60    foreach ( $options['sources'] as $luzid_btn_chk ) {
     61        if ( ! empty( $luzid_btn_chk['enabled'] ) ) {
     62            $luzid_btn_has_enabled_source = true;
     63            break;
     64        }
     65    }
    5766}
    5867?>
     
    6877            <div class="lcs-header-right">
    6978                <div class="lcs-logo">
    70                     <img class="lcs-logo-img" src="<?php echo esc_url( $luzid_btn_logo_url ); ?>" alt="Luzid">
     79                    <img class="lcs-logo-img" src="<?php echo esc_url($luzid_btn_logo_url); ?>" alt="Luzid">
    7180                </div>
    7281            </div>
     
    8190                <button class="lcs-tab-link <?php echo $active_tab === 'tab-howto' ? 'active' : ''; ?>" data-tab="tab-howto"><?php echo esc_html($t['tab_howto']); ?></button>
    8291            </div>
    83             <div class="lcs-tabs-nav__right"><?php echo wp_kses_post( $luzid_btn_lang_switcher_html ); ?></div>
     92            <div class="lcs-tabs-nav__right"><?php echo wp_kses_post($luzid_btn_lang_switcher_html); ?></div>
    8493        </div>
    8594
     
    95104                        <th><?php echo esc_html($t['webdav_url']); ?></th>
    96105                        <td>
    97                             <input type="url" name="webdav_url" value="<?php echo esc_attr($options['webdav_url']); ?>" placeholder="https://cloud.example.com/remote.php/dav/files/USERNAME/">
     106                            <input type="url" name="webdav_url" value="<?php echo esc_attr($options['webdav_url']); ?>">
    98107                            <span class="lcs-description"><?php echo esc_html($t['webdav_url_desc']); ?></span>
    99108                        </td>
     
    108117                        <th><?php echo esc_html($t['password']); ?></th>
    109118                        <td>
    110                             <input type="password" name="webdav_password" placeholder="<?php echo !empty($options['webdav_password']) ? '••••••••' : ''; ?>">
     119                            <div style="display: flex; align-items: center; gap: 6px;">
     120                                <input type="password" name="luzid_btn_app_password" id="luzid_btn_app_password" autocomplete="new-password" placeholder="<?php echo !empty($options['webdav_password']) ? '••••••••' : ''; ?>" value="">
     121                                <button type="button" id="luzid-btn-toggle-pw" class="button button-secondary" style="padding: 2px 8px; min-height: 30px; line-height: 28px;" title="<?php echo 'de' === $lang ? 'Passwort anzeigen/verbergen' : 'Show/hide password'; ?>">
     122                                    <span class="dashicons dashicons-visibility" style="vertical-align: middle;"></span>
     123                                </button>
     124                            </div>
     125                            <script>
     126                            (function(){
     127                                var btn = document.getElementById('luzid-btn-toggle-pw');
     128                                if (btn) {
     129                                    btn.addEventListener('click', function() {
     130                                        var inp = document.getElementById('luzid_btn_app_password');
     131                                        var ico = btn.querySelector('.dashicons');
     132                                        if (inp.type === 'password') {
     133                                            inp.type = 'text';
     134                                            ico.classList.remove('dashicons-visibility');
     135                                            ico.classList.add('dashicons-hidden');
     136                                        } else {
     137                                            inp.type = 'password';
     138                                            ico.classList.remove('dashicons-hidden');
     139                                            ico.classList.add('dashicons-visibility');
     140                                        }
     141                                    });
     142                                }
     143                            })();
     144                            </script>
    111145                            <span class="lcs-description"><?php echo esc_html($t['password_desc']); ?></span>
    112                             <div class="lcs-checkbox-wrapper" style="margin-top: 10px;">
    113                                 <input type="checkbox" name="remove_password" value="1" id="remove_password">
    114                                 <label for="remove_password"><?php echo esc_html($t['remove_password']); ?></label>
    115                             </div>
     146
    116147                        </td>
    117148                    </tr>
     
    164195
    165196                <?php
    166                 // Show hint if no source has been selected yet.
    167                 $luzid_btn_has_enabled_source = false;
    168                 if ( ! empty( $options['sources'] ) && is_array( $options['sources'] ) ) {
    169                     foreach ( $options['sources'] as $luzid_btn_s ) {
    170                         if ( ! empty( $luzid_btn_s['enabled'] ) ) {
    171                             $luzid_btn_has_enabled_source = true;
    172                             break;
    173                         }
    174                     }
    175                 }
    176197                if ( ! $luzid_btn_has_enabled_source ) {
    177198                    echo '<p style="margin-top: 20px;"><strong>' . esc_html( $t['select_source_hint'] ?? 'Bitte wähle eine Backup-Quelle' ) . '</strong></p>';
     
    184205                <h2><?php echo esc_html($t['sources_title']); ?></h2>
    185206
    186                 <?php foreach ( $luzid_btn_source_names as $luzid_btn_source_id => $luzid_btn_source_name ) :
    187                     $luzid_btn_source  = isset( $options['sources'][ $luzid_btn_source_id ] ) ? $options['sources'][ $luzid_btn_source_id ] : array();
    188                     $luzid_btn_enabled = ! empty( $luzid_btn_source['enabled'] );
     207                <?php foreach ($luzid_btn_source_names as $luzid_btn_source_id => $luzid_btn_source_name):
     208                    $luzid_btn_source = isset( $options['sources'][ $luzid_btn_source_id ] ) ? $options['sources'][ $luzid_btn_source_id ] : array();
     209                    $luzid_btn_enabled = !empty($luzid_btn_source['enabled']);
    189210                ?>
    190211                <div class="lcs-source-item">
    191212                    <div class="lcs-source-header">
    192                         <h3><?php echo esc_html( $luzid_btn_source_name ); ?></h3>
     213                        <h3><?php echo esc_html($luzid_btn_source_name); ?></h3>
    193214                        <div class="lcs-checkbox-wrapper">
    194                             <input type="checkbox" name="sources[<?php echo esc_attr( $luzid_btn_source_id ); ?>][enabled]" value="1" id="source_<?php echo esc_attr( $luzid_btn_source_id ); ?>" <?php checked( $luzid_btn_enabled ); ?>>
    195                             <label for="source_<?php echo esc_attr( $luzid_btn_source_id ); ?>"><?php echo esc_html( $t['source_enabled'] ); ?></label>
     215                            <input type="checkbox" name="sources[<?php echo esc_attr($luzid_btn_source_id); ?>][enabled]" value="1" id="source_<?php echo esc_attr($luzid_btn_source_id); ?>" <?php checked($luzid_btn_enabled); ?>>
     216                            <label for="source_<?php echo esc_attr($luzid_btn_source_id); ?>"><?php echo esc_html($t['source_enabled']); ?></label>
    196217                        </div>
    197218                    </div>
    198219                    <div class="lcs-source-fields">
    199220                        <div class="lcs-source-field">
    200                             <label><?php echo esc_html( $t['source_path'] ); ?></label>
    201                             <input type="text" name="sources[<?php echo esc_attr( $luzid_btn_source_id ); ?>][path]" value="<?php echo esc_attr( $luzid_btn_source['path'] ?? '' ); ?>">
     221                            <label><?php echo esc_html($t['source_path']); ?></label>
     222                            <input type="text" name="sources[<?php echo esc_attr($luzid_btn_source_id); ?>][path]" value="<?php echo esc_attr($luzid_btn_source['path'] ?? ''); ?>">
    202223                        </div>
    203224                        <div class="lcs-source-field">
    204                             <label><?php echo esc_html( $t['source_extensions'] ); ?></label>
    205                             <input type="text" name="sources[<?php echo esc_attr( $luzid_btn_source_id ); ?>][extensions]" value="<?php echo esc_attr( $luzid_btn_source['extensions'] ?? '' ); ?>">
     225                            <label><?php echo esc_html($t['source_extensions']); ?></label>
     226                            <input type="text" name="sources[<?php echo esc_attr($luzid_btn_source_id); ?>][extensions]" value="<?php echo esc_attr($luzid_btn_source['extensions'] ?? ''); ?>">
    206227                        </div>
    207228                        <div class="lcs-checkbox-wrapper">
    208                             <input type="checkbox" name="sources[<?php echo esc_attr( $luzid_btn_source_id ); ?>][scan_real_files]" value="1" id="scan_<?php echo esc_attr( $luzid_btn_source_id ); ?>" <?php checked( ! empty( $luzid_btn_source['scan_real_files'] ) ); ?>>
    209                             <label for="scan_<?php echo esc_attr( $luzid_btn_source_id ); ?>"><?php echo esc_html( $t['source_scan_real'] ); ?></label>
    210                             <span class="lcs-description"><?php echo esc_html( $t['source_scan_real_desc'] ?? '' ); ?></span>
     229                            <input type="checkbox" name="sources[<?php echo esc_attr($luzid_btn_source_id); ?>][scan_real_files]" value="1" id="scan_<?php echo esc_attr($luzid_btn_source_id); ?>" <?php checked(!empty($luzid_btn_source['scan_real_files'])); ?>>
     230                            <label for="scan_<?php echo esc_attr($luzid_btn_source_id); ?>"><?php echo esc_html($t['source_scan_real']); ?></label>
     231                            <span class="lcs-description"><?php echo esc_html($t['source_scan_real_desc'] ?? ''); ?></span>
    211232                        </div>
    212233                    </div>
     
    300321                <div class="lcs-log-container">
    301322                    <?php
    302                     $luzid_btn_logs = get_option( 'luzid_backup_logs', array() );
    303                     if ( empty( $luzid_btn_logs ) ) {
     323                    $luzid_btn_logs = get_option('luzid_backup_logs', array());
     324                    if (empty($luzid_btn_logs)) {
    304325                        echo '<div class="log-info">No logs yet</div>';
    305326                    } else {
    306                         foreach ( array_reverse( array_slice( $luzid_btn_logs, -50 ) ) as $luzid_btn_log ) {
    307                             $luzid_btn_class = 'log-' . esc_attr( $luzid_btn_log['level'] );
    308                             echo '<div class="' . esc_attr( $luzid_btn_class ) . '">[' . esc_html( $luzid_btn_log['time'] ) . '] ' . esc_html( $luzid_btn_log['message'] ) . '</div>';
     327                        foreach (array_reverse(array_slice($luzid_btn_logs, -50)) as $luzid_btn_log) {
     328                            $luzid_btn_class = 'log-' . esc_attr($luzid_btn_log['level']);
     329                            echo '<div class="' . esc_attr($luzid_btn_class) . '">[' . esc_html($luzid_btn_log['time']) . '] ' . esc_html($luzid_btn_log['message']) . '</div>';
    309330                        }
    310331                    }
     
    318339                    <?php
    319340                    $luzid_btn_howto_file = LUZID_BACKUP_PLUGIN_DIR . 'howto-' . $lang . '.php';
    320                     if ( file_exists( $luzid_btn_howto_file ) ) {
     341                    if (file_exists($luzid_btn_howto_file)) {
    321342                        include $luzid_btn_howto_file;
    322343                    }
     
    326347        </form>
    327348
    328         <!-- Action Buttons - Always visible below tabs -->
    329         <div style="padding: 20px 30px; border-top: 1px solid #e5e5e5; background: #fafafa;">
     349        <!-- Action Buttons - Visibility depends on active tab -->
     350        <div id="luzid-btn-actions" data-has-source="<?php echo $luzid_btn_has_enabled_source ? '1' : '0'; ?>" style="padding: 20px 30px; border-top: 1px solid #e5e5e5; background: #fafafa;">
    330351            <div class="lcs-button-group">
    331                 <button type="submit" form="luzid-backup-form" class="lcs-button"><?php echo esc_html($t['save_settings']); ?></button>
     352                <button type="submit" form="luzid-backup-form" class="lcs-button" id="luzid-btn-save"><?php echo esc_html($t['save_settings']); ?></button>
    332353               
    333                 <button type="button" onclick="luzidSaveAndTest()" class="lcs-button lcs-button-secondary"><?php echo esc_html($t['test_connection']); ?></button>
    334 
    335                 <button type="button" onclick="luzidStartUpload()" class="lcs-button lcs-button-secondary"><?php echo esc_html($t['manual_run']); ?></button>
     354                <button type="button" onclick="luzidSaveAndTest()" class="lcs-button lcs-button-secondary" id="luzid-btn-test"><?php echo esc_html($t['test_connection']); ?></button>
     355
     356                <button type="button" onclick="luzidStartUpload()" class="lcs-button lcs-button-secondary" id="luzid-btn-upload"><?php echo esc_html($t['manual_run']); ?></button>
    336357
    337358                <a href="<?php echo esc_url(wp_nonce_url(add_query_arg(array('action' => 'luzid_backup_delete_all', 'luzid_lang' => $lang), admin_url('admin-post.php')), 'luzid_backup_delete_all')); ?>"
    338                    class="lcs-button lcs-button-secondary"
     359                   class="lcs-button lcs-button-secondary" id="luzid-btn-delete"
    339360                   onclick="return confirm('<?php echo esc_js($t['confirm_delete_all']); ?>')">
    340361                    <?php echo esc_html($t['delete_all']); ?>
     
    342363            </div>
    343364        </div>
     365        <script>
     366        (function(){
     367            function luzidUpdateButtons() {
     368                var tabInput  = document.getElementById('active-tab-input');
     369                var tab       = tabInput ? tabInput.value : 'tab-nextcloud';
     370                var btnSave   = document.getElementById('luzid-btn-save');
     371                var btnTest   = document.getElementById('luzid-btn-test');
     372                var btnUpload = document.getElementById('luzid-btn-upload');
     373                var btnDelete = document.getElementById('luzid-btn-delete');
     374                var bar       = document.getElementById('luzid-btn-actions');
     375                var hasSource = bar && bar.getAttribute('data-has-source') === '1';
     376
     377                if (tab === 'tab-nextcloud') {
     378                    btnSave.style.display   = 'none';
     379                    btnTest.style.display   = '';
     380                    btnUpload.style.display = 'none';
     381                    btnDelete.style.display = '';
     382                    bar.style.display       = '';
     383                } else if (tab === 'tab-howto') {
     384                    bar.style.display = 'none';
     385                } else {
     386                    btnSave.style.display   = '';
     387                    btnTest.style.display   = 'none';
     388                    btnUpload.style.display = '';
     389                    btnDelete.style.display = '';
     390                    bar.style.display       = '';
     391                }
     392                // Upload button: disabled (greyed out) when no source is enabled
     393                if (btnUpload) {
     394                    if (!hasSource) {
     395                        btnUpload.disabled = true;
     396                        btnUpload.style.opacity = '0.5';
     397                        btnUpload.style.cursor = 'not-allowed';
     398                    } else {
     399                        btnUpload.disabled = false;
     400                        btnUpload.style.opacity = '';
     401                        btnUpload.style.cursor = '';
     402                    }
     403                }
     404            }
     405            document.addEventListener('click', function(e) {
     406                if (e.target.classList.contains('lcs-tab-link')) {
     407                    setTimeout(luzidUpdateButtons, 10);
     408                }
     409            });
     410            document.addEventListener('DOMContentLoaded', luzidUpdateButtons);
     411        })();
     412        </script>
    344413    </div>
    345414</div>
     
    363432<div id="test-result-modal" class="lcs-overlay <?php echo $luzid_btn_show_test_modal ? 'active' : ''; ?>">
    364433    <div class="lcs-overlay-content">
    365         <h2><?php echo esc_html( $luzid_btn_test_success ? $t['test_success'] : $t['test_failed'] ); ?></h2>
     434        <h2><?php echo esc_html($luzid_btn_test_success ? $t['test_success'] : $t['test_failed']); ?></h2>
    366435        <p style="font-size: 48px; margin: 20px 0; text-align: center;"><?php echo $luzid_btn_test_success ? '✓' : '✗'; ?></p>
    367         <p style="text-align: center;"><?php echo esc_html( $luzid_btn_test_success ? $t['test_success'] : $t['test_failed'] ); ?></p>
     436        <p style="text-align: center;"><?php echo esc_html($luzid_btn_test_success ? $t['test_success'] : $t['test_failed']); ?></p>
    368437        <div class="lcs-button-group">
    369438            <button type="button" onclick="luzidCloseModal('test-result-modal')" class="lcs-button"><?php echo esc_html($t['close']); ?></button>
  • luzid-backup-to-nextcloud/trunk/howto-de.php

    r3481769 r3484887  
    44 */
    55
    6 // Exit if accessed directly
    7 if (!defined('ABSPATH')) {
     6if ( ! defined( 'ABSPATH' ) ) {
    87    exit;
    98}
     
    1413
    1514<h3>Voraussetzungen</h3>
    16 
    17 <p>Bevor Sie beginnen, stellen Sie sicher, dass Sie folgendes haben:</p>
    1815
    1916<ul>
     
    2724<p>Aus Sicherheitsgründen sollten Sie ein <strong>App-Passwort</strong> verwenden, nicht Ihr Hauptpasswort.</p>
    2825
    29 <h4>So erstellen Sie ein App-Passwort:</h4>
    30 
    3126<ol>
    3227    <li>Melden Sie sich in Ihrer Nextcloud an</li>
    33     <li>Klicken Sie oben rechts auf Ihr Profilbild</li>
    34     <li>Wählen Sie <strong>Einstellungen</strong></li>
    35     <li>Navigieren Sie zu <strong>Sicherheit</strong> im linken Menü</li>
    36     <li>Scrollen Sie zu <strong>Geräte & Sitzungen</strong></li>
     28    <li>Klicken Sie oben rechts auf Ihr Profilbild → <strong>Einstellungen</strong></li>
     29    <li>Navigieren Sie zu <strong>Sicherheit</strong> → <strong>Geräte & Sitzungen</strong></li>
    3730    <li>Geben Sie einen App-Namen ein (z.B. "WordPress Backup")</li>
    3831    <li>Klicken Sie auf <strong>Neues App-Passwort erstellen</strong></li>
     
    4033</ol>
    4134
    42 <p><strong>Wichtig:</strong> Bewahren Sie dieses Passwort sicher auf. Sie benötigen es für die Plugin-Konfiguration.</p>
    43 
    4435<h3>Schritt 2: WebDAV-URL herausfinden</h3>
    45 
    46 <p>Die WebDAV-URL ist die Adresse, über die das Plugin auf Ihre Nextcloud zugreift.</p>
    47 
    48 <h4>So finden Sie Ihre WebDAV-URL:</h4>
    4936
    5037<ol>
    5138    <li>Melden Sie sich in Ihrer Nextcloud an</li>
    5239    <li>Klicken Sie links unten auf das <strong>Einstellungen-Symbol</strong> (Zahnrad)</li>
    53     <li>Gehen Sie zu <strong>Dateien</strong> → <strong>WebDAV</strong></li>
    54     <li>Kopieren Sie die angezeigte WebDAV-URL</li>
     40    <li>Die WebDAV-URL wird unter <strong>Dateien → WebDAV</strong> angezeigt</li>
    5541</ol>
    5642
    57 <p><strong>Die URL sollte so aussehen:</strong></p>
     43<p><strong>Die URL sieht so aus:</strong></p>
    5844<pre>https://ihre-nextcloud.de/remote.php/dav/files/IHR-BENUTZERNAME/</pre>
    5945
    60 <p><strong>Wichtig:</strong> Die URL muss mit einem <code>/</code> (Schrägstrich) enden!</p>
     46<p><strong>Wichtig:</strong> Die URL muss mit einem <code>/</code> enden!</p>
    6147
    62 <h3>Schritt 3: Plugin konfigurieren</h3>
    63 
    64 <h4>Nextcloud-Verbindung einrichten:</h4>
     48<h3>Schritt 3: Verbindung einrichten</h3>
    6549
    6650<ol>
    6751    <li>Gehen Sie in WordPress zu <strong>Luzid WP Tools → Backup to Nextcloud</strong></li>
    68     <li>Im Tab <strong>Nextcloud</strong>:</li>
    69     <ul>
    70         <li><strong>WebDAV Base URL:</strong> Fügen Sie Ihre WebDAV-URL ein</li>
    71         <li><strong>Benutzername:</strong> Ihr Nextcloud-Benutzername</li>
    72         <li><strong>App-Passwort:</strong> Das in Schritt 1 erstellte App-Passwort</li>
    73         <li><strong>Remote Ordner:</strong> Name des Zielordners in Nextcloud (z.B. "WP-Backups")</li>
    74     </ul>
     52    <li>Im Tab <strong>Setup</strong> füllen Sie alle Felder aus:
     53        <ul>
     54            <li><strong>WebDAV Base URL:</strong> Ihre WebDAV-URL</li>
     55            <li><strong>Benutzername:</strong> Ihr Nextcloud-Benutzername</li>
     56            <li><strong>App-Passwort:</strong> Das in Schritt 1 erstellte Passwort. Mit dem Auge-Symbol können Sie das Passwort sichtbar machen, um es zu überprüfen.</li>
     57            <li><strong>Remote Ordner:</strong> Pfad zum Zielordner in Nextcloud (z.B. "/backups/wordpress")</li>
     58        </ul>
     59    </li>
    7560    <li>Klicken Sie auf <strong>Speichern & Testen</strong></li>
    76     <li>Wenn die Verbindung erfolgreich ist, sehen Sie eine Bestätigung</li>
     61    <li>Bei erfolgreicher Verbindung erscheint eine Bestätigung mit ✓</li>
    7762</ol>
    7863
    79 <p><strong>Hinweis:</strong> Der Zielordner wird automatisch erstellt, falls er nicht existiert.</p>
     64<p><strong>Hinweis:</strong> Der Zielordner wird automatisch erstellt, falls er nicht existiert. Im Setup-Tab gibt es nur den Button "Speichern & Testen" – damit werden Ihre Einstellungen gespeichert und die Verbindung gleichzeitig geprüft.</p>
    8065
    8166<h4>Email Reporting konfigurieren (Optional):</h4>
    8267
    83 <p>Im Bereich <strong>Reporting</strong> können Sie E-Mail-Benachrichtigungen einrichten:</p>
     68<p>Im Bereich <strong>Reporting</strong> (ebenfalls im Setup-Tab) können Sie E-Mail-Benachrichtigungen einrichten:</p>
    8469
    8570<ol>
     
    8873    <li>Wählen Sie aus:
    8974        <ul>
    90             <li><strong>Bei erfolgreichem Transfer:</strong> Benachrichtigung bei jedem erfolgreichen Backup-Upload</li>
     75            <li><strong>Bei erfolgreichem Transfer:</strong> Benachrichtigung bei jedem erfolgreichen Upload</li>
    9176            <li><strong>Bei fehlerhaftem Transfer:</strong> Benachrichtigung nur bei Fehlern (empfohlen)</li>
    9277        </ul>
    9378    </li>
    94     <li>Klicken Sie auf <strong>Einstellungen speichern</strong></li>
    9579</ol>
    9680
    97 <h4>Backup-Quellen aktivieren:</h4>
     81<h3>Schritt 4: Backup-Quellen aktivieren</h3>
    9882
    9983<ol>
    10084    <li>Wechseln Sie zum Tab <strong>Backup-Quellen</strong></li>
    10185    <li>Aktivieren Sie die Checkbox bei Ihrem Backup-Plugin (z.B. UpdraftPlus)</li>
    102     <li>Überprüfen Sie den <strong>Lokalen Pfad</strong> (wird automatisch gesetzt)</li>
    103     <li>Passen Sie die <strong>Dateiendungen</strong> an, falls nötig (z.B. <code>zip,gz,tar.gz</code>)</li>
    104     <li>Optional: Aktivieren Sie <strong>Nur echte Dateien scannen</strong> (überspringt Dateien mit Berechtigungsproblemen)</li>
     86    <li>Überprüfen Sie den <strong>Lokalen Pfad</strong> (wird automatisch vorausgefüllt)</li>
     87    <li>Passen Sie die <strong>Dateiendungen</strong> an, falls nötig</li>
    10588    <li>Klicken Sie auf <strong>Einstellungen speichern</strong></li>
    10689</ol>
    10790
    108 <p><strong>Unterstützte Backup-Plugins:</strong></p>
    109 <ul>
    110     <li>UpdraftPlus</li>
    111     <li>BackWPup</li>
    112     <li>WPvivid</li>
    113     <li>All-in-One WP Migration</li>
    114     <li>Duplicator</li>
    115     <li>Custom (für eigene Backup-Ordner)</li>
    116 </ul>
     91<p><strong>Unterstützte Backup-Plugins:</strong> UpdraftPlus, BackWPup, WPvivid, All-in-One WP Migration, Duplicator sowie eigene Ordner (Custom).</p>
    11792
    118 <h3>Schritt 4: Zeitplan einrichten (Optional)</h3>
     93<h3>Schritt 5: Ersten Upload testen</h3>
    11994
    120 <p>Sie können automatische Uploads zu bestimmten Zeiten einrichten.</p>
     95<ol>
     96    <li>Stellen Sie sicher, dass Ihr Backup-Plugin mindestens ein Backup erstellt hat</li>
     97    <li>Wechseln Sie zum Tab <strong>Backup-Quellen</strong> oder <strong>Zeitplan & Logs</strong></li>
     98    <li>Klicken Sie auf <strong>Upload manuell starten</strong></li>
     99    <li>Ein Fenster zeigt den Fortschritt an</li>
     100    <li>Warten Sie, bis "Upload erfolgreich abgeschlossen!" erscheint</li>
     101    <li>Prüfen Sie in Ihrer Nextcloud, ob die Dateien angekommen sind</li>
     102</ol>
     103
     104<p><strong>Hinweis:</strong> Der Button "Upload manuell starten" wird erst aktiv, wenn mindestens eine Backup-Quelle aktiviert wurde. Große Dateien werden automatisch per Streaming hochgeladen, um Speicher- und Timeout-Probleme zu vermeiden.</p>
     105
     106<h3>Schritt 6: Zeitplan einrichten (Optional)</h3>
    121107
    122108<ol>
    123109    <li>Gehen Sie zum Tab <strong>Zeitplan & Logs</strong></li>
    124110    <li>Aktivieren Sie <strong>Zeitplan aktivieren</strong></li>
    125     <li>Wählen Sie die <strong>Frequenz</strong>:</li>
    126     <ul>
    127         <li><strong>Täglich um:</strong> Upload jeden Tag zur gewählten Uhrzeit</li>
    128         <li><strong>Wöchentlich am:</strong> Upload einmal pro Woche an einem bestimmten Wochentag</li>
    129         <li><strong>Monatlich am:</strong> Upload einmal pro Monat an einem bestimmten Tag</li>
    130     </ul>
    131     <li>Wählen Sie die gewünschte <strong>Uhrzeit</strong> (z.B. 03:00 für 3 Uhr nachts)</li>
     111    <li>Wählen Sie die <strong>Frequenz</strong>:
     112        <ul>
     113            <li><strong>Täglich um:</strong> Upload jeden Tag zur gewählten Uhrzeit</li>
     114            <li><strong>Wöchentlich am:</strong> Upload einmal pro Woche</li>
     115            <li><strong>Monatlich am:</strong> Upload einmal pro Monat</li>
     116        </ul>
     117    </li>
     118    <li>Wählen Sie die gewünschte <strong>Uhrzeit</strong></li>
    132119    <li>Klicken Sie auf <strong>Einstellungen speichern</strong></li>
    133120</ol>
    134121
    135 <p><strong>Wichtig:</strong> WordPress Cron ist traffic-abhängig. Die Ausführung erfolgt, wenn nach der eingestellten Zeit Ihre Website besucht wird. Bei wenig Traffic kann es zu Verzögerungen kommen.</p>
     122<p><strong>Wichtig:</strong> WordPress Cron ist traffic-abhängig. Die Ausführung erfolgt, wenn nach der eingestellten Zeit Ihre Website besucht wird.</p>
    136123
    137124<h4>Rotation einrichten (Optional):</h4>
     
    141128<ol>
    142129    <li>Aktivieren Sie <strong>Rotation aktivieren</strong></li>
    143     <li>Geben Sie an, wie viele Backup-Sets pro Quelle behalten werden sollen (z.B. 10)</li>
    144     <li>Ältere Backups werden automatisch gelöscht</li>
    145 </ol>
    146 
    147 <h3>Schritt 5: Ersten Upload testen</h3>
    148 
    149 <ol>
    150     <li>Stellen Sie sicher, dass Ihr Backup-Plugin mindestens ein Backup erstellt hat</li>
    151     <li>Klicken Sie auf <strong>Upload manuell starten</strong></li>
    152     <li>Ein Fenster öffnet sich und zeigt den Fortschritt</li>
    153     <li>Warten Sie, bis "Upload erfolgreich abgeschlossen!" angezeigt wird</li>
    154     <li>Prüfen Sie in Ihrer Nextcloud, ob die Dateien angekommen sind</li>
     130    <li>Geben Sie an, wie viele Backup-Sets pro Quelle behalten werden sollen</li>
     131    <li>Ältere Backups werden bei jedem Upload-Durchlauf automatisch gelöscht</li>
    155132</ol>
    156133
     
    160137
    161138<pre>
    162 WP-Backups/
    163 ├── ihre-domain_de/
     139Ihr-Remote-Ordner/
     140├── ihre_domain_de/
    164141│   ├── updraftplus/
    165 │   │   ├── 20250207-1430/
    166 │   │   │   ├── backup-database.zip
     142│   │   ├── 2026-03-17-1430/
     143│   │   │   ├── backup-database.gz
    167144│   │   │   ├── backup-plugins.zip
    168 │   │   │   └── backup-themes.zip
    169 │   │   └── 20250206-1430/
     145│   │   │   ├── backup-themes.zip
     146│   │   │   └── backup-uploads.zip
     147│   │   └── 2026-03-16-1430/
    170148│   │       └── ...
    171149│   └── backwpup/
     
    173151</pre>
    174152
    175 <p>Jeder Backup-Durchlauf erhält einen eigenen Unterordner mit Zeitstempel.</p>
    176 
    177153<h3>Logs und Fehlerbehebung</h3>
    178154
    179 <h4>Logs anzeigen:</h4>
    180 
    181 <p>Im Tab <strong>Zeitplan & Logs</strong> finden Sie eine detaillierte Aufzeichnung aller Aktivitäten:</p>
    182 
    183 <ul>
    184     <li><strong>Info:</strong> Normale Informationen (z.B. "Backup upload started")</li>
    185     <li><strong>Success:</strong> Erfolgreiche Aktionen (z.B. "Uploaded: file.zip")</li>
    186     <li><strong>Warning:</strong> Warnungen (z.B. "Source path not found")</li>
    187     <li><strong>Error:</strong> Fehler (z.B. "Upload failed")</li>
    188 </ul>
     155<p>Im Tab <strong>Zeitplan & Logs</strong> finden Sie eine detaillierte Aufzeichnung aller Aktivitäten.</p>
    189156
    190157<h4>Häufige Probleme:</h4>
    191158
    192 <p><strong>Problem: Verbindungstest schlägt fehl</strong></p>
     159<p><strong>Verbindungstest schlägt fehl</strong></p>
    193160<ul>
    194161    <li>Überprüfen Sie die WebDAV-URL (muss mit / enden)</li>
    195     <li>Stellen Sie sicher, dass Sie Ihr <strong>App-Passwort</strong> verwenden, nicht Ihr Hauptpasswort</li>
     162    <li>Stellen Sie sicher, dass Sie ein <strong>App-Passwort</strong> verwenden, nicht Ihr Hauptpasswort</li>
     163    <li>Nutzen Sie das Auge-Symbol, um zu prüfen, ob Ihr Browser das Passwort-Feld automatisch mit einem falschen Passwort ausgefüllt hat</li>
    196164    <li>Prüfen Sie, ob Ihre Nextcloud von außen erreichbar ist</li>
    197     <li>Kontaktieren Sie ggf. Ihren Nextcloud-Administrator</li>
    198165</ul>
    199166
    200 <p><strong>Problem: Upload wird nicht gefunden</strong></p>
     167<p><strong>Keine Backup-Dateien gefunden</strong></p>
    201168<ul>
    202169    <li>Prüfen Sie im Tab "Backup-Quellen", ob der lokale Pfad korrekt ist</li>
    203170    <li>Stellen Sie sicher, dass Ihr Backup-Plugin tatsächlich Backups erstellt hat</li>
    204     <li>Überprüfen Sie die Dateiendungen (z.B. .zip, .gz)</li>
     171    <li>Überprüfen Sie die Dateiendungen</li>
    205172</ul>
    206173
    207 <p><strong>Problem: Zeitplan wird nicht ausgeführt</strong></p>
     174<p><strong>Upload bleibt hängen</strong></p>
    208175<ul>
    209     <li>WordPress Cron benötigt Website-Traffic zur Ausführung</li>
    210     <li>Prüfen Sie, ob "Zeitplan aktivieren" aktiviert ist</li>
    211     <li>Testen Sie zunächst mit "Upload manuell starten"</li>
     176    <li>Bei sehr großen Dateien (> 200 MB) kann der Upload einige Minuten dauern</li>
     177    <li>Das Plugin verwendet für große Dateien automatisch Streaming-Uploads, die wenig RAM benötigen</li>
     178    <li>Prüfen Sie die Logs auf Fehlermeldungen</li>
    212179</ul>
    213180
     
    217184    <li>Verwenden Sie <strong>immer</strong> ein Nextcloud App-Passwort, nie Ihr Hauptpasswort</li>
    218185    <li>Aktivieren Sie HTTPS/SSL für Ihre Nextcloud-Verbindung</li>
    219     <li>Schützen Sie Ihren WordPress-Admin-Bereich</li>
    220186    <li>Erstellen Sie regelmäßig Backups und testen Sie die Wiederherstellung</li>
    221     <li>Löschen Sie alte App-Passwörter, wenn Sie sie nicht mehr benötigen</li>
    222 </ul>
    223 
    224 <h3>Tipps und Best Practices</h3>
    225 
    226 <ul>
    227     <li><strong>Backup-Zeitplan:</strong> Führen Sie Backups nachts aus (geringere Last)</li>
    228     <li><strong>Rotation:</strong> Behalten Sie mindestens die letzten 7-14 Backup-Sets</li>
    229     <li><strong>Speicherplatz:</strong> Überwachen Sie Ihren Nextcloud-Speicher regelmäßig</li>
    230     <li><strong>Test:</strong> Testen Sie die Wiederherstellung aus Nextcloud</li>
    231     <li><strong>Mehrere Quellen:</strong> Sie können mehrere Backup-Plugins gleichzeitig verwenden</li>
    232187</ul>
    233188
     
    238193<ul>
    239194    <li>Prüfen Sie die Logs im Tab "Zeitplan & Logs"</li>
    240     <li>Schicken Sie eine Supportanfrage mit einer ausführlichen Fehlerbeschreibung an <a href="mailto:[email protected]">[email protected]</a></li>
     195    <li>Schicken Sie eine Supportanfrage an <a href="mailto:[email protected]">[email protected]</a></li>
    241196</ul>
    242 
    243 <p><strong>Viel Erfolg mit Ihren Backups!</strong></p>
  • luzid-backup-to-nextcloud/trunk/howto-en.php

    r3481769 r3484887  
    44 */
    55
    6 // Exit if accessed directly
    7 if (!defined('ABSPATH')) {
     6if ( ! defined( 'ABSPATH' ) ) {
    87    exit;
    98}
     
    1110<h2>Welcome to Luzid Backup to Nextcloud</h2>
    1211
    13 <p>This plugin allows you to automatically upload WordPress backup files to your Nextcloud. It is compatible with the most popular backup plugins and offers additional features such as scheduled uploads and automatic rotation of old backups.</p>
     12<p>This plugin allows you to automatically upload WordPress backup files to your Nextcloud. It is compatible with the most popular backup plugins and offers features such as scheduled uploads and automatic rotation of old backups.</p>
    1413
    1514<h3>Prerequisites</h3>
    16 
    17 <p>Before you begin, make sure you have:</p>
    1815
    1916<ul>
     
    2522<h3>Step 1: Create Nextcloud App Password</h3>
    2623
    27 <p>For security reasons, you should use an <strong>App Password</strong>, not your main password.</p>
    28 
    29 <h4>How to create an App Password:</h4>
     24<p>For security reasons, use an <strong>App Password</strong>, not your main password.</p>
    3025
    3126<ol>
    3227    <li>Log in to your Nextcloud</li>
    33     <li>Click on your profile picture in the top right</li>
    34     <li>Select <strong>Settings</strong></li>
    35     <li>Navigate to <strong>Security</strong> in the left menu</li>
    36     <li>Scroll to <strong>Devices & Sessions</strong></li>
     28    <li>Click your profile picture (top right) → <strong>Settings</strong></li>
     29    <li>Navigate to <strong>Security</strong> → <strong>Devices & Sessions</strong></li>
    3730    <li>Enter an app name (e.g. "WordPress Backup")</li>
    3831    <li>Click <strong>Create new app password</strong></li>
    39     <li>Copy the generated password (you'll only see it once!)</li>
     32    <li>Copy the generated password (you will only see it once!)</li>
    4033</ol>
    41 
    42 <p><strong>Important:</strong> Keep this password safe. You'll need it for the plugin configuration.</p>
    4334
    4435<h3>Step 2: Find Your WebDAV URL</h3>
    4536
    46 <p>The WebDAV URL is the address through which the plugin accesses your Nextcloud.</p>
    47 
    48 <h4>How to find your WebDAV URL:</h4>
    49 
    5037<ol>
    5138    <li>Log in to your Nextcloud</li>
    52     <li>Click on the <strong>Settings icon</strong> (gear) in the bottom left</li>
    53     <li>Go to <strong>Files</strong> → <strong>WebDAV</strong></li>
    54     <li>Copy the displayed WebDAV URL</li>
     39    <li>Click the <strong>Settings icon</strong> (gear) in the bottom left</li>
     40    <li>The WebDAV URL is shown under <strong>Files → WebDAV</strong></li>
    5541</ol>
    5642
    57 <p><strong>The URL should look like this:</strong></p>
     43<p><strong>The URL looks like this:</strong></p>
    5844<pre>https://your-nextcloud.com/remote.php/dav/files/YOUR-USERNAME/</pre>
    5945
    60 <p><strong>Important:</strong> The URL must end with a <code>/</code> (slash)!</p>
     46<p><strong>Important:</strong> The URL must end with a <code>/</code>!</p>
    6147
    62 <h3>Step 3: Configure the Plugin</h3>
    63 
    64 <h4>Set up Nextcloud connection:</h4>
     48<h3>Step 3: Set Up Connection</h3>
    6549
    6650<ol>
    6751    <li>In WordPress, go to <strong>Luzid WP Tools → Backup to Nextcloud</strong></li>
    68     <li>In the <strong>Nextcloud</strong> tab:</li>
    69     <ul>
    70         <li><strong>WebDAV Base URL:</strong> Paste your WebDAV URL</li>
    71         <li><strong>Username:</strong> Your Nextcloud username</li>
    72         <li><strong>App Password:</strong> The app password created in Step 1</li>
    73         <li><strong>Remote Folder:</strong> Name of the target folder in Nextcloud (e.g. "WP-Backups")</li>
    74     </ul>
     52    <li>In the <strong>Setup</strong> tab, fill in all fields:
     53        <ul>
     54            <li><strong>WebDAV Base URL:</strong> Your WebDAV URL</li>
     55            <li><strong>Username:</strong> Your Nextcloud username</li>
     56            <li><strong>App Password:</strong> The password from Step 1. Use the eye icon to reveal the password and verify it.</li>
     57            <li><strong>Remote Folder:</strong> Path to the target folder in Nextcloud (e.g. "/backups/wordpress")</li>
     58        </ul>
     59    </li>
    7560    <li>Click <strong>Save & Test</strong></li>
    76     <li>If the connection is successful, you'll see a confirmation</li>
     61    <li>A successful connection shows a ✓ confirmation</li>
    7762</ol>
    7863
    79 <p><strong>Note:</strong> The target folder will be created automatically if it doesn't exist.</p>
     64<p><strong>Note:</strong> The target folder is created automatically if it does not exist. The Setup tab only has the "Save & Test" button, which saves your settings and tests the connection in one step.</p>
    8065
    8166<h4>Configure Email Reporting (Optional):</h4>
    8267
    83 <p>In the <strong>Reporting</strong> section, you can set up email notifications:</p>
     68<p>In the <strong>Reporting</strong> section (also in the Setup tab), you can set up email notifications:</p>
    8469
    8570<ol>
     
    8873    <li>Choose:
    8974        <ul>
    90             <li><strong>On successful transfer:</strong> Notification for every successful backup upload</li>
     75            <li><strong>On successful transfer:</strong> Notification for every successful upload</li>
    9176            <li><strong>On failed transfer:</strong> Notification only for errors (recommended)</li>
    9277        </ul>
    9378    </li>
    94     <li>Click <strong>Save Settings</strong></li>
    9579</ol>
    9680
    97 <h4>Activate backup sources:</h4>
     81<h3>Step 4: Activate Backup Sources</h3>
    9882
    9983<ol>
    10084    <li>Switch to the <strong>Backup Sources</strong> tab</li>
    10185    <li>Check the box for your backup plugin (e.g. UpdraftPlus)</li>
    102     <li>Verify the <strong>Local Path</strong> (set automatically)</li>
    103     <li>Adjust <strong>File Extensions</strong> if needed (e.g. <code>zip,gz,tar.gz</code>)</li>
    104     <li>Optional: Enable <strong>Scan real files only</strong> (skips files with permission issues)</li>
     86    <li>Verify the <strong>Local Path</strong> (pre-filled automatically)</li>
     87    <li>Adjust <strong>File Extensions</strong> if needed</li>
    10588    <li>Click <strong>Save Settings</strong></li>
    10689</ol>
    10790
    108 <p><strong>Supported backup plugins:</strong></p>
    109 <ul>
    110     <li>UpdraftPlus</li>
    111     <li>BackWPup</li>
    112     <li>WPvivid</li>
    113     <li>All-in-One WP Migration</li>
    114     <li>Duplicator</li>
    115     <li>Custom (for your own backup folders)</li>
    116 </ul>
     91<p><strong>Supported backup plugins:</strong> UpdraftPlus, BackWPup, WPvivid, All-in-One WP Migration, Duplicator, and custom folders.</p>
    11792
    118 <h3>Step 4: Set Up Schedule (Optional)</h3>
     93<h3>Step 5: Test First Upload</h3>
    11994
    120 <p>You can set up automatic uploads at specific times.</p>
     95<ol>
     96    <li>Make sure your backup plugin has created at least one backup</li>
     97    <li>Switch to the <strong>Backup Sources</strong> or <strong>Schedule & Logs</strong> tab</li>
     98    <li>Click <strong>Start Manual Upload</strong></li>
     99    <li>A window shows the upload progress</li>
     100    <li>Wait until "Upload completed successfully!" is displayed</li>
     101    <li>Check your Nextcloud to verify the files arrived</li>
     102</ol>
     103
     104<p><strong>Note:</strong> The "Start Manual Upload" button only becomes active once at least one backup source has been enabled. Large files are automatically uploaded via streaming to avoid memory and timeout issues.</p>
     105
     106<h3>Step 6: Set Up Schedule (Optional)</h3>
    121107
    122108<ol>
    123109    <li>Go to the <strong>Schedule & Logs</strong> tab</li>
    124110    <li>Enable <strong>Enable Schedule</strong></li>
    125     <li>Choose the <strong>Frequency</strong>:</li>
    126     <ul>
    127         <li><strong>Daily at:</strong> Upload every day at the chosen time</li>
    128         <li><strong>Weekly on:</strong> Upload once per week on a specific weekday</li>
    129         <li><strong>Monthly on:</strong> Upload once per month on a specific day</li>
    130     </ul>
    131     <li>Select the desired <strong>Time</strong> (e.g. 03:00 for 3 AM)</li>
     111    <li>Choose the <strong>Frequency</strong>:
     112        <ul>
     113            <li><strong>Daily at:</strong> Upload every day at the chosen time</li>
     114            <li><strong>Weekly on:</strong> Upload once per week</li>
     115            <li><strong>Monthly on:</strong> Upload once per month</li>
     116        </ul>
     117    </li>
     118    <li>Select the desired <strong>Time</strong></li>
    132119    <li>Click <strong>Save Settings</strong></li>
    133120</ol>
    134121
    135 <p><strong>Important:</strong> WordPress Cron is traffic-based. Execution occurs when your website is visited after the set time. With low traffic, delays may occur.</p>
     122<p><strong>Important:</strong> WordPress Cron is traffic-based. Execution occurs when your website is visited after the scheduled time.</p>
    136123
    137 <h4>Set up rotation (Optional):</h4>
     124<h4>Set Up Rotation (Optional):</h4>
    138125
    139126<p>Rotation automatically deletes old backups in Nextcloud to save storage space.</p>
     
    141128<ol>
    142129    <li>Enable <strong>Enable Rotation</strong></li>
    143     <li>Specify how many backup sets per source should be kept (e.g. 10)</li>
    144     <li>Older backups will be automatically deleted</li>
    145 </ol>
    146 
    147 <h3>Step 5: Test First Upload</h3>
    148 
    149 <ol>
    150     <li>Make sure your backup plugin has created at least one backup</li>
    151     <li>Click <strong>Start Manual Upload</strong></li>
    152     <li>A window opens showing the progress</li>
    153     <li>Wait until "Upload completed successfully!" is displayed</li>
    154     <li>Check in your Nextcloud if the files have arrived</li>
     130    <li>Specify how many backup sets per source should be kept</li>
     131    <li>Older backups are automatically deleted during each upload run</li>
    155132</ol>
    156133
     
    160137
    161138<pre>
    162 WP-Backups/
    163 ├── your-domain_com/
     139Your-Remote-Folder/
     140├── your_domain_com/
    164141│   ├── updraftplus/
    165 │   │   ├── 20250207-1430/
    166 │   │   │   ├── backup-database.zip
     142│   │   ├── 2026-03-17-1430/
     143│   │   │   ├── backup-database.gz
    167144│   │   │   ├── backup-plugins.zip
    168 │   │   │   └── backup-themes.zip
    169 │   │   └── 20250206-1430/
     145│   │   │   ├── backup-themes.zip
     146│   │   │   └── backup-uploads.zip
     147│   │   └── 2026-03-16-1430/
    170148│   │       └── ...
    171149│   └── backwpup/
     
    173151</pre>
    174152
    175 <p>Each backup run gets its own subfolder with timestamp.</p>
    176 
    177153<h3>Logs and Troubleshooting</h3>
    178154
    179 <h4>View logs:</h4>
     155<p>In the <strong>Schedule & Logs</strong> tab you will find a detailed record of all activities.</p>
    180156
    181 <p>In the <strong>Schedule & Logs</strong> tab you'll find a detailed record of all activities:</p>
     157<h4>Common Issues:</h4>
    182158
     159<p><strong>Connection test fails</strong></p>
    183160<ul>
    184     <li><strong>Info:</strong> Normal information (e.g. "Backup upload started")</li>
    185     <li><strong>Success:</strong> Successful actions (e.g. "Uploaded: file.zip")</li>
    186     <li><strong>Warning:</strong> Warnings (e.g. "Source path not found")</li>
    187     <li><strong>Error:</strong> Errors (e.g. "Upload failed")</li>
     161    <li>Check the WebDAV URL (must end with /)</li>
     162    <li>Make sure you are using an <strong>App Password</strong>, not your main password</li>
     163    <li>Use the eye icon to check whether your browser auto-filled the password field with the wrong password</li>
     164    <li>Check if your Nextcloud is accessible from the internet</li>
    188165</ul>
    189166
    190 <h4>Common issues:</h4>
    191 
    192 <p><strong>Issue: Connection test fails</strong></p>
    193 <ul>
    194     <li>Check the WebDAV URL (must end with /)</li>
    195     <li>Make sure you're using your <strong>App Password</strong>, not your main password</li>
    196     <li>Check if your Nextcloud is accessible from outside</li>
    197     <li>Contact your Nextcloud administrator if necessary</li>
    198 </ul>
    199 
    200 <p><strong>Issue: Upload not found</strong></p>
     167<p><strong>No backup files found</strong></p>
    201168<ul>
    202169    <li>Check in the "Backup Sources" tab if the local path is correct</li>
    203170    <li>Make sure your backup plugin has actually created backups</li>
    204     <li>Verify the file extensions (e.g. .zip, .gz)</li>
     171    <li>Verify the file extensions</li>
    205172</ul>
    206173
    207 <p><strong>Issue: Schedule not executing</strong></p>
     174<p><strong>Upload gets stuck</strong></p>
    208175<ul>
    209     <li>WordPress Cron requires website traffic to execute</li>
    210     <li>Check if "Enable Schedule" is enabled</li>
    211     <li>Test first with "Start Manual Upload"</li>
     176    <li>Very large files (> 200 MB) may take several minutes to upload</li>
     177    <li>The plugin automatically uses streaming uploads for large files, which require very little RAM</li>
     178    <li>Check the logs for error messages</li>
    212179</ul>
    213180
     
    217184    <li><strong>Always</strong> use a Nextcloud App Password, never your main password</li>
    218185    <li>Enable HTTPS/SSL for your Nextcloud connection</li>
    219     <li>Protect your WordPress admin area</li>
    220186    <li>Create regular backups and test restoration</li>
    221     <li>Delete old app passwords when you no longer need them</li>
    222 </ul>
    223 
    224 <h3>Tips and Best Practices</h3>
    225 
    226 <ul>
    227     <li><strong>Backup schedule:</strong> Run backups at night (lower load)</li>
    228     <li><strong>Rotation:</strong> Keep at least the last 7-14 backup sets</li>
    229     <li><strong>Storage:</strong> Monitor your Nextcloud storage regularly</li>
    230     <li><strong>Test:</strong> Test restoration from Nextcloud</li>
    231     <li><strong>Multiple sources:</strong> You can use multiple backup plugins simultaneously</li>
    232187</ul>
    233188
     
    238193<ul>
    239194    <li>Check the logs in the "Schedule & Logs" tab</li>
    240     <li>Send a support request with a detailed error description to <a href="mailto:[email protected]">[email protected]</a></li>
     195    <li>Send a support request to <a href="mailto:[email protected]">[email protected]</a></li>
    241196</ul>
    242 
    243 <p><strong>Good luck with your backups!</strong></p>
  • luzid-backup-to-nextcloud/trunk/luzid-backup-to-nextcloud.php

    r3481809 r3484887  
    33 * Plugin Name: Luzid Backup to Nextcloud
    44 * Description: Upload WordPress backup files to Nextcloud via WebDAV with rotation and retention management
    5  * Version: 1.2.10
     5 * Version: 1.3.0
    66 * Author: Luzid Media
    77 * Text Domain: luzid-backup-to-nextcloud
     
    1616
    1717// Define plugin constants
    18 define('LUZID_BACKUP_VERSION', '1.2.9');
     18define('LUZID_BACKUP_VERSION', '1.3.0');
    1919define('LUZID_BACKUP_PLUGIN_FILE', __FILE__);
    2020define('LUZID_BACKUP_PLUGIN_DIR', plugin_dir_path(__FILE__));
     
    376376                sanitize_text_field($input['webdav_username']) : '';
    377377
    378             if (isset($input['remove_password']) && $input['remove_password']) {
    379                 $sanitized['webdav_password'] = '';
    380             } elseif (isset($input['webdav_password']) && !empty($input['webdav_password'])) {
     378            if (isset($input['webdav_password']) && !empty($input['webdav_password'])) {
    381379                // Store as entered (app password)
    382380                $sanitized['webdav_password'] = $input['webdav_password'];
     
    409407                $sanitized['sources'][$source_id] = array(
    410408                    'enabled' => !empty($posted['enabled']) ? 1 : 0,
    411                     'path' => isset($posted['path']) ? sanitize_text_field($posted['path']) : $default_config['path'],
    412                     'extensions' => isset($posted['extensions']) ? sanitize_text_field($posted['extensions']) : $default_config['extensions'],
     409                    'path' => !empty($posted['path']) ? sanitize_text_field($posted['path']) : $default_config['path'],
     410                    'extensions' => !empty($posted['extensions']) ? sanitize_text_field($posted['extensions']) : $default_config['extensions'],
    413411                    'scan_real_files' => !empty($posted['scan_real_files']) ? 1 : 0,
    414412                );
     
    480478        $lang = $this->get_current_lang();
    481479        $options = get_option('luzid_backup_options', $this->get_default_options());
     480        // Ensure sources always contain default paths and extensions.
     481        $default_sources = $this->get_default_sources();
     482        if ( empty($options['sources']) || ! is_array($options['sources']) ) {
     483            $options['sources'] = $default_sources;
     484        } else {
     485            foreach ( $default_sources as $src_id => $src_defaults ) {
     486                if ( ! isset($options['sources'][$src_id]) ) {
     487                    $options['sources'][$src_id] = $src_defaults;
     488                } else {
     489                    $options['sources'][$src_id] = wp_parse_args($options['sources'][$src_id], $src_defaults);
     490                }
     491            }
     492        }
    482493       
    483494        $current_url = remove_query_arg('luzid_lang');
     
    492503       
    493504        if (isset($_POST['luzid_backup_save']) && check_admin_referer('luzid_backup_save')) {
    494             // Extract only plugin-specific fields to avoid processing entire $_POST
     505            // Extract only plugin-specific fields to avoid processing entire $_POST.
     506            $luzid_btn_pw_field = isset($_POST['luzid_btn_app_password']) ? sanitize_text_field(wp_unslash($_POST['luzid_btn_app_password'])) : '';
    495507            $input = array(
    496508                'active_tab'             => isset($_POST['active_tab']) ? sanitize_text_field(wp_unslash($_POST['active_tab'])) : '',
    497509                'webdav_url'             => isset($_POST['webdav_url']) ? esc_url_raw(wp_unslash($_POST['webdav_url'])) : '',
    498510                'webdav_username'        => isset($_POST['webdav_username']) ? sanitize_text_field(wp_unslash($_POST['webdav_username'])) : '',
    499                 'webdav_password'        => isset($_POST['webdav_password']) ? sanitize_text_field(wp_unslash($_POST['webdav_password'])) : '',
    500                 'remove_password'        => isset($_POST['remove_password']) ? sanitize_text_field(wp_unslash($_POST['remove_password'])) : '',
     511                'webdav_password'        => $luzid_btn_pw_field,
    501512                'remote_folder'          => isset($_POST['remote_folder']) ? sanitize_text_field(wp_unslash($_POST['remote_folder'])) : '',
    502513                'email_reporting_enabled' => isset($_POST['email_reporting_enabled']) ? absint($_POST['email_reporting_enabled']) : 0,
     
    504515                'email_on_success'       => isset($_POST['email_on_success']) ? absint($_POST['email_on_success']) : 0,
    505516                'email_on_error'         => isset($_POST['email_on_error']) ? absint($_POST['email_on_error']) : 0,
    506                 'sources'                => isset($_POST['sources']) && is_array($_POST['sources']) ? map_deep(wp_unslash($_POST['sources']), 'sanitize_text_field') : array(),
     517                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nested array; each value is sanitized individually in sanitize_options().
     518                'sources'                => isset($_POST['sources']) && is_array($_POST['sources']) ? wp_unslash($_POST['sources']) : array(),
    507519                'schedule_enabled'       => isset($_POST['schedule_enabled']) ? absint($_POST['schedule_enabled']) : 0,
    508520                'schedule_frequency'     => isset($_POST['schedule_frequency']) ? sanitize_text_field(wp_unslash($_POST['schedule_frequency'])) : '',
     
    549561            'webdav_username' => '',
    550562            'webdav_password' => '',
    551             'remote_folder' => 'WP-Backups',
     563            'remote_folder' => '',
    552564            'email_reporting_enabled' => 0,
    553565            'email_address' => '',
     
    631643                'username' => 'Benutzername',
    632644                'password' => 'App-Passwort',
    633                 'password_desc' => 'Leer lassen = Passwort behalten. Verwenden Sie ein Nextcloud App-Passwort.',
    634                 'remove_password' => 'Passwort entfernen',
     645                'password_desc' => 'Verwenden Sie ein Nextcloud App-Passwort.',
    635646                'remote_folder' => 'Remote Ordner',
    636647                'remote_folder_desc' => 'Zielordner in Nextcloud, z.B. WP-Backups',
     
    649660                'source_scan_real' => 'Nur echte Dateien scannen',
    650661                'source_scan_real_desc' => 'Wenn aktiv, werden nur lesbare Dateien berücksichtigt (überspringt Dateien mit Berechtigungsproblemen).',
    651                 'select_source_hint' => 'Bitte wähle eine Backup-Quelle',
     662                'select_source_hint' => 'Fülle alle Felder aus und drücke anschließend "Speichern & Testen". Wenn die Verbindung erfolgreich ist, wähle anschließend eine Backup-Quelle.',
    652663                'schedule_title' => 'Zeitplan',
    653664                'schedule_enabled' => 'Zeitplan aktivieren',
     
    703714                'username' => 'Username',
    704715                'password' => 'App Password',
    705                 'password_desc' => 'Leave empty to keep existing. Use Nextcloud app password.',
    706                 'remove_password' => 'Remove password',
     716                'password_desc' => 'Use a Nextcloud app password.',
    707717                'remote_folder' => 'Remote Folder',
    708718                'remote_folder_desc' => 'Target folder in Nextcloud, e.g. WP-Backups',
     
    721731                'source_scan_real' => 'Scan real files only',
    722732                'source_scan_real_desc' => 'If enabled, only readable files are included (skips files with permission issues).',
    723                 'select_source_hint' => 'Please select a backup source',
     733                'select_source_hint' => 'Fill in all fields and press "Save & Test". Once the connection is successful, select a backup source.',
    724734                'schedule_title' => 'Schedule',
    725735                'schedule_enabled' => 'Enable Schedule',
     
    777787        }
    778788       
    779         // Extract only plugin-specific fields to avoid processing entire $_POST
     789        // Extract only plugin-specific fields to avoid processing entire $_POST.
     790        $luzid_btn_pw_field = isset($_POST['luzid_btn_app_password']) ? sanitize_text_field(wp_unslash($_POST['luzid_btn_app_password'])) : '';
    780791        $input = array(
    781792            'active_tab'             => isset($_POST['active_tab']) ? sanitize_text_field(wp_unslash($_POST['active_tab'])) : '',
    782793            'webdav_url'             => isset($_POST['webdav_url']) ? esc_url_raw(wp_unslash($_POST['webdav_url'])) : '',
    783794            'webdav_username'        => isset($_POST['webdav_username']) ? sanitize_text_field(wp_unslash($_POST['webdav_username'])) : '',
    784             'webdav_password'        => isset($_POST['webdav_password']) ? sanitize_text_field(wp_unslash($_POST['webdav_password'])) : '',
    785             'remove_password'        => isset($_POST['remove_password']) ? sanitize_text_field(wp_unslash($_POST['remove_password'])) : '',
     795            'webdav_password'        => $luzid_btn_pw_field,
    786796            'remote_folder'          => isset($_POST['remote_folder']) ? sanitize_text_field(wp_unslash($_POST['remote_folder'])) : '',
    787797            'email_reporting_enabled' => isset($_POST['email_reporting_enabled']) ? absint($_POST['email_reporting_enabled']) : 0,
     
    866876        }
    867877       
    868         delete_option('luzid_backup_options');
     878        // Remove logs, state and cron schedule.
    869879        delete_option('luzid_backup_state');
    870880        delete_option('luzid_backup_logs');
     881        wp_clear_scheduled_hook('luzid_backup_cron_hook');
     882
     883        // Reset options to defaults (preserves source paths and file extensions).
     884        update_option('luzid_backup_options', $this->get_default_options());
    871885       
    872886        $lang = $this->get_current_lang();
     
    11851199        $remote_url = $options['webdav_url'] . ltrim($remote_path, '/') . $filename;
    11861200        $auth = base64_encode($options['webdav_username'] . ':' . $options['webdav_password']);
    1187         $this->log('info', "Uploading: {$filename}");
    1188        
    1189         // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Large file streaming needed for WebDAV PUT
     1201        $filesize = filesize($local_path);
     1202        $filesize_mb = round($filesize / 1048576, 1);
     1203        $this->log('info', "Uploading: {$filename} ({$filesize_mb} MB)");
     1204       
     1205        // Attempt to extend PHP limits for large uploads.
     1206        // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, Squiz.PHP.DiscouragedFunctions.Discouraged -- Graceful timeout extension for large file uploads.
     1207        @set_time_limit(600);
     1208       
     1209        // Large files (>2 MB): use cURL streaming to avoid loading entire file into RAM.
     1210        if ($filesize > 2 * 1048576 && function_exists('curl_init')) {
     1211            return $this->upload_file_curl($local_path, $remote_url, $auth, $filename);
     1212        }
     1213       
     1214        // Small files: use wp_remote_request (keeps full WP compatibility).
     1215        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reading local backup file for WebDAV PUT.
    11901216        $file_data = file_get_contents($local_path);
    11911217        if ($file_data === false) {
     
    12011227            ),
    12021228            'body' => $file_data,
    1203             'timeout' => 300, // 5 minutes for large files
     1229            'timeout' => 300,
    12041230        ));
    12051231       
     
    12121238        if ($http_code >= 200 && $http_code < 300) {
    12131239            $this->log('success', "Uploaded: {$filename}");
     1240            return true;
     1241        } else {
     1242            $this->log('error', "Upload failed: {$filename} (HTTP {$http_code})");
     1243            return false;
     1244        }
     1245    }
     1246   
     1247    /**
     1248     * Upload a file via cURL with streaming (reads from disk, never loads entire file into RAM).
     1249     */
     1250    private function upload_file_curl($local_path, $remote_url, $auth, $filename) {
     1251        // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_init -- Required for streaming large files that exceed PHP memory limits.
     1252        $ch = curl_init();
     1253       
     1254        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Opening local backup file for streaming upload.
     1255        $fh = fopen($local_path, 'rb');
     1256        if ( ! $fh ) {
     1257            $this->log('error', "Cannot open file for streaming: {$filename}");
     1258            return false;
     1259        }
     1260       
     1261        $filesize = filesize($local_path);
     1262       
     1263        // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt_array -- Required for streaming PUT upload with CURLOPT_INFILE; no wp_remote_request equivalent.
     1264        curl_setopt_array($ch, array(
     1265            CURLOPT_URL            => $remote_url,
     1266            CURLOPT_PUT            => true,
     1267            CURLOPT_INFILE         => $fh,
     1268            CURLOPT_INFILESIZE     => $filesize,
     1269            CURLOPT_RETURNTRANSFER => true,
     1270            CURLOPT_TIMEOUT        => 600,
     1271            CURLOPT_CONNECTTIMEOUT => 30,
     1272            CURLOPT_HTTPHEADER     => array(
     1273                'Authorization: Basic ' . $auth,
     1274                'Content-Type: application/octet-stream',
     1275            ),
     1276            CURLOPT_SSL_VERIFYPEER => true,
     1277        ));
     1278       
     1279        // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_exec
     1280        $result = curl_exec($ch);
     1281        // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_getinfo
     1282        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
     1283        // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_error
     1284        $curl_error = curl_error($ch);
     1285        // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_close
     1286        curl_close($ch);
     1287        fclose($fh); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing file handle opened for cURL streaming.
     1288       
     1289        if ( ! empty($curl_error) ) {
     1290            $this->log('error', "Upload failed (cURL): {$filename} - {$curl_error}");
     1291            return false;
     1292        }
     1293       
     1294        if ($http_code >= 200 && $http_code < 300) {
     1295            $this->log('success', "Uploaded: {$filename} (streamed)");
    12141296            return true;
    12151297        } else {
  • luzid-backup-to-nextcloud/trunk/readme.txt

    r3481809 r3484887  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.2.10
     7Stable tag: 1.3.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    1717It is designed for two main workflows:
    1818
    19 1) **Automate offsite copies**: upload backup archives on a schedule (WordPress Cron)
    20 2) **Manual runs + monitoring**: start uploads manually and follow progress + logs
     191. **Automate offsite copies**: upload backup archives on a schedule (WordPress Cron).
     202. **Manual runs + monitoring**: start uploads manually and follow progress + logs.
    2121
    2222Typical use cases:
    2323
    2424* Keep an offsite copy of backup archives generated by popular backup plugins.
    25 * Upload backups to a structured Nextcloud folder (per source).
     25* Upload backups to a structured Nextcloud folder (per source, per domain).
    2626* Reduce storage usage with optional rotation (keep only the last N backup sets per source).
    2727
    2828Core concepts:
    2929
    30 * **Sources**: each source represents a folder where backup files are created (UpdraftPlus, BackWPup, … or custom).
    31 * **Upload**: the plugin scans sources for backup files and uploads them to Nextcloud.
    32 * **Schedule**: optional daily/weekly/monthly runs at a fixed time (WordPress Cron; traffic-dependent).
    33 * **Rotation**: keep the newest N backup sets and remove older ones on Nextcloud.
    34 * **Email Reporting**: get notified on successful or failed transfers.
     30* **Sources** – each source represents a folder where backup files are created (UpdraftPlus, BackWPup, WPvivid, All-in-One WP Migration, Duplicator, or custom).
     31* **Upload** – the plugin scans sources for backup files and uploads them to Nextcloud via WebDAV PUT.
     32* **Streaming** – files larger than 2 MB are uploaded via cURL streaming (reads from disk, never loads the entire file into RAM). This prevents memory and timeout issues on shared hosting.
     33* **Schedule** – optional daily/weekly/monthly runs at a fixed time (WordPress Cron; traffic-dependent).
     34* **Rotation** – keep the newest N backup sets and remove older ones on Nextcloud.
     35* **Email Reporting** – get notified on successful or failed transfers.
    3536
    3637== Features ==
    3738
    38 * Nextcloud WebDAV connection (URL, username, app password)
    39 * Multiple backup sources (known plugins + custom)
     39* Nextcloud WebDAV connection with one-click Save & Test
     40* Password field with visibility toggle and browser autofill protection
     41* Multiple backup sources (known plugins + custom folders)
    4042* File filters by extension
     43* Streaming uploads for large files (low RAM usage)
    4144* Manual upload with progress modal
    4245* Scheduled runs: Daily, Weekly, or Monthly (WordPress Cron)
    4346* Email notifications for successful/failed transfers
    44 * Logs viewer + clear logs
     47* Log viewer with clear function
    4548* Rotation / retention (keep last N backup sets)
    46 * German / English admin UI
     49* Context-aware action buttons per tab
     50* German and English admin UI with integrated How-To guide
    4751
    4852== Installation ==
     
    5458== Usage ==
    5559
    56 = Configure Nextcloud =
     60= 1. Setup (connection) =
    5761
    58 1. Enter your WebDAV base URL (must end with `/`).
    59 2. Enter username + **Nextcloud App Password**.
    60 3. Set a target folder name.
    61 4. Click **Save & Test**.
     621. Fill in WebDAV URL, username, app password, and remote folder.
     632. Click **Save & Test** – this saves settings and tests the connection in one step.
    6264
    63 = Configure sources =
     65= 2. Backup Sources =
    6466
    65 1. Enable one or more sources.
    66 2. Adjust local paths (if needed).
    67 3. Set file extensions to include (comma-separated).
     671. Switch to the **Backup Sources** tab.
     682. Enable one or more sources (paths and extensions are pre-filled).
     693. Click **Save Settings**.
    6870
    69 = Schedule + rotation =
     71= 3. Manual Upload =
    7072
    71 * Enable the schedule and set a time.
     731. Click **Start Manual Upload** (available once a source is enabled).
     742. A progress modal shows the upload status.
     75
     76= 4. Schedule + Rotation =
     77
     78* Enable the schedule, choose frequency and time, then save.
    7279* Enable rotation and choose how many backup sets to keep.
    7380
     
    8491= Should I use a normal password or an app password? =
    8592
    86 Use a Nextcloud **App Password**.
     93Always use a Nextcloud **App Password** (Settings → Security → Devices & Sessions).
     94
     95= My upload gets stuck on large files =
     96
     97The plugin streams files larger than 2 MB via cURL to avoid PHP memory limits. If uploads still time out, check your hosting provider's PHP `max_execution_time` setting.
     98
     99= The password field is filled with the wrong password =
     100
     101Click the eye icon next to the password field to reveal and verify the content. The field uses `autocomplete="new-password"` to prevent browser autofill, but some browsers may still fill it. Clear the field and enter your Nextcloud app password manually.
    87102
    88103== License ==
     
    90105This plugin is licensed under the **GNU General Public License v2.0 or later**.
    91106
    92 Assets:
     107== Changelog ==
    93108
    94 * Logo + flag icons are shipped as SVG assets in `assets/img/`.
    95 
    96 == Screenshots ==
    97 
    98 1. Settings: Configure your Nextcloud WebDAV connection and select auto-detected backup sources.
    99 2. Backup Sources: See which backup plugin folders/files were found and what will be uploaded.
    100 3. Logs / Status: Review upload results, last run, and any errors.
    101 
    102 == Changelog ==
     109= 1.3.0 =
     110* **Streaming uploads**: Files > 2 MB are now uploaded via cURL streaming (reads from disk, never loads entire file into RAM). Solves timeout and memory issues on shared hosting with large backup files.
     111* **Context-aware buttons**: Each tab shows only the relevant action buttons. Setup shows only "Save & Test"; Backup Sources and Schedule show "Save Settings"; How-To hides all buttons.
     112* **Upload button gating**: "Start Manual Upload" is disabled (greyed out) until at least one backup source is enabled.
     113* **Password field improvements**: Visibility toggle (eye icon), `autocomplete="new-password"` to prevent browser autofill, renamed form field name to avoid browser recognition.
     114* **Removed "Remove password" checkbox**: Passwords are now simply replaced by entering a new one.
     115* **Default source paths**: Backup source paths and file extensions always fall back to sensible defaults (even after reset). Empty fields are never saved – defaults are restored automatically.
     116* **"Delete All Settings" preserves defaults**: Resetting the plugin now keeps default source paths and extensions intact, only clears user configuration, logs, and schedule.
     117* **Guided setup flow**: New hint text on Setup tab guides users through the configuration process.
     118* **Updated How-To guides**: German and English documentation rewritten to match current UI and features.
     119* **Fixed**: `$log` / `$source` / `$s` variable reference bugs from prefix renaming.
     120* **Fixed**: Sources tab fields (path, extensions) now save correctly.
    103121
    104122= 1.2.9 =
     
    106124* Fixed: Removed invalid Author URI (luzid.app)
    107125* Fixed: Contributors changed from "luzid" to "luzidmedia"
    108 * Fixed: Replaced `WP_CONTENT_DIR` constant with `wp_normalize_path( dirname( wp_upload_dir()['basedir'] ) )`
    109 * Fixed: `$_POST` is no longer processed as a whole; explicit field extraction in render_admin_page() and save_and_test_connection()
    110 * Fixed: JavaScript object name `luzidBackup` renamed to `luzid_backup_data` (prefixed)
     126* Fixed: Replaced `WP_CONTENT_DIR` constant with `wp_normalize_path( dirname() )`
     127* Fixed: Explicit field extraction from `$_POST` instead of processing the whole stack
     128* Fixed: JS object name `luzidBackup` renamed to `luzid_backup_data` (prefixed)
    111129* Fixed: All global variables in admin-page.php prefixed with `luzid_btn_`
    112 * Fixed: Added proper `phpcs:ignore` annotations with justifications for display-only `$_GET` usage
    113 * Fixed: `$_GET['test_result']` now sanitized with `sanitize_text_field( wp_unslash() )`
    114 * Security: `webdav_password` sanitized via `sanitize_text_field()` before processing
    115 * Security: Nested `$_POST['sources']` array sanitized via `map_deep()` with `sanitize_text_field`
    116130
    117131= 1.2.4 =
    118 * **CRITICAL FIX:** Settings now save correctly on first install
    119 * Fixed: active_tab defaults to 'tab-nextcloud' instead of empty string
    120 * Fixed: WebDAV credentials and settings persist after "Save & Test"
    121 * Improved: Better fallback handling for tab detection
     132* Settings now save correctly on first install
     133* WebDAV credentials persist after "Save & Test"
     134* Better fallback handling for tab detection
    122135
    123136= 1.2.3 =
    124 * **CRITICAL:** Replaced cURL with wp_remote_request() for WordPress compliance
    125 * Fixed: parse_url() replaced with wp_parse_url()
    126 * Fixed: File upload now uses WordPress HTTP API instead of cURL/fopen
    127 * Fixed: Added phpcs:ignore for remaining display-only $_GET usage
    128 * Performance: Increased timeout to 300s for large file uploads
    129 * Compliance: 100% WordPress.org Plugin Check compliant (no ERRORs)
     137* Replaced cURL with wp_remote_request() for WordPress compliance
     138* All PHPCS warnings resolved
    130139
    131140= 1.2.2 =
    132 * Fixed: All date() replaced with gmdate() for timezone safety
    133 * Fixed: All wp_redirect() replaced with wp_safe_redirect()
    134 * Fixed: Added wp_unslash() before sanitize_text_field() everywhere
    135 * Fixed: Added phpcs:ignore comments for legitimate nonce-free $_GET usage
    136 * Compliance: Fully WordPress.org Plugin Check compliant
     141* All date() replaced with gmdate()
     142* All wp_redirect() replaced with wp_safe_redirect()
     143* Proper wp_unslash() + sanitize everywhere
    137144
    138145= 1.2.1 =
    139 * Initial public release
    140 * **Core Features:**
    141   - Automatic backup uploads to Nextcloud via WebDAV
    142   - Multiple backup source support (UpdraftPlus, BackWPup, etc.)
    143   - Schedule options: Daily, Weekly, Monthly with custom times
    144   - Automatic rotation/retention: Keep last N backups
    145   - Email notifications for successful/failed transfers
    146   - Real-time upload progress tracking
    147   - Comprehensive logging system
    148 * **Security & Compliance:**
    149   - WordPress.org coding standards compliant
    150   - Proper nonce verification and data sanitization
    151   - Secure credential storage
    152   - ABSPATH protection on all files
    153 * **User Experience:**
    154   - German and English interface
    155   - Detailed HowTo guides with screenshots
    156   - Conflict resolution for backup files
    157   - Easy WebDAV configuration and testing
     146* Initial public release with full feature set
    158147
    159148= 1.2.0 =
    160 * Beta release for testing
    161 * Changed: Tab "Nextcloud" renamed to "Setup"
    162 * Added: Email Reporting feature with success/error notifications
    163 * Changed: "Rotation" heading renamed to "Anzahl Backups" (DE) / "Backup Retention" (EN)
    164 * Fixed: Cron scheduling now properly reschedules after each run (important for weekly/monthly)
    165 * Fixed: Rotation now logs detailed information for troubleshooting
    166 * Updated: Support email changed to [email protected]
    167 
    168 = 1.2.8 =
    169 * Added: Schedule frequency options (Daily, Weekly, Monthly)
    170 * Fixed: Upload modal now shows "You can close the window now" when complete
    171 * Added: Complete German and English HowTo documentation
    172 * Fixed: JavaScript code compliance for WordPress.org (wp_add_inline_script)
    173 * Improved: Schedule Cron calculations for weekly and monthly backups
    174 
    175 = 1.2.7 =
    176 * Fixed: Checkbox values (Sources/Schedule/Rotation) are now parsed robustly, so only selected options are stored (no accidental “all enabled”).
    177 
    178 = 1.2.6 =
    179 * Fixed: “Settings saved” modal is now styled like the Content Scheduler modal (OK button, backdrop click, ESC to close).
    180 * Fixed: Tab selection is now persisted correctly (active_tab), preventing unintended resets when saving.
    181 * Fixed: Backup sources, schedule and rotation checkboxes are now reliably saved (unchecked stays off).
    182 * Changed: Backup source fields are no longer greyed out when a source is inactive (only “Aktiviert” controls whether it is used).
    183 
    184 = 1.2.4 =
    185 * Fixed: Backup sources are now disabled by default on first install.
    186 * Fixed: Source enable/disable state is now reliably persisted after saving.
    187 * Added: Hint in the Nextcloud tab if no backup source has been selected yet.
    188 * Improved: Clarified what “Scan real files only” does.
    189 * Improved: Settings saved feedback now uses a centered modal (consistent with other Luzid plugins).
    190 * Improved: Form field styling (select/input) adjusted to avoid clipped text.
    191 
    192 = 1.2.2 =
    193 * Changed: Admin UI branding aligned with other Luzid WP Tools plugins (header, tabs, styling).
    194 * Changed: Language switcher uses SVG assets from `assets/img/` (no inline data-URI).
    195 * Changed: Unified admin CSS to shared `assets/css/luzid.css`.
    196 * Added: WordPress-standard `readme.txt`.
    197 
    198 = 1.2.1 =
    199 * Previous release.
     149* Beta release
    200150
    201151== Upgrade Notice ==
    202152
    203 = 1.2.6 =
    204 No breaking changes. UI improvements + bugfixes for source selection.
     153= 1.3.0 =
     154Streaming uploads for large files, improved UI with context-aware buttons, password field improvements. Recommended update for all users.
    205155
    206 = 1.2.2 =
    207 No breaking changes. UI/branding update only.
     156= 1.2.9 =
     157WordPress.org review compliance. Required for plugin directory submission.
Note: See TracChangeset for help on using the changeset viewer.