Plugin Directory

Changeset 3448097


Ignore:
Timestamp:
01/27/2026 05:30:57 PM (4 weeks ago)
Author:
blaminhor
Message:
  1. 1.3.1
Location:
blaminhor-essentials
Files:
95 added
9 edited

Legend:

Unmodified
Added
Removed
  • blaminhor-essentials/trunk/assets/js/modules.js

    r3447961 r3448097  
    946946                var files = e.originalEvent.dataTransfer.files;
    947947                if (files.length > 0) {
    948                     self.uploadBackup(files[0]);
     948                    self.addFilesToQueue(files);
    949949                }
    950950            });
     
    958958            $fileInput.off('change' + ns).on('change' + ns, function() {
    959959                if (this.files.length > 0) {
    960                     self.uploadBackup(this.files[0]);
    961                 }
     960                    self.addFilesToQueue(this.files);
     961                    this.value = ''; // Reset to allow selecting same files again
     962                }
     963            });
     964
     965            // Start upload button
     966            $('#ap-start-upload').off('click' + ns).on('click' + ns, function() {
     967                self.processUploadQueue();
     968            });
     969
     970            // Clear queue button
     971            $('#ap-clear-queue').off('click' + ns).on('click' + ns, function() {
     972                self.clearUploadQueue();
     973            });
     974
     975            // Remove file from queue (delegated event)
     976            $('#ap-file-list').off('click' + ns, '.ap-remove-file').on('click' + ns, '.ap-remove-file', function() {
     977                var index = $(this).data('index');
     978                self.removeFileFromQueue(index);
    962979            });
    963980
     
    10821099
    10831100        backupComplete: function() {
     1101            var self = this;
    10841102            var $btn = $('#ap-create-backup');
    10851103            $btn.prop('disabled', false);
     
    10881106            $('#ap-backup-progress-status').text(this.strings.complete || 'Complete!');
    10891107
    1090             setTimeout(function() {
     1108            // Fetch updated backup list and switch to backups tab
     1109            $.post(this.ajaxurl, {
     1110                action: 'ap_backup_get_list',
     1111                nonce: this.nonce
     1112            }, function(response) {
    10911113                $('#ap-backup-progress').hide();
    1092                 $('#ap-backup-result').html(
    1093                     '<div class="blaminhor-essentials-notice success" style="display: flex; align-items: center; gap: 10px;">' +
    1094                     '<span class="dashicons dashicons-yes-alt" style="color: #00a32a; font-size: 20px;"></span>' +
    1095                     '<span>' + (this.strings.backupSuccess || 'Backup created successfully!') + '</span>' +
    1096                     '</div>'
    1097                 ).show();
    1098                 setTimeout(function() { location.reload(); }, 1500);
    1099             }, 500);
     1114
     1115                if (response.success) {
     1116                    // Update the backup count in the tab
     1117                    self.updateBackupCount(response.data.count);
     1118
     1119                    // Update the backup table content
     1120                    var $tabContent = $('[data-tab-content="backups"]');
     1121                    var $h3 = $tabContent.find('h3').first();
     1122
     1123                    // Remove existing table/notice and restore modal (we'll keep the modal)
     1124                    var $restoreModal = $tabContent.find('#ap-restore-modal').detach();
     1125                    $tabContent.find('#ap-backups-table, .blaminhor-essentials-notice.warning').remove();
     1126
     1127                    // Insert new table/notice after h3
     1128                    $h3.after(response.data.html);
     1129
     1130                    // Re-attach restore modal at the end
     1131                    if ($restoreModal.length) {
     1132                        $tabContent.append($restoreModal);
     1133                    }
     1134
     1135                    // Re-bind sortable table events
     1136                    self.bindSortable();
     1137
     1138                    // Switch to the backups tab
     1139                    if (typeof window.BlaminhorEssentialsActivateTab === 'function') {
     1140                        window.BlaminhorEssentialsActivateTab('backups');
     1141                    } else {
     1142                        // Fallback: manually switch tabs
     1143                        $('.blaminhor-essentials-tab').removeClass('active');
     1144                        $('[data-tab="backups"]').addClass('active');
     1145                        $('.blaminhor-essentials-tab-content').removeClass('active');
     1146                        $tabContent.addClass('active');
     1147                    }
     1148
     1149                    // Show success message at the top of the backups tab
     1150                    var successHtml = '<div class="blaminhor-essentials-notice success ap-backup-success-notice" style="display: flex; align-items: center; gap: 10px; margin-bottom: 15px;">' +
     1151                        '<span class="dashicons dashicons-yes-alt" style="color: #00a32a; font-size: 20px;"></span>' +
     1152                        '<span>' + (self.strings.backupSuccess || 'Backup created successfully!') + '</span>' +
     1153                        '</div>';
     1154
     1155                    // Remove any existing success notice and prepend new one
     1156                    $tabContent.find('.ap-backup-success-notice').remove();
     1157                    $h3.after(successHtml);
     1158
     1159                    // Hide the result area on the create tab
     1160                    $('#ap-backup-result').hide();
     1161                } else {
     1162                    // Fallback to page reload on error
     1163                    location.reload();
     1164                }
     1165            }).fail(function() {
     1166                // Fallback to page reload on network error
     1167                location.reload();
     1168            });
    11001169        },
    11011170
     
    11101179
    11111180        backupState: null,
     1181        uploadQueue: [],
     1182
     1183        addFilesToQueue: function(files) {
     1184            var self = this;
     1185            var validExtensions = ['.zip'];
     1186
     1187            for (var i = 0; i < files.length; i++) {
     1188                var file = files[i];
     1189                var ext = file.name.toLowerCase().substring(file.name.lastIndexOf('.'));
     1190
     1191                if (validExtensions.indexOf(ext) === -1) {
     1192                    continue; // Skip non-ZIP files
     1193                }
     1194
     1195                // Check if file is already in queue
     1196                var exists = false;
     1197                for (var j = 0; j < this.uploadQueue.length; j++) {
     1198                    if (this.uploadQueue[j].name === file.name && this.uploadQueue[j].size === file.size) {
     1199                        exists = true;
     1200                        break;
     1201                    }
     1202                }
     1203
     1204                if (!exists) {
     1205                    this.uploadQueue.push(file);
     1206                }
     1207            }
     1208
     1209            this.renderUploadQueue();
     1210        },
     1211
     1212        removeFileFromQueue: function(index) {
     1213            this.uploadQueue.splice(index, 1);
     1214            this.renderUploadQueue();
     1215        },
     1216
     1217        clearUploadQueue: function() {
     1218            this.uploadQueue = [];
     1219            this.renderUploadQueue();
     1220        },
     1221
     1222        renderUploadQueue: function() {
     1223            var $queue = $('#ap-upload-queue');
     1224            var $list = $('#ap-file-list');
     1225            var $dropzone = $('#ap-backup-dropzone');
     1226
     1227            if (this.uploadQueue.length === 0) {
     1228                $queue.hide();
     1229                $dropzone.show();
     1230                return;
     1231            }
     1232
     1233            $dropzone.hide();
     1234            $queue.show();
     1235
     1236            var html = '';
     1237            for (var i = 0; i < this.uploadQueue.length; i++) {
     1238                var file = this.uploadQueue[i];
     1239                var size = this.formatFileSize(file.size);
     1240                html += '<li style="display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; background: #f6f7f7; border-radius: 4px; margin-bottom: 5px;">' +
     1241                    '<span><span class="dashicons dashicons-media-archive" style="color: #2271b1; margin-right: 8px;"></span>' + file.name + ' <small style="color: #646970;">(' + size + ')</small></span>' +
     1242                    '<button type="button" class="ap-remove-file" data-index="' + i + '" style="background: none; border: none; cursor: pointer; color: #d63638; padding: 0;"><span class="dashicons dashicons-no-alt"></span></button>' +
     1243                    '</li>';
     1244            }
     1245            $list.html(html);
     1246        },
     1247
     1248        formatFileSize: function(bytes) {
     1249            if (bytes === 0) return '0 B';
     1250            var k = 1024;
     1251            var sizes = ['B', 'KB', 'MB', 'GB'];
     1252            var i = Math.floor(Math.log(bytes) / Math.log(k));
     1253            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
     1254        },
     1255
     1256        processUploadQueue: function() {
     1257            if (this.uploadQueue.length === 0) {
     1258                return;
     1259            }
     1260
     1261            var self = this;
     1262            this.uploadQueueIndex = 0;
     1263            this.uploadResults = [];
     1264
     1265            // Hide queue, show progress
     1266            $('#ap-upload-queue').hide();
     1267            $('#ap-upload-progress').show();
     1268            $('#ap-upload-result').hide();
     1269
     1270            this.uploadNextInQueue();
     1271        },
     1272
     1273        uploadNextInQueue: function() {
     1274            var self = this;
     1275
     1276            if (this.uploadQueueIndex >= this.uploadQueue.length) {
     1277                // All done - switch to backups tab
     1278                this.onUploadQueueComplete();
     1279                return;
     1280            }
     1281
     1282            var file = this.uploadQueue[this.uploadQueueIndex];
     1283            var $progressFill = $('#ap-upload-progress-fill');
     1284            var $status = $('#ap-upload-status');
     1285            var $currentFile = $('#ap-upload-current-file');
     1286
     1287            $currentFile.text((this.strings.uploading || 'Uploading') + ' ' + file.name + ' (' + (this.uploadQueueIndex + 1) + '/' + this.uploadQueue.length + ')');
     1288            $progressFill.css('width', '0%');
     1289
     1290            var formData = new FormData();
     1291            formData.append('action', 'ap_backup_upload');
     1292            formData.append('nonce', this.nonce);
     1293            formData.append('backup_file', file);
     1294
     1295            $.ajax({
     1296                url: this.ajaxurl,
     1297                type: 'POST',
     1298                data: formData,
     1299                processData: false,
     1300                contentType: false,
     1301                xhr: function() {
     1302                    var xhr = new window.XMLHttpRequest();
     1303                    xhr.upload.addEventListener('progress', function(e) {
     1304                        if (e.lengthComputable) {
     1305                            var percent = (e.loaded / e.total) * 100;
     1306                            $progressFill.css('width', percent + '%');
     1307                            if (percent >= 100) {
     1308                                $status.text(self.strings.processing || 'Processing...');
     1309                            }
     1310                        }
     1311                    }, false);
     1312                    return xhr;
     1313                },
     1314                success: function(response) {
     1315                    self.uploadResults.push({
     1316                        file: file.name,
     1317                        success: response.success,
     1318                        data: response.data
     1319                    });
     1320                    self.uploadQueueIndex++;
     1321                    self.uploadNextInQueue();
     1322                },
     1323                error: function() {
     1324                    self.uploadResults.push({
     1325                        file: file.name,
     1326                        success: false,
     1327                        data: self.strings.error || 'Upload failed'
     1328                    });
     1329                    self.uploadQueueIndex++;
     1330                    self.uploadNextInQueue();
     1331                }
     1332            });
     1333        },
     1334
     1335        onUploadQueueComplete: function() {
     1336            var self = this;
     1337
     1338            // Clear the queue
     1339            this.uploadQueue = [];
     1340
     1341            // Fetch updated backup list and switch to backups tab
     1342            $.post(this.ajaxurl, {
     1343                action: 'ap_backup_get_list',
     1344                nonce: this.nonce
     1345            }, function(response) {
     1346                $('#ap-upload-progress').hide();
     1347                $('#ap-backup-dropzone').show();
     1348
     1349                if (response.success) {
     1350                    // Update the backup count in the tab
     1351                    self.updateBackupCount(response.data.count);
     1352
     1353                    // Update the backup table content
     1354                    var $tabContent = $('[data-tab-content="backups"]');
     1355                    var $h3 = $tabContent.find('h3').first();
     1356
     1357                    // Remove existing table/notice and restore modal
     1358                    var $restoreModal = $tabContent.find('#ap-restore-modal').detach();
     1359                    $tabContent.find('#ap-backups-table, .blaminhor-essentials-notice.warning').remove();
     1360
     1361                    // Insert new table/notice after h3
     1362                    $h3.after(response.data.html);
     1363
     1364                    // Re-attach restore modal at the end
     1365                    if ($restoreModal.length) {
     1366                        $tabContent.append($restoreModal);
     1367                    }
     1368
     1369                    // Re-bind sortable table events
     1370                    self.bindSortable();
     1371
     1372                    // Switch to the backups tab
     1373                    if (typeof window.BlaminhorEssentialsActivateTab === 'function') {
     1374                        window.BlaminhorEssentialsActivateTab('backups');
     1375                    } else {
     1376                        $('.blaminhor-essentials-tab').removeClass('active');
     1377                        $('[data-tab="backups"]').addClass('active');
     1378                        $('.blaminhor-essentials-tab-content').removeClass('active');
     1379                        $tabContent.addClass('active');
     1380                    }
     1381
     1382                    // Build success message
     1383                    var successCount = 0;
     1384                    var errorCount = 0;
     1385                    for (var i = 0; i < self.uploadResults.length; i++) {
     1386                        if (self.uploadResults[i].success) {
     1387                            successCount++;
     1388                        } else {
     1389                            errorCount++;
     1390                        }
     1391                    }
     1392
     1393                    var successHtml = '';
     1394                    if (successCount > 0) {
     1395                        var msg = successCount === 1
     1396                            ? (self.strings.uploadSuccess || 'Backup uploaded successfully!')
     1397                            : successCount + ' ' + (self.strings.backupsUploaded || 'backups uploaded successfully!');
     1398                        successHtml = '<div class="blaminhor-essentials-notice success ap-backup-success-notice" style="display: flex; align-items: center; gap: 10px; margin-bottom: 15px;">' +
     1399                            '<span class="dashicons dashicons-yes-alt" style="color: #00a32a; font-size: 20px;"></span>' +
     1400                            '<span>' + msg + '</span>' +
     1401                            '</div>';
     1402                    }
     1403
     1404                    if (errorCount > 0) {
     1405                        successHtml += '<div class="blaminhor-essentials-notice error ap-backup-error-notice" style="margin-bottom: 15px;">' +
     1406                            '<span class="dashicons dashicons-warning"></span> ' +
     1407                            errorCount + ' ' + (self.strings.uploadsFailed || 'file(s) failed to upload.') +
     1408                            '</div>';
     1409                    }
     1410
     1411                    // Remove any existing notices and add new ones
     1412                    $tabContent.find('.ap-backup-success-notice, .ap-backup-error-notice, .ap-backup-domain-notice').remove();
     1413                    if (successHtml) {
     1414                        $h3.after(successHtml);
     1415                    }
     1416                } else {
     1417                    location.reload();
     1418                }
     1419            }).fail(function() {
     1420                location.reload();
     1421            });
     1422        },
    11121423
    11131424        downloadBackup: function(filename) {
     
    12771588
    12781589                    if (response.success) {
    1279                         $success.show();
    1280                         $('#ap-upload-filename').text(response.data.filename + ' (' + response.data.size + ')');
    1281 
    1282                         // Check if domain differs
    1283                         if (response.data.domain_differs) {
    1284                             $domainWarning.show();
    1285                             $('#ap-upload-domain-info').html(
    1286                                 (self.strings.backupFrom || 'This backup is from') + ' <strong>' + response.data.backup_domain + '</strong>. ' +
    1287                                 (self.strings.currentDomain || 'Current domain is') + ' <strong>' + response.data.current_domain + '</strong>.'
    1288                             );
    1289                         }
    1290 
    1291                         // Refresh backups list after short delay
    1292                         setTimeout(function() {
     1590                        // Fetch updated backup list and switch to backups tab
     1591                        $.post(self.ajaxurl, {
     1592                            action: 'ap_backup_get_list',
     1593                            nonce: self.nonce
     1594                        }, function(listResponse) {
     1595                            if (listResponse.success) {
     1596                                // Update the backup count in the tab
     1597                                self.updateBackupCount(listResponse.data.count);
     1598
     1599                                // Update the backup table content
     1600                                var $tabContent = $('[data-tab-content="backups"]');
     1601                                var $h3 = $tabContent.find('h3').first();
     1602
     1603                                // Remove existing table/notice and restore modal
     1604                                var $restoreModal = $tabContent.find('#ap-restore-modal').detach();
     1605                                $tabContent.find('#ap-backups-table, .blaminhor-essentials-notice.warning').remove();
     1606
     1607                                // Insert new table/notice after h3
     1608                                $h3.after(listResponse.data.html);
     1609
     1610                                // Re-attach restore modal at the end
     1611                                if ($restoreModal.length) {
     1612                                    $tabContent.append($restoreModal);
     1613                                }
     1614
     1615                                // Re-bind sortable table events
     1616                                self.bindSortable();
     1617
     1618                                // Switch to the backups tab
     1619                                if (typeof window.BlaminhorEssentialsActivateTab === 'function') {
     1620                                    window.BlaminhorEssentialsActivateTab('backups');
     1621                                } else {
     1622                                    // Fallback: manually switch tabs
     1623                                    $('.blaminhor-essentials-tab').removeClass('active');
     1624                                    $('[data-tab="backups"]').addClass('active');
     1625                                    $('.blaminhor-essentials-tab-content').removeClass('active');
     1626                                    $tabContent.addClass('active');
     1627                                }
     1628
     1629                                // Build success message with domain warning if needed
     1630                                var successMsg = self.strings.uploadSuccess || 'Backup uploaded successfully!';
     1631                                var successHtml = '<div class="blaminhor-essentials-notice success ap-backup-success-notice" style="display: flex; align-items: center; gap: 10px; margin-bottom: 15px;">' +
     1632                                    '<span class="dashicons dashicons-yes-alt" style="color: #00a32a; font-size: 20px;"></span>' +
     1633                                    '<span>' + successMsg + ' (' + response.data.filename + ')</span>' +
     1634                                    '</div>';
     1635
     1636                                // Add domain warning if needed
     1637                                if (response.data.domain_differs) {
     1638                                    successHtml += '<div class="blaminhor-essentials-notice warning ap-backup-domain-notice" style="margin-bottom: 15px;">' +
     1639                                        '<span class="dashicons dashicons-info" style="color: #dba617;"></span> ' +
     1640                                        (self.strings.backupFrom || 'This backup is from') + ' <strong>' + response.data.backup_domain + '</strong>. ' +
     1641                                        (self.strings.currentDomain || 'Current domain is') + ' <strong>' + response.data.current_domain + '</strong>.' +
     1642                                        '</div>';
     1643                                }
     1644
     1645                                // Remove any existing notices and add new ones
     1646                                $tabContent.find('.ap-backup-success-notice, .ap-backup-domain-notice').remove();
     1647                                $h3.after(successHtml);
     1648
     1649                                // Reset upload area
     1650                                $result.hide();
     1651                                $success.hide();
     1652                                $domainWarning.hide();
     1653                            } else {
     1654                                // Fallback to page reload
     1655                                location.reload();
     1656                            }
     1657                        }).fail(function() {
    12931658                            location.reload();
    1294                         }, 2000);
     1659                        });
    12951660                    } else {
    12961661                        $error.show();
  • blaminhor-essentials/trunk/blaminhor-essentials.php

    r3447961 r3448097  
    44 * Plugin URI:        https://wp.blaminhor.com/
    55 * Description:       A modular toolkit for WordPress with activatable features. Lightweight, secure, and reliable.
    6  * Version:           1.3
     6 * Version:           1.3.1
    77 * Requires at least: 6.2
    88 * Requires PHP:      7.4
     
    2323
    2424// Plugin constants
    25 define('BLAMINHOR_ESSENTIALS_VERSION', '1.3');
     25define('BLAMINHOR_ESSENTIALS_VERSION', '1.3.1');
    2626define('BLAMINHOR_ESSENTIALS_PLUGIN_FILE', __FILE__);
    2727define('BLAMINHOR_ESSENTIALS_PLUGIN_DIR', plugin_dir_path(__FILE__));
  • blaminhor-essentials/trunk/includes/class-blaminhor-essentials-admin.php

    r3447753 r3448097  
    3939        // Check for activation redirect
    4040        add_action( 'admin_init', array( $this, 'maybe_redirect_after_activation' ) );
     41
     42        // Add admin bar menu
     43        add_action( 'admin_bar_menu', array( $this, 'add_admin_bar_menu' ), 100 );
    4144    }
    4245
     
    151154        foreach ($modules_to_sort as $module_data) {
    152155            $module_data['module']->add_admin_menu();
     156        }
     157    }
     158
     159    /**
     160     * Add admin bar menu
     161     *
     162     * @param WP_Admin_Bar $wp_admin_bar The admin bar instance.
     163     */
     164    public function add_admin_bar_menu( $wp_admin_bar ) {
     165        // Only show for users who can manage options
     166        if ( ! current_user_can( 'manage_options' ) ) {
     167            return;
     168        }
     169
     170        // Only show in admin area
     171        if ( ! is_admin() ) {
     172            return;
     173        }
     174
     175        // Bow tie SVG icon for admin bar (same as sidebar)
     176        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
     177        $bow_tie_svg = 'data:image/svg+xml;base64,' . base64_encode(
     178            '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">' .
     179            '<path fill="#a0a5aa" d="M1 5l6 5-6 5V5zm18 0l-6 5 6 5V5zM8 8h4v4H8z"/>' .
     180            '</svg>'
     181        );
     182
     183        // Add main menu item
     184        $wp_admin_bar->add_node( array(
     185            'id'    => 'blaminhor-essentials',
     186            'title' => '<img src="' . esc_attr( $bow_tie_svg ) . '" alt="" style="height: 20px; width: 20px; vertical-align: middle; margin-right: 6px; margin-top: -2px;">' . esc_html__( 'Blaminhor', 'blaminhor-essentials' ),
     187            'href'  => admin_url( 'admin.php?page=blaminhor-essentials' ),
     188            'meta'  => array(
     189                'title' => __( 'Blaminhor Essentials', 'blaminhor-essentials' ),
     190            ),
     191        ) );
     192
     193        // Add Dashboard submenu
     194        $wp_admin_bar->add_node( array(
     195            'id'     => 'blaminhor-essentials-dashboard',
     196            'parent' => 'blaminhor-essentials',
     197            'title'  => __( 'Dashboard', 'blaminhor-essentials' ),
     198            'href'   => admin_url( 'admin.php?page=blaminhor-essentials' ),
     199        ) );
     200
     201        // Get active modules sorted alphabetically by translated name
     202        $active_modules = $this->plugin->get_active_modules();
     203
     204        if ( empty( $active_modules ) ) {
     205            return;
     206        }
     207
     208        // Build array with names for sorting
     209        $modules_to_sort = array();
     210        foreach ( $active_modules as $module_id => $module ) {
     211            $module_name = method_exists( $module, 'get_name' ) ? $module->get_name() : $module_id;
     212            $modules_to_sort[ $module_id ] = array(
     213                'module' => $module,
     214                'name'   => $module_name,
     215            );
     216        }
     217
     218        // Sort by translated name (case-insensitive, accent-insensitive)
     219        uasort( $modules_to_sort, function( $a, $b ) {
     220            $name_a = remove_accents( $a['name'] );
     221            $name_b = remove_accents( $b['name'] );
     222            return strcasecmp( $name_a, $name_b );
     223        } );
     224
     225        // Add sorted module submenus
     226        foreach ( $modules_to_sort as $module_id => $module_data ) {
     227            $wp_admin_bar->add_node( array(
     228                'id'     => 'blaminhor-essentials-' . $module_id,
     229                'parent' => 'blaminhor-essentials',
     230                'title'  => $module_data['name'],
     231                'href'   => admin_url( 'admin.php?page=blaminhor-essentials-' . $module_id ),
     232            ) );
    153233        }
    154234    }
  • blaminhor-essentials/trunk/languages/blaminhor-essentials-fr_FR.po

    r3447961 r3448097  
    647647
    648648msgid "Enable Coming Soon or Maintenance mode for your site."
    649 msgstr "Activer le mode Coming Soon ou Maintenance pour votre site."
     649msgstr "Activer le mode En construction ou Maintenance pour votre site."
    650650
    651651msgid "Coming Soon"
    652 msgstr "Bientôt disponible"
     652msgstr "En construction"
    653653
    654654msgid "Under Maintenance"
     
    670670
    671671msgid "Coming Soon Mode Active"
    672 msgstr "Mode Coming Soon actif"
     672msgstr "Mode En construction actif"
    673673
    674674msgid "Maintenance Mode Active"
     
    682682
    683683msgid "Activate maintenance/coming soon mode"
    684 msgstr "Activer le mode maintenance/coming soon"
     684msgstr "Activer le mode maintenance/en construction"
    685685
    686686msgid "Mode is currently ACTIVE. Visitors cannot access your site."
     
    700700
    701701msgid "Coming Soon Mode"
    702 msgstr "Mode Coming Soon"
     702msgstr "Mode En construction"
    703703
    704704msgid "For new sites not yet ready to launch."
     
    718718
    719719msgid "Coming Soon / Under Maintenance"
    720 msgstr "Bientôt disponible / En maintenance"
     720msgstr "En construction / En maintenance"
    721721
    722722msgid "Basic HTML allowed."
     
    11661166# Favicon Generator Module
    11671167msgid "Favicon Generator"
    1168 msgstr "Générateur de Favicon"
     1168msgstr "Favicon"
    11691169
    11701170msgid "Generate all favicon formats from a single image."
     
    15091509# Maintenance mode renamed
    15101510msgid "Coming soon / Maintenance"
    1511 msgstr "Coming soon / Maintenance"
     1511msgstr "En construction / Maintenance"
    15121512
    15131513# AJAX confirm
     
    22352235msgid "Backup created successfully!"
    22362236msgstr "Sauvegarde créée avec succès !"
     2237
     2238msgid "Backup uploaded successfully!"
     2239msgstr "Sauvegarde téléversée avec succès !"
     2240
     2241msgid "Uploading"
     2242msgstr "Téléversement de"
     2243
     2244msgid "backups uploaded successfully!"
     2245msgstr "sauvegardes téléversées avec succès !"
     2246
     2247msgid "file(s) failed to upload."
     2248msgstr "fichier(s) n'ont pas pu être téléversés."
     2249
     2250msgid "Selected Files"
     2251msgstr "Fichiers sélectionnés"
     2252
     2253msgid "Upload All Files"
     2254msgstr "Téléverser tous les fichiers"
     2255
     2256msgid "Clear"
     2257msgstr "Effacer"
     2258
     2259msgid "Drag and drop backup files here"
     2260msgstr "Glissez-déposez des fichiers de sauvegarde ici"
     2261
     2262msgid "Maximum upload size: %s per file"
     2263msgstr "Taille maximale de téléversement : %s par fichier"
    22372264
    22382265msgid "Backup deleted successfully."
  • blaminhor-essentials/trunk/modules/backup/class-module-backup.php

    r3447961 r3448097  
    9393        add_action( 'wp_ajax_ap_backup_get_domain_info', array( $this, 'ajax_get_domain_info' ) );
    9494        add_action( 'wp_ajax_ap_backup_create_step', array( $this, 'ajax_create_backup_step' ) );
     95        add_action( 'wp_ajax_ap_backup_get_list', array( $this, 'ajax_get_backup_list' ) );
    9596
    9697        // Scheduled backup cron.
     
    133134                'creating'      => __( 'Backing up', 'blaminhor-essentials' ),
    134135                'complete'      => __( 'Complete!', 'blaminhor-essentials' ),
    135                 'backupSuccess' => __( 'Backup created successfully!', 'blaminhor-essentials' ),
     136                'backupSuccess'    => __( 'Backup created successfully!', 'blaminhor-essentials' ),
     137                'uploadSuccess'    => __( 'Backup uploaded successfully!', 'blaminhor-essentials' ),
     138                'uploading'        => __( 'Uploading', 'blaminhor-essentials' ),
     139                'backupsUploaded'  => __( 'backups uploaded successfully!', 'blaminhor-essentials' ),
     140                'uploadsFailed'    => __( 'file(s) failed to upload.', 'blaminhor-essentials' ),
    136141            ),
    137142        );
     
    19011906                ucfirst( $component )
    19021907            ),
     1908        ) );
     1909    }
     1910
     1911    /**
     1912     * AJAX: Get backup list HTML and count
     1913     */
     1914    public function ajax_get_backup_list() {
     1915        check_ajax_referer( 'blaminhor_essentials_admin', 'nonce' );
     1916
     1917        if ( ! current_user_can( 'manage_options' ) ) {
     1918            wp_send_json_error( __( 'Unauthorized', 'blaminhor-essentials' ) );
     1919        }
     1920
     1921        $backups = $this->get_backups();
     1922        $count   = count( $backups );
     1923
     1924        // Generate HTML for the backup table.
     1925        ob_start();
     1926        if ( empty( $backups ) ) :
     1927            ?>
     1928            <div class="blaminhor-essentials-notice warning">
     1929                <?php esc_html_e( 'No backups found. Create your first backup now!', 'blaminhor-essentials' ); ?>
     1930            </div>
     1931            <?php
     1932        else :
     1933            ?>
     1934            <table class="widefat striped ap-sortable-table" id="ap-backups-table">
     1935                <thead>
     1936                    <tr>
     1937                        <th class="ap-sortable" data-sort="string"><?php esc_html_e( 'Backup', 'blaminhor-essentials' ); ?> <span class="ap-sort-icon"></span></th>
     1938                        <th class="ap-sortable" data-sort="string" style="width: 100px;"><?php esc_html_e( 'Type', 'blaminhor-essentials' ); ?> <span class="ap-sort-icon"></span></th>
     1939                        <th class="ap-sortable" data-sort="number" style="width: 100px;"><?php esc_html_e( 'Size', 'blaminhor-essentials' ); ?> <span class="ap-sort-icon"></span></th>
     1940                        <th class="ap-sortable desc" data-sort="number" style="width: 160px;"><?php esc_html_e( 'Date', 'blaminhor-essentials' ); ?> <span class="ap-sort-icon"></span></th>
     1941                        <th style="width: 180px;"><?php esc_html_e( 'Actions', 'blaminhor-essentials' ); ?></th>
     1942                    </tr>
     1943                </thead>
     1944                <tbody>
     1945                    <?php foreach ( $backups as $backup ) : ?>
     1946                        <tr data-prefix="<?php echo esc_attr( $backup['prefix'] ); ?>" data-type="<?php echo esc_attr( $backup['type'] ); ?>" data-size="<?php echo esc_attr( $backup['total_size'] ); ?>" data-date="<?php echo esc_attr( $backup['date'] ); ?>">
     1947                            <td>
     1948                                <code style="font-size: 12px;"><?php echo esc_html( $backup['prefix'] ); ?></code>
     1949                                <div style="font-size: 11px; color: #646970; margin-top: 3px;">
     1950                                    <?php
     1951                                    $component_labels = array(
     1952                                        'database'   => __( 'DB', 'blaminhor-essentials' ),
     1953                                        'plugins'    => __( 'Plugins', 'blaminhor-essentials' ),
     1954                                        'themes'     => __( 'Themes', 'blaminhor-essentials' ),
     1955                                        'uploads'    => __( 'Uploads', 'blaminhor-essentials' ),
     1956                                        'wp-content' => __( 'WP-Content', 'blaminhor-essentials' ),
     1957                                        'wp-core'    => __( 'WP Core', 'blaminhor-essentials' ),
     1958                                    );
     1959                                    $labels = array();
     1960                                    foreach ( $backup['components'] as $comp ) {
     1961                                        if ( isset( $component_labels[ $comp ] ) ) {
     1962                                            $labels[] = $component_labels[ $comp ];
     1963                                        }
     1964                                    }
     1965                                    echo esc_html( implode( ', ', $labels ) );
     1966                                    ?>
     1967                                    (<?php echo esc_html( count( $backup['archives'] ) ); ?> <?php echo esc_html( _n( 'archive', 'archives', count( $backup['archives'] ), 'blaminhor-essentials' ) ); ?>)
     1968                                </div>
     1969                            </td>
     1970                            <td><span class="ap-backup-type ap-backup-type-<?php echo esc_attr( $backup['type'] ); ?>"><?php echo esc_html( $backup['type_label'] ); ?></span></td>
     1971                            <td><?php echo esc_html( $backup['size_human'] ); ?></td>
     1972                            <td><?php echo esc_html( $backup['date_human'] ); ?></td>
     1973                            <td style="white-space: nowrap;">
     1974                                <button type="button" class="button button-small ap-backup-restore" data-prefix="<?php echo esc_attr( $backup['prefix'] ); ?>" data-type="<?php echo esc_attr( $backup['type'] ); ?>" data-components="<?php echo esc_attr( wp_json_encode( $backup['components'] ) ); ?>">
     1975                                    <?php esc_html_e( 'Restore', 'blaminhor-essentials' ); ?>
     1976                                </button>
     1977                                <button type="button" class="button button-small ap-backup-delete" data-prefix="<?php echo esc_attr( $backup['prefix'] ); ?>" style="color: #d63638;">
     1978                                    <?php esc_html_e( 'Delete', 'blaminhor-essentials' ); ?>
     1979                                </button>
     1980                            </td>
     1981                        </tr>
     1982                    <?php endforeach; ?>
     1983                </tbody>
     1984            </table>
     1985            <?php
     1986        endif;
     1987        $html = ob_get_clean();
     1988
     1989        // Clear the backup in progress lock.
     1990        delete_transient( 'blaminhor_backup_in_progress' );
     1991
     1992        wp_send_json_success( array(
     1993            'count' => $count,
     1994            'html'  => $html,
    19031995        ) );
    19041996    }
     
    24662558                        <div class="ap-upload-dropzone" id="ap-backup-dropzone">
    24672559                            <span class="dashicons dashicons-upload"></span>
    2468                             <p><?php esc_html_e( 'Drag and drop a backup file here', 'blaminhor-essentials' ); ?></p>
     2560                            <p><?php esc_html_e( 'Drag and drop backup files here', 'blaminhor-essentials' ); ?></p>
    24692561                            <p class="ap-upload-or"><?php esc_html_e( 'or', 'blaminhor-essentials' ); ?></p>
    24702562                            <label class="button button-secondary">
    24712563                                <?php esc_html_e( 'Browse Files', 'blaminhor-essentials' ); ?>
    2472                                 <input type="file" id="ap-backup-file-input" accept=".zip" style="display: none;">
     2564                                <input type="file" id="ap-backup-file-input" accept=".zip" multiple style="display: none;">
    24732565                            </label>
    24742566                            <p class="description" style="margin-top: 15px;">
     
    24772569                                printf(
    24782570                                    /* translators: %s: maximum upload size */
    2479                                     esc_html__( 'Maximum upload size: %s', 'blaminhor-essentials' ),
     2571                                    esc_html__( 'Maximum upload size: %s per file', 'blaminhor-essentials' ),
    24802572                                    esc_html( $max_upload )
    24812573                                );
     
    24842576                        </div>
    24852577
     2578                        <!-- File Queue -->
     2579                        <div class="ap-upload-queue" id="ap-upload-queue" style="display: none;">
     2580                            <h4 style="margin: 0 0 10px;"><?php esc_html_e( 'Selected Files', 'blaminhor-essentials' ); ?></h4>
     2581                            <ul class="ap-file-list" id="ap-file-list"></ul>
     2582                            <div style="margin-top: 15px; display: flex; gap: 10px;">
     2583                                <button type="button" class="button button-primary" id="ap-start-upload">
     2584                                    <span class="dashicons dashicons-upload" style="margin-right: 5px;"></span>
     2585                                    <?php esc_html_e( 'Upload All Files', 'blaminhor-essentials' ); ?>
     2586                                </button>
     2587                                <button type="button" class="button" id="ap-clear-queue">
     2588                                    <?php esc_html_e( 'Clear', 'blaminhor-essentials' ); ?>
     2589                                </button>
     2590                            </div>
     2591                        </div>
     2592
    24862593                        <div class="ap-upload-progress" id="ap-upload-progress" style="display: none;">
     2594                            <p id="ap-upload-current-file" style="margin-bottom: 10px; font-weight: 500;"></p>
    24872595                            <div class="ap-upload-progress-bar">
    24882596                                <div class="ap-upload-progress-fill" id="ap-upload-progress-fill"></div>
  • blaminhor-essentials/trunk/modules/duplicator/class-module-duplicator.php

    r3447961 r3448097  
    5050        $current_suffix = $this->get_setting( 'title_suffix', '' );
    5151
    52         // Check for old format (with parentheses)
    53         if ( ' (Copy)' === $current_suffix || preg_match( '/^\s*\(.+\)\s*$/', $current_suffix ) ) {
     52        // Check for old format (with parentheses in any language) or malformed new format
     53        // Old formats: " (Copy)", " (Copie)", "(Kopie)", etc.
     54        // Malformed: "- copy", "- Copie" (missing leading space)
     55        $needs_migration = false;
     56
     57        // Check for parentheses format
     58        if ( preg_match( '/\(.*\)/', $current_suffix ) ) {
     59            $needs_migration = true;
     60        }
     61
     62        // Check for malformed new format (missing leading space before dash)
     63        if ( preg_match( '/^-\s/', $current_suffix ) ) {
     64            $needs_migration = true;
     65        }
     66
     67        if ( $needs_migration ) {
    5468            $this->settings['title_suffix'] = __( ' - copy', 'blaminhor-essentials' );
    5569            $this->save_settings( $this->settings );
     
    6377     */
    6478    protected function get_default_settings() {
     79        // Get all available post types (includes custom post types).
     80        $all_post_types = blaminhor_essentials_get_post_types();
     81        $default_post_types = ! empty( $all_post_types ) ? array_keys( $all_post_types ) : array( 'post', 'page' );
     82
     83        // Get all available taxonomies (includes custom taxonomies).
     84        $all_taxonomies = blaminhor_essentials_get_taxonomies();
     85        $default_taxonomies = ! empty( $all_taxonomies ) ? array_keys( $all_taxonomies ) : array( 'category', 'post_tag' );
     86
    6587        return array(
    66             'post_types'       => array( 'post', 'page' ),
    67             'taxonomies'       => array( 'category', 'post_tag' ),
     88            'post_types'       => $default_post_types,
     89            'taxonomies'       => $default_taxonomies,
    6890            'copy_title'       => true,
    6991            'title_prefix'     => '',
     
    360382     * Copy post meta
    361383     *
     384     * Uses direct database query to preserve exact data encoding (important for page builders like Elementor).
     385     *
    362386     * @param int $source_id Source post ID.
    363387     * @param int $target_id Target post ID.
    364388     */
    365389    private function copy_post_meta( $source_id, $target_id ) {
    366         $meta = get_post_meta( $source_id );
     390        global $wpdb;
    367391
    368392        // Meta keys to exclude (internal WordPress + generated CSS that contains post IDs)
     
    380404        ) );
    381405
    382         foreach ( $meta as $key => $values ) {
    383             // Skip excluded keys
    384             if ( in_array( $key, $excluded, true ) ) {
    385                 continue;
    386             }
    387 
    388             foreach ( $values as $value ) {
    389                 add_post_meta( $target_id, $key, maybe_unserialize( $value ) );
    390             }
     406        // Build exclusion placeholders
     407        $excluded_placeholders = implode( ',', array_fill( 0, count( $excluded ), '%s' ) );
     408
     409        // Get raw meta data directly from database to preserve exact encoding
     410        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
     411        $source_meta = $wpdb->get_results(
     412            $wpdb->prepare(
     413                "SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key NOT IN ($excluded_placeholders)",
     414                array_merge( array( $source_id ), $excluded )
     415            )
     416        );
     417        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
     418
     419        // Copy each meta entry directly to preserve encoding
     420        foreach ( $source_meta as $meta ) {
     421            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
     422            $wpdb->insert(
     423                $wpdb->postmeta,
     424                array(
     425                    'post_id'    => $target_id,
     426                    'meta_key'   => $meta->meta_key,
     427                    'meta_value' => $meta->meta_value,
     428                ),
     429                array( '%d', '%s', '%s' )
     430            );
    391431        }
    392432
  • blaminhor-essentials/trunk/modules/seo-manager/class-module-seo-manager.php

    r3447961 r3448097  
    106106        // Sitemap functionality.
    107107        if ( $this->get_setting( 'sitemap_enabled', true ) ) {
    108             add_action( 'init', array( $this, 'add_sitemap_rewrite_rules' ) );
    109             add_action( 'init', array( $this, 'maybe_flush_rewrite_rules' ), 99 );
     108            // Call directly since we're already in init (plugin loads on init priority 0).
     109            $this->add_sitemap_rewrite_rules();
     110            add_action( 'wp_loaded', array( $this, 'maybe_flush_rewrite_rules' ) );
    110111            add_action( 'template_redirect', array( $this, 'render_sitemap' ), 1 ); // Priority 1 = run early.
    111112            add_filter( 'query_vars', array( $this, 'add_sitemap_query_vars' ) );
     
    899900            'post_types' => $this->get_setting( 'sitemap_post_types', array( 'post', 'page' ) ),
    900901            'taxonomies' => $this->get_setting( 'sitemap_taxonomies', array( 'category', 'post_tag' ) ),
    901             'flush_v2'   => true, // Force flush on update.
     902            'flush_v3'   => true, // Force flush on update (v3: subdirectory fix).
    902903        ) ) );
    903904
     
    975976        if ( empty( $path ) ) {
    976977            return false;
     978        }
     979
     980        // Handle subdirectory installations: remove home path prefix.
     981        $home_path = wp_parse_url( home_url(), PHP_URL_PATH );
     982        if ( ! empty( $home_path ) ) {
     983            $home_path = trailingslashit( $home_path );
     984            if ( strpos( $path, $home_path ) === 0 ) {
     985                $path = substr( $path, strlen( $home_path ) );
     986            }
    977987        }
    978988
  • blaminhor-essentials/trunk/readme.txt

    r3447961 r3448097  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.3
     7Stable tag: 1.3.1
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    194194
    195195== Changelog ==
     196
     197= 1.3.1 =
     198* Fixed: Content Duplicator - Unicode characters (accents, special characters) are now preserved correctly when duplicating page builder content.
     199* Fixed: Content Duplicator - Title suffix now displays correctly with proper spacing (" - Copie" instead of "- copie").
     200* Fixed: SEO/GSO - XML sitemaps now work correctly on WordPress installations in subdirectories.
     201* Fixed: SEO/GSO - Sitemap rewrite rules timing issue resolved for better compatibility.
     202* Improved: Content Duplicator - Meta data is now copied directly via database to preserve exact encoding for page builders.
     203* Improved: Content Duplicator - All post types and taxonomies (including custom ones) are now enabled by default.
     204* Improved: Backup module - After backup completion, automatically switches to Backups tab with updated list and success message.
     205* Improved: Backup module - Upload tab now supports multiple files with queue management before uploading.
     206* Added: Admin bar menu with quick access to Dashboard and all active modules.
    196207
    197208= 1.3 =
     
    271282== Upgrade Notice ==
    272283
     284= 1.3.1 =
     285Fixed Unicode character corruption when duplicating page builder content. Fixed XML sitemaps on subdirectory installations. Custom post types and taxonomies now enabled by default. Backup shows updated list after completion. Admin bar menu added for quick access.
     286
    273287= 1.3 =
    2742889 new languages added. Full page builder support in Content Duplicator (Elementor, Beaver Builder, Divi, etc.). Fixed SMTP import and email log refresh. Added missing HTTPS translations. Various bug fixes and improvements.
Note: See TracChangeset for help on using the changeset viewer.