Plugin Directory

Changeset 3437565


Ignore:
Timestamp:
01/12/2026 10:18:43 AM (2 months ago)
Author:
awesomefootnotes
Message:

Adding the first version of my plugin

Location:
0-day-analytics
Files:
16 edited
1 copied

Legend:

Unmodified
Added
Removed
  • 0-day-analytics/tags/4.4.1/advanced-analytics.php

    r3423226 r3437565  
    1111 * Plugin Name:     0 Day Analytics
    1212 * Description:     Take full control of error log, crons, transients, plugins, requests, mails and DB tables.
    13  * Version:         4.4.0
     13 * Version:         4.4.1
    1414 * Author:          Stoil Dobrev
    1515 * Author URI:      https://github.com/sdobreff/
     
    3838// Constants.
    3939if ( ! defined( 'ADVAN_VERSION' ) ) {
    40     define( 'ADVAN_VERSION', '4.4.0' );
     40    define( 'ADVAN_VERSION', '4.4.1' );
    4141    define( 'ADVAN_TEXTDOMAIN', '0-day-analytics' );
    4242    define( 'ADVAN_NAME', '0 Day Analytics' );
  • 0-day-analytics/tags/4.4.1/classes/vendor/controllers/class-requests-log.php

    r3393178 r3437565  
    123123
    124124            // Prepare the log entry.
    125             $trace_array = \json_decode( self::$trace, true );
     125            $trace_array = \json_decode( self::get_trace(), true );
    126126            $log_entry   = array(
    127127                'url'            => $url,
     
    337337                'requests'       => self::$requests,
    338338                'trace'          => self::get_trace(),
    339                 'plugin'         => ( isset( \json_decode( self::$trace, true )[7] ) && isset( \json_decode( self::$trace, true )[7]['file'] ) ) ? Plugin_Theme_Helper::get_plugin_from_file_path( \json_decode( self::$trace, true )[7]['file'] ) : '',
     339                'plugin'         => ( isset( \json_decode( self::get_trace(), true )[7] ) && isset( \json_decode( self::get_trace(), true )[7]['file'] ) ) ? Plugin_Theme_Helper::get_plugin_from_file_path( \json_decode( self::get_trace(), true )[7]['file'] ) : '',
    340340            );
    341341
  • 0-day-analytics/tags/4.4.1/classes/vendor/helpers/class-ajax-helper.php

    r3413453 r3437565  
    163163                    \add_action( 'wp_ajax_advan_file_editor_diff', array( File_Editor::class, 'ajax_diff' ) );
    164164                    \add_action( 'wp_ajax_advan_file_editor_create', array( File_Editor::class, 'ajax_create' ) );
     165                    \add_action( 'wp_ajax_advan_file_editor_upload_file', array( File_Editor::class, 'ajax_upload_file' ) );
    165166                    \add_action( 'wp_ajax_advan_file_editor_delete', array( File_Editor::class, 'ajax_delete' ) );
    166167                    \add_action( 'wp_ajax_advan_file_editor_restore', array( File_Editor::class, 'ajax_restore' ) );
  • 0-day-analytics/tags/4.4.1/classes/vendor/views/class-file-editor.php

    r3413453 r3437565  
    294294                                <button id="wfe-new-file" class="button"><?php \esc_html_e( '+ File', '0-day-analytics' ); ?></button>
    295295                                <button id="wfe-new-folder" class="button"><?php \esc_html_e( '+ Folder', '0-day-analytics' ); ?></button>
    296                                 <button id="wfe-empty-trash" class="button button-danger"><?php \esc_html_e( '🧹 Empty Trash', '0-day-analytics' ); ?></button>
     296                            <button id="wfe-upload-file" class="button button-primary"><?php \esc_html_e( '⬆️ Upload', '0-day-analytics' ); ?></button>
     297                            <button id="wfe-empty-trash" class="button button-danger"><?php \esc_html_e( '🧹 Empty Trash', '0-day-analytics' ); ?></button>
     298                            <input type="file" id="wfe-file-input" style="display: none;" multiple />
    297299                            </div>
    298300                            <div class="wfe-dir-wrapper">
    299                                 <div id="wfe-tree" class="wfe-tree"></div>
     301                                <div id="wfe-tree" class="wfe-tree">
     302                                    <div id="wfe-drop-zone" class="wfe-drop-zone">
     303                                        <p><?php \esc_html_e( '📁 Drop files here to upload', '0-day-analytics' ); ?></p>
     304                                    </div>
     305                                </div>
    300306                            </div>
    301307                        </div>
     
    318324                </div>
    319325            </div>
     326           
     327            <!-- Upload Progress Bar -->
     328            <div id="wfe-upload-progress" class="wfe-upload-progress" style="display: none;">
     329                <div class="wfe-upload-progress-content">
     330                    <h3><?php \esc_html_e( 'Uploading Files', '0-day-analytics' ); ?></h3>
     331                    <div class="wfe-progress-bar-container">
     332                        <div id="wfe-progress-bar" class="wfe-progress-bar"></div>
     333                    </div>
     334                    <div id="wfe-progress-text" class="wfe-progress-text">0 / 0</div>
     335                    <div id="wfe-progress-status" class="wfe-progress-status"></div>
     336                </div>
     337            </div>
    320338            <?php
    321339        }
     
    437455            }
    438456            @rmdir( $dir );
     457        }
     458
     459        /**
     460         * AJAX: Uploads a file
     461         *
     462         * @return void
     463         *
     464         * @since 4.0.0
     465         */
     466        public static function ajax_upload_file() {
     467            // Standardize nonce and capability checks.
     468            WP_Helper::verify_admin_nonce( 'advan_file_editor_nonce', '_ajax_nonce' );
     469
     470            if ( empty( $_FILES['file'] ) ) {
     471                \wp_send_json_error( 'No file uploaded.' );
     472            }
     473
     474            $dir    = \sanitize_text_field( $_POST['dir'] ?? self::BASE_DIR );
     475            $parent = self::safe_path( $dir );
     476            if ( ! $parent || ! is_dir( $parent ) ) {
     477                \wp_send_json_error( 'Invalid directory.' );
     478            }
     479
     480            $file  = $_FILES['file'];
     481            $error = $file['error'];
     482
     483            if ( UPLOAD_ERR_OK !== $error ) {
     484                \wp_send_json_error( 'Upload error: ' . $error );
     485            }
     486
     487            $filename    = \sanitize_file_name( $file['name'] );
     488            $target_path = $parent . DIRECTORY_SEPARATOR . $filename;
     489
     490            // Check if file already exists.
     491            if ( file_exists( $target_path ) ) {
     492                \wp_send_json_error( 'File already exists: ' . $filename );
     493            }
     494
     495            // Move uploaded file to target directory.
     496            if ( ! move_uploaded_file( $file['tmp_name'], $target_path ) ) {
     497                \wp_send_json_error( 'Failed to save uploaded file.' );
     498            }
     499
     500            \wp_send_json_success(
     501                array(
     502                    'message' => 'File uploaded successfully.',
     503                    'path'    => $target_path,
     504                    'name'    => $filename,
     505                )
     506            );
    439507        }
    440508
     
    497565                $display = $file;
    498566                if ( 'file' === $type ) {
    499                     $size     = @filesize( $path );
     567                    $size = @filesize( $path );
    500568                    if ( false !== $size ) {
    501569                        $display .= ' (' . \size_format( (int) $size ) . ')';
     
    587655            }
    588656
    589             $trash_path = self::get_trash_dir() . \DIRECTORY_SEPARATOR . basename( $real ) . '.' . time();
    590             if ( rename( $real, $trash_path ) ) {
     657            // Check permissions
     658            if ( ! is_writable( dirname( $real ) ) ) {
     659                \wp_send_json_error( 'Permission denied: parent directory not writable.' );
     660            }
     661
     662            // Ensure trash directory exists and is writable
     663            $trash_dir = self::get_trash_dir();
     664            if ( ! is_dir( $trash_dir ) ) {
     665                @\wp_mkdir_p( $trash_dir );
     666                self::harden_directory( $trash_dir );
     667            }
     668
     669            if ( ! is_writable( $trash_dir ) ) {
     670                \wp_send_json_error( 'Trash directory is not writable.' );
     671            }
     672
     673            // Generate unique trash path
     674            $base_name  = basename( $real );
     675            $trash_path = $trash_dir . \DIRECTORY_SEPARATOR . $base_name . '.' . time();
     676            $counter    = 0;
     677            while ( file_exists( $trash_path ) ) {
     678                ++$counter;
     679                $trash_path = $trash_dir . \DIRECTORY_SEPARATOR . $base_name . '.' . time() . '_' . $counter;
     680            }
     681
     682            // Try to move to trash
     683            if ( @rename( $real, $trash_path ) ) {
    591684                self::$last_deleted = array(
    592685                    'src'   => $real,
     
    594687                );
    595688                self::save_meta();
    596                 \wp_send_json_success( 'Moved to trash.' );
     689                $msg = is_dir( $trash_path ) ? 'Directory moved to trash.' : 'Moved to trash.';
     690                \wp_send_json_success( $msg );
    597691            } else {
    598                 \wp_send_json_error( 'Unable to move to trash.' );
    599             }
    600         }
    601 
    602         /**
    603          * AJAX: Restores the last deleted file
    604          *
    605          * @return void
    606          *
    607          * @since 4.0.0
    608          */
    609         public static function ajax_restore() {
    610             WP_Helper::verify_admin_nonce( 'advan_file_editor_nonce', '_ajax_nonce' );
    611 
    612             self::load_meta();
    613             if ( ! self::get_last_deleted() || ! file_exists( self::$last_deleted['trash'] ) ) {
    614                 \wp_send_json_error( 'Nothing to restore.' );
     692                // If rename fails, try alternative approach for directories
     693                if ( is_dir( $real ) ) {
     694                    // Try to copy recursively and then delete
     695                    if ( self::recursive_copy( $real, $trash_path ) ) {
     696                        self::delete_dir( $real );
     697                        if ( ! file_exists( $real ) ) {
     698                            self::$last_deleted = array(
     699                                'src'   => $real,
     700                                'trash' => $trash_path,
     701                            );
     702                            self::save_meta();
     703                            \wp_send_json_success( 'Directory moved to trash.' );
     704                        } else {
     705                            // Cleanup trash if original still exists
     706                            self::delete_dir( $trash_path );
     707                            \wp_send_json_error( 'Unable to delete directory after copying.' );
     708                        }
     709                    } else {
     710                        \wp_send_json_error( 'Unable to copy directory to trash.' );
     711                    }
     712                } else {
     713                    \wp_send_json_error( 'Unable to move to trash. Check file permissions.' );
     714                }
    615715            }
    616716            $src   = self::$last_deleted['src'];
     
    835935            }
    836936
    837             \wp_send_json_success( array( 'new_path' => $target, 'new_name' => basename( $target ) ) );
     937            \wp_send_json_success(
     938                array(
     939                    'new_path' => $target,
     940                    'new_name' => basename( $target ),
     941                )
     942            );
    838943        }
    839944
     
    863968
    864969            // Determine target name.
    865             $ext      = '';
     970            $ext       = '';
    866971            $name_only = $basename;
    867972            if ( ! $is_dir && false !== strpos( $basename, '.' ) ) {
    868                 $parts    = explode( '.', $basename );
    869                 $ext      = array_pop( $parts );
     973                $parts     = explode( '.', $basename );
     974                $ext       = array_pop( $parts );
    870975                $name_only = implode( '.', $parts );
    871976            }
     
    875980            $target         = '';
    876981            while ( true ) {
    877                 $suffix = ( $counter > 1 ) ? '-' . $counter : '';
     982                $suffix      = ( $counter > 1 ) ? '-' . $counter : '';
    878983                $target_name = $base_candidate . $suffix . ( $is_dir ? '' : ( ( '' !== $ext ) ? '.' . $ext : '' ) );
    879984                $target      = $dirname . \DIRECTORY_SEPARATOR . $target_name;
     
    881986                    break;
    882987                }
    883                 $counter++;
     988                ++$counter;
    884989            }
    885990
     
    887992            if ( $is_dir ) {
    888993                self::recursive_copy( $real, $target );
    889             } else {
    890                 if ( ! @copy( $real, $target ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     994            } elseif ( ! @copy( $real, $target ) ) {
     995                // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
    891996                    \wp_send_json_error( 'Unable to duplicate file.' );
    892                 }
    893             }
    894 
    895             \wp_send_json_success( array( 'new_path' => $target, 'new_name' => basename( $target ) ) );
     997            }
     998
     999            \wp_send_json_success(
     1000                array(
     1001                    'new_path' => $target,
     1002                    'new_name' => basename( $target ),
     1003                )
     1004            );
    8961005        }
    8971006
     
    9021011         * @param string $dst Destination dir.
    9031012         *
    904          * @return void
     1013         * @return bool True on success, false on failure.
    9051014         *
    9061015         * @since 4.1.1
     
    9081017        private static function recursive_copy( $src, $dst ) {
    9091018            if ( ! is_dir( $src ) ) {
    910                 return;
    911             }
    912             @wp_mkdir_p( $dst );
     1019                return false;
     1020            }
     1021            if ( ! @wp_mkdir_p( $dst ) ) {
     1022                return false;
     1023            }
    9131024            $dir_handle = opendir( $src );
    9141025            if ( false === $dir_handle ) {
    915                 return;
    916             }
     1026                return false;
     1027            }
     1028            $success = true;
    9171029            while ( false !== ( $item = readdir( $dir_handle ) ) ) {
    9181030                if ( '.' === $item || '..' === $item ) {
     
    9221034                $to   = $dst . \DIRECTORY_SEPARATOR . $item;
    9231035                if ( is_dir( $from ) ) {
    924                     self::recursive_copy( $from, $to );
    925                 } else {
    926                     @copy( $from, $to ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     1036                    if ( ! self::recursive_copy( $from, $to ) ) {
     1037                        $success = false;
     1038                    }
     1039                } elseif ( ! @copy( $from, $to ) ) {
     1040                    // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     1041                        $success = false;
    9271042                }
    9281043            }
    9291044            closedir( $dir_handle );
     1045            return $success;
    9301046        }
    9311047    }
  • 0-day-analytics/tags/4.4.1/css/block-editor.css

    r3423226 r3437565  
    3232    display: inline-block;
    3333    line-height: 1;
     34    vertical-align: bottom;
    3435}
    3536
  • 0-day-analytics/tags/4.4.1/css/wfe.css

    r3423226 r3437565  
    172172    background: #1e3e59;
    173173}
    174 .wfe-tree {
    175     border-left: 1px solid #8d7a7a73;
    176     border-bottom: 1px solid #8d7a7a73;
    177     border-top: 1px solid #8d7a7a73;
    178 }
    179174.wfe-tree ul ul {
    180175    list-style: none;
     
    252247    height: 80vh;
    253248}
     249
     250/* Drop zone styling */
     251.wfe-drop-zone {
     252    display: none;
     253    position: absolute;
     254    top: 0;
     255    left: 0;
     256    right: 0;
     257    bottom: 0;
     258    background: rgba(33, 150, 243, 0.9);
     259    border: 3px dashed #fff;
     260    border-radius: 8px;
     261    z-index: 1000;
     262    pointer-events: none;
     263    align-items: center;
     264    justify-content: center;
     265    margin: 10px;
     266}
     267
     268.wfe-drop-zone.wfe-drop-active {
     269    display: flex;
     270}
     271
     272.wfe-drop-zone p {
     273    color: #fff;
     274    font-size: 18px;
     275    font-weight: 600;
     276    text-align: center;
     277    margin: 0;
     278    padding: 20px;
     279}
     280
     281.aadvana-darkskin .wfe-drop-zone {
     282    background: rgba(30, 62, 89, 0.95);
     283    border-color: #2196f3;
     284}
     285
     286/* Upload button styling */
     287#wfe-upload-file {
     288    background: #2196f3 !important;
     289    border-color: #1976d2 !important;
     290    color: #fff !important;
     291}
     292
     293#wfe-upload-file:hover {
     294    background: #1976d2 !important;
     295}
     296
     297/* Tree positioning for drop zone */
     298.wfe-tree {
     299    position: relative;
     300    border-left: 1px solid #8d7a7a73;
     301    border-bottom: 1px solid #8d7a7a73;
     302    border-top: 1px solid #8d7a7a73;
     303}
     304
     305/* Upload Progress Bar */
     306.wfe-upload-progress {
     307    position: fixed;
     308    top: 0;
     309    left: 0;
     310    right: 0;
     311    bottom: 0;
     312    background: rgba(0, 0, 0, 0.7);
     313    z-index: 10000;
     314    display: flex;
     315    align-items: center;
     316    justify-content: center;
     317}
     318
     319.wfe-upload-progress-content {
     320    background: #fff;
     321    padding: 30px;
     322    border-radius: 8px;
     323    min-width: 400px;
     324    max-width: 500px;
     325    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
     326}
     327
     328.aadvana-darkskin .wfe-upload-progress-content {
     329    background: #13273b;
     330    color: #c9c0c0;
     331}
     332
     333.wfe-upload-progress-content h3 {
     334    margin: 0 0 20px 0;
     335    font-size: 18px;
     336    font-weight: 600;
     337    text-align: center;
     338}
     339
     340.wfe-progress-bar-container {
     341    width: 100%;
     342    height: 30px;
     343    background: #e0e0e0;
     344    border-radius: 15px;
     345    overflow: hidden;
     346    margin-bottom: 15px;
     347}
     348
     349.aadvana-darkskin .wfe-progress-bar-container {
     350    background: #1e3e59;
     351}
     352
     353.wfe-progress-bar {
     354    height: 100%;
     355    background: linear-gradient(90deg, #2196f3, #1976d2);
     356    width: 0%;
     357    transition: width 0.3s ease;
     358    display: flex;
     359    align-items: center;
     360    justify-content: center;
     361    color: #fff;
     362    font-weight: 600;
     363    font-size: 14px;
     364}
     365
     366.wfe-progress-text {
     367    text-align: center;
     368    font-size: 16px;
     369    font-weight: 600;
     370    margin-bottom: 10px;
     371}
     372
     373.wfe-progress-status {
     374    text-align: center;
     375    font-size: 13px;
     376    color: #666;
     377    min-height: 20px;
     378}
     379
     380.aadvana-darkskin .wfe-progress-status {
     381    color: #999;
     382}
     383
     384.wfe-progress-status.success {
     385    color: #46b450;
     386    font-weight: 600;
     387}
     388
     389.wfe-progress-status.error {
     390    color: #d63638;
     391    font-weight: 600;
     392}
     393
     394.wfe-progress-status.warning {
     395    color: #f0b849;
     396    font-weight: 600;
     397}
  • 0-day-analytics/tags/4.4.1/js/admin/wfe.js

    r3413453 r3437565  
    11jQuery(function ($) {
    22    let editor, currentFile = null, currentDir = AFE_Ajax.base, { __ } = wp.i18n;
     3    let expandedFolders = new Set(); // Track expanded folder paths
    34
    45    // CodeMirror init
     
    1920    editor = wp.codeEditor.initialize($('#wfe-editor'), settings).codemirror;
    2021
     22    // Function to get all currently expanded folder paths
     23    function getExpandedFolders() {
     24        const expanded = [];
     25        $('#wfe-tree .wfe-item.dir').each(function() {
     26            const $li = $(this);
     27            const $sub = $li.children('ul');
     28            if ($sub.length && $sub.is(':visible')) {
     29                expanded.push($li.data('path'));
     30            }
     31        });
     32        return expanded;
     33    }
     34
     35    // Smart refresh that preserves folder states
     36    function smartRefresh() {
     37        const expanded = getExpandedFolders();
     38        $('#wfe-tree').empty();
     39        loadDir(AFE_Ajax.base, $('#wfe-tree'), function() {
     40            // After tree is loaded, restore expanded folders in hierarchical order
     41            if (expanded.length > 0) {
     42                // Sort paths by depth (number of separators) to expand parent folders first
     43                expanded.sort((a, b) => {
     44                    const aDepth = (a.match(/\//g) || []).length;
     45                    const bDepth = (b.match(/\//g) || []).length;
     46                    return aDepth - bDepth;
     47                });
     48               
     49                // Restore folders sequentially to ensure parent folders are loaded before children
     50                restoreFoldersSequentially(expanded, 0);
     51            }
     52        });
     53    }
     54
     55    // Recursively restore folders in order
     56    function restoreFoldersSequentially(paths, index) {
     57        if (index >= paths.length) return;
     58       
     59        const path = paths[index];
     60        const $folder = $('#wfe-tree .wfe-item.dir').filter(function() {
     61            return $(this).data('path') === path;
     62        });
     63       
     64        if ($folder.length) {
     65            const $sub = $folder.children('ul');
     66            if ($sub.length) {
     67                // Folder already has content, just expand it
     68                $sub.show();
     69                $folder.children('.toggle').text('▼');
     70                expandedFolders.add(path);
     71                // Continue with next folder
     72                restoreFoldersSequentially(paths, index + 1);
     73            } else {
     74                // Need to load folder content first
     75                loadDir(path, $folder, function() {
     76                    $folder.children('ul').show();
     77                    $folder.children('.toggle').text('▼');
     78                    expandedFolders.add(path);
     79                    // Continue with next folder after this one loads
     80                    restoreFoldersSequentially(paths, index + 1);
     81                });
     82            }
     83        } else {
     84            // Folder not found, skip to next
     85            restoreFoldersSequentially(paths, index + 1);
     86        }
     87    }
     88
     89    // Function to expand folders by path
     90    function expandFolderByPath(path) {
     91        const $folder = $('#wfe-tree .wfe-item.dir').filter(function() {
     92            return $(this).data('path') === path;
     93        });
     94       
     95        if ($folder.length) {
     96            const $sub = $folder.children('ul');
     97            if ($sub.length && !$sub.is(':visible')) {
     98                $sub.show();
     99                $folder.children('.toggle').text('▼');
     100            }
     101        }
     102    }
     103
    21104    // Load directory
    22     function loadDir(dir, container) {
     105    function loadDir(dir, container, callback) {
    23106        $.post(AFE_Ajax.ajax_url, { action: 'advan_file_editor_list_dir', dir, _ajax_nonce: AFE_Ajax.nonce }, (res) => {
    24107            if (res.success) {
     
    32115                    ul.append(li);
    33116                });
    34                 container.append(ul);
    35             }
     117                container.append(ul);                if (callback) callback();            }
    36118        });
    37119    }
     
    48130        if (sub.length) {
    49131            sub.toggle();
    50             li.children('.toggle').text(sub.is(':visible') ? '▼' : '▶');
     132            const isVisible = sub.is(':visible');
     133            li.children('.toggle').text(isVisible ? '▼' : '▶');
     134            // Track expanded state
     135            if (isVisible) {
     136                expandedFolders.add(path);
     137            } else {
     138                expandedFolders.delete(path);
     139            }
    51140        } else {
    52141            li.children('.toggle').text('▼');
    53142            loadDir(path, li);
     143            expandedFolders.add(path);
    54144        }
    55145    });
     
    123213        }, (res) => {
    124214            alert(res.success ? __('✅ Created.', '0-day-analytics') : '❌ ' + res.data);
    125             $('#wfe-tree').empty();
    126             loadDir(AFE_Ajax.base, $('#wfe-tree'));
     215            smartRefresh();
    127216        });
    128217    });
     
    138227        }, (res) => {
    139228            alert(res.success ? __('🗑️ Deleted.', '0-day-analytics') : '❌ ' + res.data);
    140             $('#wfe-tree').empty();
    141             loadDir(AFE_Ajax.base, $('#wfe-tree'));
     229            smartRefresh();
    142230            $('#wfe-filename').text(__('No file selected', '0-day-analytics'));
    143231            editor.setValue('');
     
    153241        }, (res) => {
    154242            alert(res.success ? '♻ ' + res.data.msg : '❌ ' + res.data);
    155             $('#wfe-tree').empty();
    156             loadDir(AFE_Ajax.base, $('#wfe-tree'));
     243            smartRefresh();
    157244        });
    158245    });
     
    385472                        $('#wfe-filename').text(res.data.new_path);
    386473                    }
    387                     $('#wfe-tree').empty();
    388                     loadDir(AFE_Ajax.base, $('#wfe-tree'));
     474                    smartRefresh();
    389475                }
    390476            });
     
    411497                        $('#wfe-filename').text(__('No file selected','0-day-analytics'));
    412498                    }
    413                     $('#wfe-tree').empty();
    414                     loadDir(AFE_Ajax.base, $('#wfe-tree'));
     499                    smartRefresh();
    415500                }
    416501            });
     
    423508                alert(res.success ? '🧬 '+__('Duplicated','0-day-analytics')+': '+res.data.new_name : '❌ '+res.data);
    424509                if(res.success){
    425                     $('#wfe-tree').empty();
    426                     loadDir(AFE_Ajax.base, $('#wfe-tree'));
     510                    smartRefresh();
    427511                }
    428512            });
     
    452536    $('#wfe-tree').on('contextmenu', '.wfe-item.file', function(e){
    453537        e.preventDefault();
     538        e.stopPropagation();
    454539        const path = $(this).data('path');
    455540        showContextMenu(e, path);
    456541    });
     542
     543    // Context menu for directories
     544    $('#wfe-tree').on('contextmenu', '.wfe-item.dir', function(e){
     545        e.preventDefault();
     546        e.stopPropagation();
     547        const path = $(this).data('path');
     548        showDirectoryContextMenu(e, path);
     549    });
     550
     551    // Directory-specific context menu
     552    function showDirectoryContextMenu(e, dirPath){
     553        hideContextMenu();
     554        $ctxMenu = $('<div class="wfe-context-menu" role="menu"></div>');
     555       
     556        // Rename
     557        const $renameBtn = $('<button type="button" role="menuitem">✏️ '+__('Rename','0-day-analytics')+'</button>');
     558        $renameBtn.on('click', function(){
     559            const currentBase = dirPath.substring(dirPath.lastIndexOf('/'));
     560            const newName = prompt(__('Enter new name:','0-day-analytics'), currentBase.replace('/',''));
     561            if(!newName){ return; }
     562            $.post(AFE_Ajax.ajax_url, { action:'advan_file_editor_rename', file:dirPath, new_name:newName, _ajax_nonce: AFE_Ajax.nonce }, function(res){
     563                alert(res.success ? '✅ '+__('Renamed','0-day-analytics') : '❌ '+res.data);
     564                if(res.success){
     565                    smartRefresh();
     566                }
     567            });
     568            hideContextMenu();
     569        });
     570       
     571        // Copy Path
     572        const $copyPathBtn = $('<button type="button" role="menuitem">📋 '+__('Copy Path','0-day-analytics')+'</button>');
     573        $copyPathBtn.on('click', function(){
     574            navigator.clipboard.writeText(dirPath).then(()=>{
     575                alert('📋 '+__('Path copied','0-day-analytics'));
     576            }).catch(()=>{ alert('❌ '+__('Unable to copy path','0-day-analytics')); });
     577            hideContextMenu();
     578        });
     579       
     580        // Delete Directory (Recursive)
     581        const $deleteBtn = $('<button type="button" role="menuitem" style="color: #d63638; font-weight: 600;">🗑️ '+__('Delete Recursively','0-day-analytics')+'</button>');
     582        $deleteBtn.on('click', function(){
     583            if(!confirm(__('⚠️ WARNING: This will RECURSIVELY delete the entire directory and all its contents!','0-day-analytics')+'\n\n'+__('Directory:','0-day-analytics')+' '+dirPath+'\n\n'+__('This action cannot be undone. Are you absolutely sure?','0-day-analytics'))) {
     584                return;
     585            }
     586            // Double confirmation
     587            if(!confirm(__('FINAL CONFIRMATION: Delete','0-day-analytics')+' '+dirPath+' '+__('and ALL its contents?','0-day-analytics'))) {
     588                return;
     589            }
     590            $.post(AFE_Ajax.ajax_url, { action:'advan_file_editor_delete', path:dirPath, _ajax_nonce:AFE_Ajax.nonce }, function(res){
     591                alert(res.success ? '🗑️ '+__('Directory deleted','0-day-analytics') : '❌ '+res.data);
     592                if(res.success){
     593                    smartRefresh();
     594                }
     595            });
     596            hideContextMenu();
     597        });
     598       
     599        // Duplicate
     600        const $duplicateBtn = $('<button type="button" role="menuitem">🧬 '+__('Duplicate','0-day-analytics')+'</button>');
     601        $duplicateBtn.on('click', function(){
     602            $.post(AFE_Ajax.ajax_url, { action:'advan_file_editor_duplicate', file:dirPath, _ajax_nonce:AFE_Ajax.nonce }, function(res){
     603                alert(res.success ? '🧬 '+__('Duplicated','0-day-analytics')+': '+res.data.new_name : '❌ '+res.data);
     604                if(res.success){
     605                    smartRefresh();
     606                }
     607            });
     608            hideContextMenu();
     609        });
     610       
     611        $ctxMenu.append($renameBtn, $duplicateBtn, $copyPathBtn, $deleteBtn);
     612        $('body').append($ctxMenu);
     613        const x = e.pageX;
     614        const y = e.pageY;
     615        $ctxMenu.css({ top: y + 'px', left: x + 'px' });
     616       
     617        // Delay binding to avoid immediate self-close
     618        setTimeout(function(){
     619            $(document).on('mousedown.wfeCtx', function(ev){
     620                if(!$ctxMenu) return;
     621                if(!$(ev.target).closest('.wfe-context-menu').length){ hideContextMenu(); }
     622            });
     623            $(document).on('contextmenu.wfeCtx', function(ev){
     624                if(!$ctxMenu) return;
     625                if(!$(ev.target).closest('.wfe-context-menu').length){ hideContextMenu(); }
     626            });
     627            $(document).on('keydown.wfeCtx', function(ev){ if(ev.key === 'Escape'){ hideContextMenu(); } });
     628            $(window).on('scroll.wfeCtx resize.wfeCtx', hideContextMenu);
     629        },0);
     630       
     631        $renameBtn.focus();
     632    }
     633   
    457634    // Global listeners now added dynamically per open; fallback in case menu injected elsewhere
    458635    // (No always-on handlers needed here.)
     636
     637    // --- File Upload Functionality ---
     638    const $fileInput = $('#wfe-file-input');
     639    const $uploadBtn = $('#wfe-upload-file');
     640    const $tree = $('#wfe-tree');
     641    const $dropZone = $('#wfe-drop-zone');
     642
     643    // Upload button click handler
     644    $uploadBtn.on('click', function() {
     645        $fileInput.click();
     646    });
     647
     648    // File input change handler
     649    $fileInput.on('change', function(e) {
     650        const files = e.target.files;
     651        if (files.length > 0) {
     652            uploadFiles(files);
     653        }
     654        // Reset the input so the same file can be selected again
     655        $fileInput.val('');
     656    });
     657
     658    // Drag and drop handlers
     659    $tree.on('dragover', function(e) {
     660        e.preventDefault();
     661        e.stopPropagation();
     662        $dropZone.addClass('wfe-drop-active');
     663    });
     664
     665    $tree.on('dragleave', function(e) {
     666        e.preventDefault();
     667        e.stopPropagation();
     668        // Only hide if leaving the tree completely
     669        if (e.target === $tree[0]) {
     670            $dropZone.removeClass('wfe-drop-active');
     671        }
     672    });
     673
     674    $tree.on('drop', function(e) {
     675        e.preventDefault();
     676        e.stopPropagation();
     677        $dropZone.removeClass('wfe-drop-active');
     678       
     679        const files = e.originalEvent.dataTransfer.files;
     680        if (files.length > 0) {
     681            uploadFiles(files);
     682        }
     683    });
     684
     685    // Function to upload files
     686    function uploadFiles(files) {
     687        if (!files || files.length === 0) return;
     688
     689        const totalFiles = files.length;
     690        let uploadedCount = 0;
     691        let failedCount = 0;
     692        const errors = [];
     693
     694        // Show progress bar
     695        const $progressOverlay = $('#wfe-upload-progress');
     696        const $progressBar = $('#wfe-progress-bar');
     697        const $progressText = $('#wfe-progress-text');
     698        const $progressStatus = $('#wfe-progress-status');
     699       
     700        $progressOverlay.fadeIn(200);
     701        $progressBar.css('width', '0%').text('');
     702        $progressText.text('0 / ' + totalFiles);
     703        $progressStatus.text(__('Preparing upload...', '0-day-analytics')).removeClass('success error warning');
     704
     705        Array.from(files).forEach(file => {
     706            const formData = new FormData();
     707            formData.append('action', 'advan_file_editor_upload_file');
     708            formData.append('_ajax_nonce', AFE_Ajax.nonce);
     709            formData.append('dir', currentDir);
     710            formData.append('file', file);
     711
     712            $.ajax({
     713                url: AFE_Ajax.ajax_url,
     714                type: 'POST',
     715                data: formData,
     716                processData: false,
     717                contentType: false,
     718                success: function(res) {
     719                    uploadedCount++;
     720                    if (!res.success) {
     721                        failedCount++;
     722                        errors.push(file.name + ': ' + res.data);
     723                    }
     724                    updateProgress();
     725                },
     726                error: function() {
     727                    uploadedCount++;
     728                    failedCount++;
     729                    errors.push(file.name + ': ' + __('Network error', '0-day-analytics'));
     730                    updateProgress();
     731                }
     732            });
     733        });
     734
     735        function updateProgress() {
     736            const percent = Math.round((uploadedCount / totalFiles) * 100);
     737            $progressBar.css('width', percent + '%').text(percent + '%');
     738            $progressText.text(uploadedCount + ' / ' + totalFiles);
     739           
     740            if (uploadedCount < totalFiles) {
     741                const successCount = uploadedCount - failedCount;
     742                $progressStatus.text(__('Uploading', '0-day-analytics') + ': ' + successCount + ' ' + __('successful', '0-day-analytics'));
     743            } else {
     744                // All uploads complete
     745                completeUpload();
     746            }
     747        }
     748
     749        function completeUpload() {
     750            const successCount = totalFiles - failedCount;
     751           
     752            // Update status message
     753            if (failedCount === 0) {
     754                $progressStatus.text('✅ ' + __('All files uploaded successfully!', '0-day-analytics')).addClass('success');
     755            } else if (successCount === 0) {
     756                $progressStatus.html('❌ ' + __('All uploads failed', '0-day-analytics') + '<br><small>' + errors.slice(0, 3).join('<br>') + (errors.length > 3 ? '<br>...' : '') + '</small>').addClass('error');
     757            } else {
     758                $progressStatus.html('⚠️ ' + __('Partial success', '0-day-analytics') + ': ' + successCount + '/' + totalFiles + '<br><small>' + errors.slice(0, 2).join('<br>') + (errors.length > 2 ? '<br>...' : '') + '</small>').addClass('warning');
     759            }
     760           
     761            // Refresh the file tree with smart refresh
     762            smartRefresh();
     763           
     764            // Auto-hide progress bar after 2.5 seconds
     765            setTimeout(function() {
     766                $progressOverlay.fadeOut(300);
     767            }, 2500);
     768        }
     769    }
    459770});
  • 0-day-analytics/tags/4.4.1/readme.txt

    r3423226 r3437565  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 4.4.0
     7Stable tag: 4.4.1
    88License: GPLv3 or later
    99License URI: https://www.gnu.org/licenses/gpl-3.0.txt
     
    9292== Changelog ==
    9393
     94= 4.4.1 =
     95* Small bug fixes and File Manager improvements.
     96
    9497= 4.4.0 =
    9598* Now the Table module has the option to insert records.
  • 0-day-analytics/trunk/advanced-analytics.php

    r3423226 r3437565  
    1111 * Plugin Name:     0 Day Analytics
    1212 * Description:     Take full control of error log, crons, transients, plugins, requests, mails and DB tables.
    13  * Version:         4.4.0
     13 * Version:         4.4.1
    1414 * Author:          Stoil Dobrev
    1515 * Author URI:      https://github.com/sdobreff/
     
    3838// Constants.
    3939if ( ! defined( 'ADVAN_VERSION' ) ) {
    40     define( 'ADVAN_VERSION', '4.4.0' );
     40    define( 'ADVAN_VERSION', '4.4.1' );
    4141    define( 'ADVAN_TEXTDOMAIN', '0-day-analytics' );
    4242    define( 'ADVAN_NAME', '0 Day Analytics' );
  • 0-day-analytics/trunk/classes/vendor/controllers/class-requests-log.php

    r3393178 r3437565  
    123123
    124124            // Prepare the log entry.
    125             $trace_array = \json_decode( self::$trace, true );
     125            $trace_array = \json_decode( self::get_trace(), true );
    126126            $log_entry   = array(
    127127                'url'            => $url,
     
    337337                'requests'       => self::$requests,
    338338                'trace'          => self::get_trace(),
    339                 'plugin'         => ( isset( \json_decode( self::$trace, true )[7] ) && isset( \json_decode( self::$trace, true )[7]['file'] ) ) ? Plugin_Theme_Helper::get_plugin_from_file_path( \json_decode( self::$trace, true )[7]['file'] ) : '',
     339                'plugin'         => ( isset( \json_decode( self::get_trace(), true )[7] ) && isset( \json_decode( self::get_trace(), true )[7]['file'] ) ) ? Plugin_Theme_Helper::get_plugin_from_file_path( \json_decode( self::get_trace(), true )[7]['file'] ) : '',
    340340            );
    341341
  • 0-day-analytics/trunk/classes/vendor/helpers/class-ajax-helper.php

    r3413453 r3437565  
    163163                    \add_action( 'wp_ajax_advan_file_editor_diff', array( File_Editor::class, 'ajax_diff' ) );
    164164                    \add_action( 'wp_ajax_advan_file_editor_create', array( File_Editor::class, 'ajax_create' ) );
     165                    \add_action( 'wp_ajax_advan_file_editor_upload_file', array( File_Editor::class, 'ajax_upload_file' ) );
    165166                    \add_action( 'wp_ajax_advan_file_editor_delete', array( File_Editor::class, 'ajax_delete' ) );
    166167                    \add_action( 'wp_ajax_advan_file_editor_restore', array( File_Editor::class, 'ajax_restore' ) );
  • 0-day-analytics/trunk/classes/vendor/views/class-file-editor.php

    r3413453 r3437565  
    294294                                <button id="wfe-new-file" class="button"><?php \esc_html_e( '+ File', '0-day-analytics' ); ?></button>
    295295                                <button id="wfe-new-folder" class="button"><?php \esc_html_e( '+ Folder', '0-day-analytics' ); ?></button>
    296                                 <button id="wfe-empty-trash" class="button button-danger"><?php \esc_html_e( '🧹 Empty Trash', '0-day-analytics' ); ?></button>
     296                            <button id="wfe-upload-file" class="button button-primary"><?php \esc_html_e( '⬆️ Upload', '0-day-analytics' ); ?></button>
     297                            <button id="wfe-empty-trash" class="button button-danger"><?php \esc_html_e( '🧹 Empty Trash', '0-day-analytics' ); ?></button>
     298                            <input type="file" id="wfe-file-input" style="display: none;" multiple />
    297299                            </div>
    298300                            <div class="wfe-dir-wrapper">
    299                                 <div id="wfe-tree" class="wfe-tree"></div>
     301                                <div id="wfe-tree" class="wfe-tree">
     302                                    <div id="wfe-drop-zone" class="wfe-drop-zone">
     303                                        <p><?php \esc_html_e( '📁 Drop files here to upload', '0-day-analytics' ); ?></p>
     304                                    </div>
     305                                </div>
    300306                            </div>
    301307                        </div>
     
    318324                </div>
    319325            </div>
     326           
     327            <!-- Upload Progress Bar -->
     328            <div id="wfe-upload-progress" class="wfe-upload-progress" style="display: none;">
     329                <div class="wfe-upload-progress-content">
     330                    <h3><?php \esc_html_e( 'Uploading Files', '0-day-analytics' ); ?></h3>
     331                    <div class="wfe-progress-bar-container">
     332                        <div id="wfe-progress-bar" class="wfe-progress-bar"></div>
     333                    </div>
     334                    <div id="wfe-progress-text" class="wfe-progress-text">0 / 0</div>
     335                    <div id="wfe-progress-status" class="wfe-progress-status"></div>
     336                </div>
     337            </div>
    320338            <?php
    321339        }
     
    437455            }
    438456            @rmdir( $dir );
     457        }
     458
     459        /**
     460         * AJAX: Uploads a file
     461         *
     462         * @return void
     463         *
     464         * @since 4.0.0
     465         */
     466        public static function ajax_upload_file() {
     467            // Standardize nonce and capability checks.
     468            WP_Helper::verify_admin_nonce( 'advan_file_editor_nonce', '_ajax_nonce' );
     469
     470            if ( empty( $_FILES['file'] ) ) {
     471                \wp_send_json_error( 'No file uploaded.' );
     472            }
     473
     474            $dir    = \sanitize_text_field( $_POST['dir'] ?? self::BASE_DIR );
     475            $parent = self::safe_path( $dir );
     476            if ( ! $parent || ! is_dir( $parent ) ) {
     477                \wp_send_json_error( 'Invalid directory.' );
     478            }
     479
     480            $file  = $_FILES['file'];
     481            $error = $file['error'];
     482
     483            if ( UPLOAD_ERR_OK !== $error ) {
     484                \wp_send_json_error( 'Upload error: ' . $error );
     485            }
     486
     487            $filename    = \sanitize_file_name( $file['name'] );
     488            $target_path = $parent . DIRECTORY_SEPARATOR . $filename;
     489
     490            // Check if file already exists.
     491            if ( file_exists( $target_path ) ) {
     492                \wp_send_json_error( 'File already exists: ' . $filename );
     493            }
     494
     495            // Move uploaded file to target directory.
     496            if ( ! move_uploaded_file( $file['tmp_name'], $target_path ) ) {
     497                \wp_send_json_error( 'Failed to save uploaded file.' );
     498            }
     499
     500            \wp_send_json_success(
     501                array(
     502                    'message' => 'File uploaded successfully.',
     503                    'path'    => $target_path,
     504                    'name'    => $filename,
     505                )
     506            );
    439507        }
    440508
     
    497565                $display = $file;
    498566                if ( 'file' === $type ) {
    499                     $size     = @filesize( $path );
     567                    $size = @filesize( $path );
    500568                    if ( false !== $size ) {
    501569                        $display .= ' (' . \size_format( (int) $size ) . ')';
     
    587655            }
    588656
    589             $trash_path = self::get_trash_dir() . \DIRECTORY_SEPARATOR . basename( $real ) . '.' . time();
    590             if ( rename( $real, $trash_path ) ) {
     657            // Check permissions
     658            if ( ! is_writable( dirname( $real ) ) ) {
     659                \wp_send_json_error( 'Permission denied: parent directory not writable.' );
     660            }
     661
     662            // Ensure trash directory exists and is writable
     663            $trash_dir = self::get_trash_dir();
     664            if ( ! is_dir( $trash_dir ) ) {
     665                @\wp_mkdir_p( $trash_dir );
     666                self::harden_directory( $trash_dir );
     667            }
     668
     669            if ( ! is_writable( $trash_dir ) ) {
     670                \wp_send_json_error( 'Trash directory is not writable.' );
     671            }
     672
     673            // Generate unique trash path
     674            $base_name  = basename( $real );
     675            $trash_path = $trash_dir . \DIRECTORY_SEPARATOR . $base_name . '.' . time();
     676            $counter    = 0;
     677            while ( file_exists( $trash_path ) ) {
     678                ++$counter;
     679                $trash_path = $trash_dir . \DIRECTORY_SEPARATOR . $base_name . '.' . time() . '_' . $counter;
     680            }
     681
     682            // Try to move to trash
     683            if ( @rename( $real, $trash_path ) ) {
    591684                self::$last_deleted = array(
    592685                    'src'   => $real,
     
    594687                );
    595688                self::save_meta();
    596                 \wp_send_json_success( 'Moved to trash.' );
     689                $msg = is_dir( $trash_path ) ? 'Directory moved to trash.' : 'Moved to trash.';
     690                \wp_send_json_success( $msg );
    597691            } else {
    598                 \wp_send_json_error( 'Unable to move to trash.' );
    599             }
    600         }
    601 
    602         /**
    603          * AJAX: Restores the last deleted file
    604          *
    605          * @return void
    606          *
    607          * @since 4.0.0
    608          */
    609         public static function ajax_restore() {
    610             WP_Helper::verify_admin_nonce( 'advan_file_editor_nonce', '_ajax_nonce' );
    611 
    612             self::load_meta();
    613             if ( ! self::get_last_deleted() || ! file_exists( self::$last_deleted['trash'] ) ) {
    614                 \wp_send_json_error( 'Nothing to restore.' );
     692                // If rename fails, try alternative approach for directories
     693                if ( is_dir( $real ) ) {
     694                    // Try to copy recursively and then delete
     695                    if ( self::recursive_copy( $real, $trash_path ) ) {
     696                        self::delete_dir( $real );
     697                        if ( ! file_exists( $real ) ) {
     698                            self::$last_deleted = array(
     699                                'src'   => $real,
     700                                'trash' => $trash_path,
     701                            );
     702                            self::save_meta();
     703                            \wp_send_json_success( 'Directory moved to trash.' );
     704                        } else {
     705                            // Cleanup trash if original still exists
     706                            self::delete_dir( $trash_path );
     707                            \wp_send_json_error( 'Unable to delete directory after copying.' );
     708                        }
     709                    } else {
     710                        \wp_send_json_error( 'Unable to copy directory to trash.' );
     711                    }
     712                } else {
     713                    \wp_send_json_error( 'Unable to move to trash. Check file permissions.' );
     714                }
    615715            }
    616716            $src   = self::$last_deleted['src'];
     
    835935            }
    836936
    837             \wp_send_json_success( array( 'new_path' => $target, 'new_name' => basename( $target ) ) );
     937            \wp_send_json_success(
     938                array(
     939                    'new_path' => $target,
     940                    'new_name' => basename( $target ),
     941                )
     942            );
    838943        }
    839944
     
    863968
    864969            // Determine target name.
    865             $ext      = '';
     970            $ext       = '';
    866971            $name_only = $basename;
    867972            if ( ! $is_dir && false !== strpos( $basename, '.' ) ) {
    868                 $parts    = explode( '.', $basename );
    869                 $ext      = array_pop( $parts );
     973                $parts     = explode( '.', $basename );
     974                $ext       = array_pop( $parts );
    870975                $name_only = implode( '.', $parts );
    871976            }
     
    875980            $target         = '';
    876981            while ( true ) {
    877                 $suffix = ( $counter > 1 ) ? '-' . $counter : '';
     982                $suffix      = ( $counter > 1 ) ? '-' . $counter : '';
    878983                $target_name = $base_candidate . $suffix . ( $is_dir ? '' : ( ( '' !== $ext ) ? '.' . $ext : '' ) );
    879984                $target      = $dirname . \DIRECTORY_SEPARATOR . $target_name;
     
    881986                    break;
    882987                }
    883                 $counter++;
     988                ++$counter;
    884989            }
    885990
     
    887992            if ( $is_dir ) {
    888993                self::recursive_copy( $real, $target );
    889             } else {
    890                 if ( ! @copy( $real, $target ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     994            } elseif ( ! @copy( $real, $target ) ) {
     995                // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
    891996                    \wp_send_json_error( 'Unable to duplicate file.' );
    892                 }
    893             }
    894 
    895             \wp_send_json_success( array( 'new_path' => $target, 'new_name' => basename( $target ) ) );
     997            }
     998
     999            \wp_send_json_success(
     1000                array(
     1001                    'new_path' => $target,
     1002                    'new_name' => basename( $target ),
     1003                )
     1004            );
    8961005        }
    8971006
     
    9021011         * @param string $dst Destination dir.
    9031012         *
    904          * @return void
     1013         * @return bool True on success, false on failure.
    9051014         *
    9061015         * @since 4.1.1
     
    9081017        private static function recursive_copy( $src, $dst ) {
    9091018            if ( ! is_dir( $src ) ) {
    910                 return;
    911             }
    912             @wp_mkdir_p( $dst );
     1019                return false;
     1020            }
     1021            if ( ! @wp_mkdir_p( $dst ) ) {
     1022                return false;
     1023            }
    9131024            $dir_handle = opendir( $src );
    9141025            if ( false === $dir_handle ) {
    915                 return;
    916             }
     1026                return false;
     1027            }
     1028            $success = true;
    9171029            while ( false !== ( $item = readdir( $dir_handle ) ) ) {
    9181030                if ( '.' === $item || '..' === $item ) {
     
    9221034                $to   = $dst . \DIRECTORY_SEPARATOR . $item;
    9231035                if ( is_dir( $from ) ) {
    924                     self::recursive_copy( $from, $to );
    925                 } else {
    926                     @copy( $from, $to ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     1036                    if ( ! self::recursive_copy( $from, $to ) ) {
     1037                        $success = false;
     1038                    }
     1039                } elseif ( ! @copy( $from, $to ) ) {
     1040                    // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
     1041                        $success = false;
    9271042                }
    9281043            }
    9291044            closedir( $dir_handle );
     1045            return $success;
    9301046        }
    9311047    }
  • 0-day-analytics/trunk/css/block-editor.css

    r3423226 r3437565  
    3232    display: inline-block;
    3333    line-height: 1;
     34    vertical-align: bottom;
    3435}
    3536
  • 0-day-analytics/trunk/css/wfe.css

    r3423226 r3437565  
    172172    background: #1e3e59;
    173173}
    174 .wfe-tree {
    175     border-left: 1px solid #8d7a7a73;
    176     border-bottom: 1px solid #8d7a7a73;
    177     border-top: 1px solid #8d7a7a73;
    178 }
    179174.wfe-tree ul ul {
    180175    list-style: none;
     
    252247    height: 80vh;
    253248}
     249
     250/* Drop zone styling */
     251.wfe-drop-zone {
     252    display: none;
     253    position: absolute;
     254    top: 0;
     255    left: 0;
     256    right: 0;
     257    bottom: 0;
     258    background: rgba(33, 150, 243, 0.9);
     259    border: 3px dashed #fff;
     260    border-radius: 8px;
     261    z-index: 1000;
     262    pointer-events: none;
     263    align-items: center;
     264    justify-content: center;
     265    margin: 10px;
     266}
     267
     268.wfe-drop-zone.wfe-drop-active {
     269    display: flex;
     270}
     271
     272.wfe-drop-zone p {
     273    color: #fff;
     274    font-size: 18px;
     275    font-weight: 600;
     276    text-align: center;
     277    margin: 0;
     278    padding: 20px;
     279}
     280
     281.aadvana-darkskin .wfe-drop-zone {
     282    background: rgba(30, 62, 89, 0.95);
     283    border-color: #2196f3;
     284}
     285
     286/* Upload button styling */
     287#wfe-upload-file {
     288    background: #2196f3 !important;
     289    border-color: #1976d2 !important;
     290    color: #fff !important;
     291}
     292
     293#wfe-upload-file:hover {
     294    background: #1976d2 !important;
     295}
     296
     297/* Tree positioning for drop zone */
     298.wfe-tree {
     299    position: relative;
     300    border-left: 1px solid #8d7a7a73;
     301    border-bottom: 1px solid #8d7a7a73;
     302    border-top: 1px solid #8d7a7a73;
     303}
     304
     305/* Upload Progress Bar */
     306.wfe-upload-progress {
     307    position: fixed;
     308    top: 0;
     309    left: 0;
     310    right: 0;
     311    bottom: 0;
     312    background: rgba(0, 0, 0, 0.7);
     313    z-index: 10000;
     314    display: flex;
     315    align-items: center;
     316    justify-content: center;
     317}
     318
     319.wfe-upload-progress-content {
     320    background: #fff;
     321    padding: 30px;
     322    border-radius: 8px;
     323    min-width: 400px;
     324    max-width: 500px;
     325    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
     326}
     327
     328.aadvana-darkskin .wfe-upload-progress-content {
     329    background: #13273b;
     330    color: #c9c0c0;
     331}
     332
     333.wfe-upload-progress-content h3 {
     334    margin: 0 0 20px 0;
     335    font-size: 18px;
     336    font-weight: 600;
     337    text-align: center;
     338}
     339
     340.wfe-progress-bar-container {
     341    width: 100%;
     342    height: 30px;
     343    background: #e0e0e0;
     344    border-radius: 15px;
     345    overflow: hidden;
     346    margin-bottom: 15px;
     347}
     348
     349.aadvana-darkskin .wfe-progress-bar-container {
     350    background: #1e3e59;
     351}
     352
     353.wfe-progress-bar {
     354    height: 100%;
     355    background: linear-gradient(90deg, #2196f3, #1976d2);
     356    width: 0%;
     357    transition: width 0.3s ease;
     358    display: flex;
     359    align-items: center;
     360    justify-content: center;
     361    color: #fff;
     362    font-weight: 600;
     363    font-size: 14px;
     364}
     365
     366.wfe-progress-text {
     367    text-align: center;
     368    font-size: 16px;
     369    font-weight: 600;
     370    margin-bottom: 10px;
     371}
     372
     373.wfe-progress-status {
     374    text-align: center;
     375    font-size: 13px;
     376    color: #666;
     377    min-height: 20px;
     378}
     379
     380.aadvana-darkskin .wfe-progress-status {
     381    color: #999;
     382}
     383
     384.wfe-progress-status.success {
     385    color: #46b450;
     386    font-weight: 600;
     387}
     388
     389.wfe-progress-status.error {
     390    color: #d63638;
     391    font-weight: 600;
     392}
     393
     394.wfe-progress-status.warning {
     395    color: #f0b849;
     396    font-weight: 600;
     397}
  • 0-day-analytics/trunk/js/admin/wfe.js

    r3413453 r3437565  
    11jQuery(function ($) {
    22    let editor, currentFile = null, currentDir = AFE_Ajax.base, { __ } = wp.i18n;
     3    let expandedFolders = new Set(); // Track expanded folder paths
    34
    45    // CodeMirror init
     
    1920    editor = wp.codeEditor.initialize($('#wfe-editor'), settings).codemirror;
    2021
     22    // Function to get all currently expanded folder paths
     23    function getExpandedFolders() {
     24        const expanded = [];
     25        $('#wfe-tree .wfe-item.dir').each(function() {
     26            const $li = $(this);
     27            const $sub = $li.children('ul');
     28            if ($sub.length && $sub.is(':visible')) {
     29                expanded.push($li.data('path'));
     30            }
     31        });
     32        return expanded;
     33    }
     34
     35    // Smart refresh that preserves folder states
     36    function smartRefresh() {
     37        const expanded = getExpandedFolders();
     38        $('#wfe-tree').empty();
     39        loadDir(AFE_Ajax.base, $('#wfe-tree'), function() {
     40            // After tree is loaded, restore expanded folders in hierarchical order
     41            if (expanded.length > 0) {
     42                // Sort paths by depth (number of separators) to expand parent folders first
     43                expanded.sort((a, b) => {
     44                    const aDepth = (a.match(/\//g) || []).length;
     45                    const bDepth = (b.match(/\//g) || []).length;
     46                    return aDepth - bDepth;
     47                });
     48               
     49                // Restore folders sequentially to ensure parent folders are loaded before children
     50                restoreFoldersSequentially(expanded, 0);
     51            }
     52        });
     53    }
     54
     55    // Recursively restore folders in order
     56    function restoreFoldersSequentially(paths, index) {
     57        if (index >= paths.length) return;
     58       
     59        const path = paths[index];
     60        const $folder = $('#wfe-tree .wfe-item.dir').filter(function() {
     61            return $(this).data('path') === path;
     62        });
     63       
     64        if ($folder.length) {
     65            const $sub = $folder.children('ul');
     66            if ($sub.length) {
     67                // Folder already has content, just expand it
     68                $sub.show();
     69                $folder.children('.toggle').text('▼');
     70                expandedFolders.add(path);
     71                // Continue with next folder
     72                restoreFoldersSequentially(paths, index + 1);
     73            } else {
     74                // Need to load folder content first
     75                loadDir(path, $folder, function() {
     76                    $folder.children('ul').show();
     77                    $folder.children('.toggle').text('▼');
     78                    expandedFolders.add(path);
     79                    // Continue with next folder after this one loads
     80                    restoreFoldersSequentially(paths, index + 1);
     81                });
     82            }
     83        } else {
     84            // Folder not found, skip to next
     85            restoreFoldersSequentially(paths, index + 1);
     86        }
     87    }
     88
     89    // Function to expand folders by path
     90    function expandFolderByPath(path) {
     91        const $folder = $('#wfe-tree .wfe-item.dir').filter(function() {
     92            return $(this).data('path') === path;
     93        });
     94       
     95        if ($folder.length) {
     96            const $sub = $folder.children('ul');
     97            if ($sub.length && !$sub.is(':visible')) {
     98                $sub.show();
     99                $folder.children('.toggle').text('▼');
     100            }
     101        }
     102    }
     103
    21104    // Load directory
    22     function loadDir(dir, container) {
     105    function loadDir(dir, container, callback) {
    23106        $.post(AFE_Ajax.ajax_url, { action: 'advan_file_editor_list_dir', dir, _ajax_nonce: AFE_Ajax.nonce }, (res) => {
    24107            if (res.success) {
     
    32115                    ul.append(li);
    33116                });
    34                 container.append(ul);
    35             }
     117                container.append(ul);                if (callback) callback();            }
    36118        });
    37119    }
     
    48130        if (sub.length) {
    49131            sub.toggle();
    50             li.children('.toggle').text(sub.is(':visible') ? '▼' : '▶');
     132            const isVisible = sub.is(':visible');
     133            li.children('.toggle').text(isVisible ? '▼' : '▶');
     134            // Track expanded state
     135            if (isVisible) {
     136                expandedFolders.add(path);
     137            } else {
     138                expandedFolders.delete(path);
     139            }
    51140        } else {
    52141            li.children('.toggle').text('▼');
    53142            loadDir(path, li);
     143            expandedFolders.add(path);
    54144        }
    55145    });
     
    123213        }, (res) => {
    124214            alert(res.success ? __('✅ Created.', '0-day-analytics') : '❌ ' + res.data);
    125             $('#wfe-tree').empty();
    126             loadDir(AFE_Ajax.base, $('#wfe-tree'));
     215            smartRefresh();
    127216        });
    128217    });
     
    138227        }, (res) => {
    139228            alert(res.success ? __('🗑️ Deleted.', '0-day-analytics') : '❌ ' + res.data);
    140             $('#wfe-tree').empty();
    141             loadDir(AFE_Ajax.base, $('#wfe-tree'));
     229            smartRefresh();
    142230            $('#wfe-filename').text(__('No file selected', '0-day-analytics'));
    143231            editor.setValue('');
     
    153241        }, (res) => {
    154242            alert(res.success ? '♻ ' + res.data.msg : '❌ ' + res.data);
    155             $('#wfe-tree').empty();
    156             loadDir(AFE_Ajax.base, $('#wfe-tree'));
     243            smartRefresh();
    157244        });
    158245    });
     
    385472                        $('#wfe-filename').text(res.data.new_path);
    386473                    }
    387                     $('#wfe-tree').empty();
    388                     loadDir(AFE_Ajax.base, $('#wfe-tree'));
     474                    smartRefresh();
    389475                }
    390476            });
     
    411497                        $('#wfe-filename').text(__('No file selected','0-day-analytics'));
    412498                    }
    413                     $('#wfe-tree').empty();
    414                     loadDir(AFE_Ajax.base, $('#wfe-tree'));
     499                    smartRefresh();
    415500                }
    416501            });
     
    423508                alert(res.success ? '🧬 '+__('Duplicated','0-day-analytics')+': '+res.data.new_name : '❌ '+res.data);
    424509                if(res.success){
    425                     $('#wfe-tree').empty();
    426                     loadDir(AFE_Ajax.base, $('#wfe-tree'));
     510                    smartRefresh();
    427511                }
    428512            });
     
    452536    $('#wfe-tree').on('contextmenu', '.wfe-item.file', function(e){
    453537        e.preventDefault();
     538        e.stopPropagation();
    454539        const path = $(this).data('path');
    455540        showContextMenu(e, path);
    456541    });
     542
     543    // Context menu for directories
     544    $('#wfe-tree').on('contextmenu', '.wfe-item.dir', function(e){
     545        e.preventDefault();
     546        e.stopPropagation();
     547        const path = $(this).data('path');
     548        showDirectoryContextMenu(e, path);
     549    });
     550
     551    // Directory-specific context menu
     552    function showDirectoryContextMenu(e, dirPath){
     553        hideContextMenu();
     554        $ctxMenu = $('<div class="wfe-context-menu" role="menu"></div>');
     555       
     556        // Rename
     557        const $renameBtn = $('<button type="button" role="menuitem">✏️ '+__('Rename','0-day-analytics')+'</button>');
     558        $renameBtn.on('click', function(){
     559            const currentBase = dirPath.substring(dirPath.lastIndexOf('/'));
     560            const newName = prompt(__('Enter new name:','0-day-analytics'), currentBase.replace('/',''));
     561            if(!newName){ return; }
     562            $.post(AFE_Ajax.ajax_url, { action:'advan_file_editor_rename', file:dirPath, new_name:newName, _ajax_nonce: AFE_Ajax.nonce }, function(res){
     563                alert(res.success ? '✅ '+__('Renamed','0-day-analytics') : '❌ '+res.data);
     564                if(res.success){
     565                    smartRefresh();
     566                }
     567            });
     568            hideContextMenu();
     569        });
     570       
     571        // Copy Path
     572        const $copyPathBtn = $('<button type="button" role="menuitem">📋 '+__('Copy Path','0-day-analytics')+'</button>');
     573        $copyPathBtn.on('click', function(){
     574            navigator.clipboard.writeText(dirPath).then(()=>{
     575                alert('📋 '+__('Path copied','0-day-analytics'));
     576            }).catch(()=>{ alert('❌ '+__('Unable to copy path','0-day-analytics')); });
     577            hideContextMenu();
     578        });
     579       
     580        // Delete Directory (Recursive)
     581        const $deleteBtn = $('<button type="button" role="menuitem" style="color: #d63638; font-weight: 600;">🗑️ '+__('Delete Recursively','0-day-analytics')+'</button>');
     582        $deleteBtn.on('click', function(){
     583            if(!confirm(__('⚠️ WARNING: This will RECURSIVELY delete the entire directory and all its contents!','0-day-analytics')+'\n\n'+__('Directory:','0-day-analytics')+' '+dirPath+'\n\n'+__('This action cannot be undone. Are you absolutely sure?','0-day-analytics'))) {
     584                return;
     585            }
     586            // Double confirmation
     587            if(!confirm(__('FINAL CONFIRMATION: Delete','0-day-analytics')+' '+dirPath+' '+__('and ALL its contents?','0-day-analytics'))) {
     588                return;
     589            }
     590            $.post(AFE_Ajax.ajax_url, { action:'advan_file_editor_delete', path:dirPath, _ajax_nonce:AFE_Ajax.nonce }, function(res){
     591                alert(res.success ? '🗑️ '+__('Directory deleted','0-day-analytics') : '❌ '+res.data);
     592                if(res.success){
     593                    smartRefresh();
     594                }
     595            });
     596            hideContextMenu();
     597        });
     598       
     599        // Duplicate
     600        const $duplicateBtn = $('<button type="button" role="menuitem">🧬 '+__('Duplicate','0-day-analytics')+'</button>');
     601        $duplicateBtn.on('click', function(){
     602            $.post(AFE_Ajax.ajax_url, { action:'advan_file_editor_duplicate', file:dirPath, _ajax_nonce:AFE_Ajax.nonce }, function(res){
     603                alert(res.success ? '🧬 '+__('Duplicated','0-day-analytics')+': '+res.data.new_name : '❌ '+res.data);
     604                if(res.success){
     605                    smartRefresh();
     606                }
     607            });
     608            hideContextMenu();
     609        });
     610       
     611        $ctxMenu.append($renameBtn, $duplicateBtn, $copyPathBtn, $deleteBtn);
     612        $('body').append($ctxMenu);
     613        const x = e.pageX;
     614        const y = e.pageY;
     615        $ctxMenu.css({ top: y + 'px', left: x + 'px' });
     616       
     617        // Delay binding to avoid immediate self-close
     618        setTimeout(function(){
     619            $(document).on('mousedown.wfeCtx', function(ev){
     620                if(!$ctxMenu) return;
     621                if(!$(ev.target).closest('.wfe-context-menu').length){ hideContextMenu(); }
     622            });
     623            $(document).on('contextmenu.wfeCtx', function(ev){
     624                if(!$ctxMenu) return;
     625                if(!$(ev.target).closest('.wfe-context-menu').length){ hideContextMenu(); }
     626            });
     627            $(document).on('keydown.wfeCtx', function(ev){ if(ev.key === 'Escape'){ hideContextMenu(); } });
     628            $(window).on('scroll.wfeCtx resize.wfeCtx', hideContextMenu);
     629        },0);
     630       
     631        $renameBtn.focus();
     632    }
     633   
    457634    // Global listeners now added dynamically per open; fallback in case menu injected elsewhere
    458635    // (No always-on handlers needed here.)
     636
     637    // --- File Upload Functionality ---
     638    const $fileInput = $('#wfe-file-input');
     639    const $uploadBtn = $('#wfe-upload-file');
     640    const $tree = $('#wfe-tree');
     641    const $dropZone = $('#wfe-drop-zone');
     642
     643    // Upload button click handler
     644    $uploadBtn.on('click', function() {
     645        $fileInput.click();
     646    });
     647
     648    // File input change handler
     649    $fileInput.on('change', function(e) {
     650        const files = e.target.files;
     651        if (files.length > 0) {
     652            uploadFiles(files);
     653        }
     654        // Reset the input so the same file can be selected again
     655        $fileInput.val('');
     656    });
     657
     658    // Drag and drop handlers
     659    $tree.on('dragover', function(e) {
     660        e.preventDefault();
     661        e.stopPropagation();
     662        $dropZone.addClass('wfe-drop-active');
     663    });
     664
     665    $tree.on('dragleave', function(e) {
     666        e.preventDefault();
     667        e.stopPropagation();
     668        // Only hide if leaving the tree completely
     669        if (e.target === $tree[0]) {
     670            $dropZone.removeClass('wfe-drop-active');
     671        }
     672    });
     673
     674    $tree.on('drop', function(e) {
     675        e.preventDefault();
     676        e.stopPropagation();
     677        $dropZone.removeClass('wfe-drop-active');
     678       
     679        const files = e.originalEvent.dataTransfer.files;
     680        if (files.length > 0) {
     681            uploadFiles(files);
     682        }
     683    });
     684
     685    // Function to upload files
     686    function uploadFiles(files) {
     687        if (!files || files.length === 0) return;
     688
     689        const totalFiles = files.length;
     690        let uploadedCount = 0;
     691        let failedCount = 0;
     692        const errors = [];
     693
     694        // Show progress bar
     695        const $progressOverlay = $('#wfe-upload-progress');
     696        const $progressBar = $('#wfe-progress-bar');
     697        const $progressText = $('#wfe-progress-text');
     698        const $progressStatus = $('#wfe-progress-status');
     699       
     700        $progressOverlay.fadeIn(200);
     701        $progressBar.css('width', '0%').text('');
     702        $progressText.text('0 / ' + totalFiles);
     703        $progressStatus.text(__('Preparing upload...', '0-day-analytics')).removeClass('success error warning');
     704
     705        Array.from(files).forEach(file => {
     706            const formData = new FormData();
     707            formData.append('action', 'advan_file_editor_upload_file');
     708            formData.append('_ajax_nonce', AFE_Ajax.nonce);
     709            formData.append('dir', currentDir);
     710            formData.append('file', file);
     711
     712            $.ajax({
     713                url: AFE_Ajax.ajax_url,
     714                type: 'POST',
     715                data: formData,
     716                processData: false,
     717                contentType: false,
     718                success: function(res) {
     719                    uploadedCount++;
     720                    if (!res.success) {
     721                        failedCount++;
     722                        errors.push(file.name + ': ' + res.data);
     723                    }
     724                    updateProgress();
     725                },
     726                error: function() {
     727                    uploadedCount++;
     728                    failedCount++;
     729                    errors.push(file.name + ': ' + __('Network error', '0-day-analytics'));
     730                    updateProgress();
     731                }
     732            });
     733        });
     734
     735        function updateProgress() {
     736            const percent = Math.round((uploadedCount / totalFiles) * 100);
     737            $progressBar.css('width', percent + '%').text(percent + '%');
     738            $progressText.text(uploadedCount + ' / ' + totalFiles);
     739           
     740            if (uploadedCount < totalFiles) {
     741                const successCount = uploadedCount - failedCount;
     742                $progressStatus.text(__('Uploading', '0-day-analytics') + ': ' + successCount + ' ' + __('successful', '0-day-analytics'));
     743            } else {
     744                // All uploads complete
     745                completeUpload();
     746            }
     747        }
     748
     749        function completeUpload() {
     750            const successCount = totalFiles - failedCount;
     751           
     752            // Update status message
     753            if (failedCount === 0) {
     754                $progressStatus.text('✅ ' + __('All files uploaded successfully!', '0-day-analytics')).addClass('success');
     755            } else if (successCount === 0) {
     756                $progressStatus.html('❌ ' + __('All uploads failed', '0-day-analytics') + '<br><small>' + errors.slice(0, 3).join('<br>') + (errors.length > 3 ? '<br>...' : '') + '</small>').addClass('error');
     757            } else {
     758                $progressStatus.html('⚠️ ' + __('Partial success', '0-day-analytics') + ': ' + successCount + '/' + totalFiles + '<br><small>' + errors.slice(0, 2).join('<br>') + (errors.length > 2 ? '<br>...' : '') + '</small>').addClass('warning');
     759            }
     760           
     761            // Refresh the file tree with smart refresh
     762            smartRefresh();
     763           
     764            // Auto-hide progress bar after 2.5 seconds
     765            setTimeout(function() {
     766                $progressOverlay.fadeOut(300);
     767            }, 2500);
     768        }
     769    }
    459770});
  • 0-day-analytics/trunk/readme.txt

    r3423226 r3437565  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 4.4.0
     7Stable tag: 4.4.1
    88License: GPLv3 or later
    99License URI: https://www.gnu.org/licenses/gpl-3.0.txt
     
    9292== Changelog ==
    9393
     94= 4.4.1 =
     95* Small bug fixes and File Manager improvements.
     96
    9497= 4.4.0 =
    9598* Now the Table module has the option to insert records.
Note: See TracChangeset for help on using the changeset viewer.