Changeset 3437565
- Timestamp:
- 01/12/2026 10:18:43 AM (2 months ago)
- Location:
- 0-day-analytics
- Files:
-
- 16 edited
- 1 copied
-
tags/4.4.1 (copied) (copied from 0-day-analytics/trunk)
-
tags/4.4.1/advanced-analytics.php (modified) (2 diffs)
-
tags/4.4.1/classes/vendor/controllers/class-requests-log.php (modified) (2 diffs)
-
tags/4.4.1/classes/vendor/helpers/class-ajax-helper.php (modified) (1 diff)
-
tags/4.4.1/classes/vendor/views/class-file-editor.php (modified) (14 diffs)
-
tags/4.4.1/css/block-editor.css (modified) (1 diff)
-
tags/4.4.1/css/wfe.css (modified) (2 diffs)
-
tags/4.4.1/js/admin/wfe.js (modified) (11 diffs)
-
tags/4.4.1/readme.txt (modified) (2 diffs)
-
trunk/advanced-analytics.php (modified) (2 diffs)
-
trunk/classes/vendor/controllers/class-requests-log.php (modified) (2 diffs)
-
trunk/classes/vendor/helpers/class-ajax-helper.php (modified) (1 diff)
-
trunk/classes/vendor/views/class-file-editor.php (modified) (14 diffs)
-
trunk/css/block-editor.css (modified) (1 diff)
-
trunk/css/wfe.css (modified) (2 diffs)
-
trunk/js/admin/wfe.js (modified) (11 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
0-day-analytics/tags/4.4.1/advanced-analytics.php
r3423226 r3437565 11 11 * Plugin Name: 0 Day Analytics 12 12 * Description: Take full control of error log, crons, transients, plugins, requests, mails and DB tables. 13 * Version: 4.4. 013 * Version: 4.4.1 14 14 * Author: Stoil Dobrev 15 15 * Author URI: https://github.com/sdobreff/ … … 38 38 // Constants. 39 39 if ( ! defined( 'ADVAN_VERSION' ) ) { 40 define( 'ADVAN_VERSION', '4.4. 0' );40 define( 'ADVAN_VERSION', '4.4.1' ); 41 41 define( 'ADVAN_TEXTDOMAIN', '0-day-analytics' ); 42 42 define( 'ADVAN_NAME', '0 Day Analytics' ); -
0-day-analytics/tags/4.4.1/classes/vendor/controllers/class-requests-log.php
r3393178 r3437565 123 123 124 124 // Prepare the log entry. 125 $trace_array = \json_decode( self:: $trace, true );125 $trace_array = \json_decode( self::get_trace(), true ); 126 126 $log_entry = array( 127 127 'url' => $url, … … 337 337 'requests' => self::$requests, 338 338 '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'] ) : '', 340 340 ); 341 341 -
0-day-analytics/tags/4.4.1/classes/vendor/helpers/class-ajax-helper.php
r3413453 r3437565 163 163 \add_action( 'wp_ajax_advan_file_editor_diff', array( File_Editor::class, 'ajax_diff' ) ); 164 164 \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' ) ); 165 166 \add_action( 'wp_ajax_advan_file_editor_delete', array( File_Editor::class, 'ajax_delete' ) ); 166 167 \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 294 294 <button id="wfe-new-file" class="button"><?php \esc_html_e( '+ File', '0-day-analytics' ); ?></button> 295 295 <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 /> 297 299 </div> 298 300 <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> 300 306 </div> 301 307 </div> … … 318 324 </div> 319 325 </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> 320 338 <?php 321 339 } … … 437 455 } 438 456 @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 ); 439 507 } 440 508 … … 497 565 $display = $file; 498 566 if ( 'file' === $type ) { 499 $size = @filesize( $path );567 $size = @filesize( $path ); 500 568 if ( false !== $size ) { 501 569 $display .= ' (' . \size_format( (int) $size ) . ')'; … … 587 655 } 588 656 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 ) ) { 591 684 self::$last_deleted = array( 592 685 'src' => $real, … … 594 687 ); 595 688 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 ); 597 691 } 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 } 615 715 } 616 716 $src = self::$last_deleted['src']; … … 835 935 } 836 936 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 ); 838 943 } 839 944 … … 863 968 864 969 // Determine target name. 865 $ext = '';970 $ext = ''; 866 971 $name_only = $basename; 867 972 if ( ! $is_dir && false !== strpos( $basename, '.' ) ) { 868 $parts = explode( '.', $basename );869 $ext = array_pop( $parts );973 $parts = explode( '.', $basename ); 974 $ext = array_pop( $parts ); 870 975 $name_only = implode( '.', $parts ); 871 976 } … … 875 980 $target = ''; 876 981 while ( true ) { 877 $suffix = ( $counter > 1 ) ? '-' . $counter : '';982 $suffix = ( $counter > 1 ) ? '-' . $counter : ''; 878 983 $target_name = $base_candidate . $suffix . ( $is_dir ? '' : ( ( '' !== $ext ) ? '.' . $ext : '' ) ); 879 984 $target = $dirname . \DIRECTORY_SEPARATOR . $target_name; … … 881 986 break; 882 987 } 883 $counter++;988 ++$counter; 884 989 } 885 990 … … 887 992 if ( $is_dir ) { 888 993 self::recursive_copy( $real, $target ); 889 } else {890 if ( ! @copy( $real, $target ) ) {// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged994 } elseif ( ! @copy( $real, $target ) ) { 995 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged 891 996 \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 ); 896 1005 } 897 1006 … … 902 1011 * @param string $dst Destination dir. 903 1012 * 904 * @return void1013 * @return bool True on success, false on failure. 905 1014 * 906 1015 * @since 4.1.1 … … 908 1017 private static function recursive_copy( $src, $dst ) { 909 1018 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 } 913 1024 $dir_handle = opendir( $src ); 914 1025 if ( false === $dir_handle ) { 915 return; 916 } 1026 return false; 1027 } 1028 $success = true; 917 1029 while ( false !== ( $item = readdir( $dir_handle ) ) ) { 918 1030 if ( '.' === $item || '..' === $item ) { … … 922 1034 $to = $dst . \DIRECTORY_SEPARATOR . $item; 923 1035 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; 927 1042 } 928 1043 } 929 1044 closedir( $dir_handle ); 1045 return $success; 930 1046 } 931 1047 } -
0-day-analytics/tags/4.4.1/css/block-editor.css
r3423226 r3437565 32 32 display: inline-block; 33 33 line-height: 1; 34 vertical-align: bottom; 34 35 } 35 36 -
0-day-analytics/tags/4.4.1/css/wfe.css
r3423226 r3437565 172 172 background: #1e3e59; 173 173 } 174 .wfe-tree {175 border-left: 1px solid #8d7a7a73;176 border-bottom: 1px solid #8d7a7a73;177 border-top: 1px solid #8d7a7a73;178 }179 174 .wfe-tree ul ul { 180 175 list-style: none; … … 252 247 height: 80vh; 253 248 } 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 1 1 jQuery(function ($) { 2 2 let editor, currentFile = null, currentDir = AFE_Ajax.base, { __ } = wp.i18n; 3 let expandedFolders = new Set(); // Track expanded folder paths 3 4 4 5 // CodeMirror init … … 19 20 editor = wp.codeEditor.initialize($('#wfe-editor'), settings).codemirror; 20 21 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 21 104 // Load directory 22 function loadDir(dir, container ) {105 function loadDir(dir, container, callback) { 23 106 $.post(AFE_Ajax.ajax_url, { action: 'advan_file_editor_list_dir', dir, _ajax_nonce: AFE_Ajax.nonce }, (res) => { 24 107 if (res.success) { … … 32 115 ul.append(li); 33 116 }); 34 container.append(ul); 35 } 117 container.append(ul); if (callback) callback(); } 36 118 }); 37 119 } … … 48 130 if (sub.length) { 49 131 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 } 51 140 } else { 52 141 li.children('.toggle').text('▼'); 53 142 loadDir(path, li); 143 expandedFolders.add(path); 54 144 } 55 145 }); … … 123 213 }, (res) => { 124 214 alert(res.success ? __('✅ Created.', '0-day-analytics') : '❌ ' + res.data); 125 $('#wfe-tree').empty(); 126 loadDir(AFE_Ajax.base, $('#wfe-tree')); 215 smartRefresh(); 127 216 }); 128 217 }); … … 138 227 }, (res) => { 139 228 alert(res.success ? __('🗑️ Deleted.', '0-day-analytics') : '❌ ' + res.data); 140 $('#wfe-tree').empty(); 141 loadDir(AFE_Ajax.base, $('#wfe-tree')); 229 smartRefresh(); 142 230 $('#wfe-filename').text(__('No file selected', '0-day-analytics')); 143 231 editor.setValue(''); … … 153 241 }, (res) => { 154 242 alert(res.success ? '♻ ' + res.data.msg : '❌ ' + res.data); 155 $('#wfe-tree').empty(); 156 loadDir(AFE_Ajax.base, $('#wfe-tree')); 243 smartRefresh(); 157 244 }); 158 245 }); … … 385 472 $('#wfe-filename').text(res.data.new_path); 386 473 } 387 $('#wfe-tree').empty(); 388 loadDir(AFE_Ajax.base, $('#wfe-tree')); 474 smartRefresh(); 389 475 } 390 476 }); … … 411 497 $('#wfe-filename').text(__('No file selected','0-day-analytics')); 412 498 } 413 $('#wfe-tree').empty(); 414 loadDir(AFE_Ajax.base, $('#wfe-tree')); 499 smartRefresh(); 415 500 } 416 501 }); … … 423 508 alert(res.success ? '🧬 '+__('Duplicated','0-day-analytics')+': '+res.data.new_name : '❌ '+res.data); 424 509 if(res.success){ 425 $('#wfe-tree').empty(); 426 loadDir(AFE_Ajax.base, $('#wfe-tree')); 510 smartRefresh(); 427 511 } 428 512 }); … … 452 536 $('#wfe-tree').on('contextmenu', '.wfe-item.file', function(e){ 453 537 e.preventDefault(); 538 e.stopPropagation(); 454 539 const path = $(this).data('path'); 455 540 showContextMenu(e, path); 456 541 }); 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 457 634 // Global listeners now added dynamically per open; fallback in case menu injected elsewhere 458 635 // (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 } 459 770 }); -
0-day-analytics/tags/4.4.1/readme.txt
r3423226 r3437565 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 4.4. 07 Stable tag: 4.4.1 8 8 License: GPLv3 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-3.0.txt … … 92 92 == Changelog == 93 93 94 = 4.4.1 = 95 * Small bug fixes and File Manager improvements. 96 94 97 = 4.4.0 = 95 98 * Now the Table module has the option to insert records. -
0-day-analytics/trunk/advanced-analytics.php
r3423226 r3437565 11 11 * Plugin Name: 0 Day Analytics 12 12 * Description: Take full control of error log, crons, transients, plugins, requests, mails and DB tables. 13 * Version: 4.4. 013 * Version: 4.4.1 14 14 * Author: Stoil Dobrev 15 15 * Author URI: https://github.com/sdobreff/ … … 38 38 // Constants. 39 39 if ( ! defined( 'ADVAN_VERSION' ) ) { 40 define( 'ADVAN_VERSION', '4.4. 0' );40 define( 'ADVAN_VERSION', '4.4.1' ); 41 41 define( 'ADVAN_TEXTDOMAIN', '0-day-analytics' ); 42 42 define( 'ADVAN_NAME', '0 Day Analytics' ); -
0-day-analytics/trunk/classes/vendor/controllers/class-requests-log.php
r3393178 r3437565 123 123 124 124 // Prepare the log entry. 125 $trace_array = \json_decode( self:: $trace, true );125 $trace_array = \json_decode( self::get_trace(), true ); 126 126 $log_entry = array( 127 127 'url' => $url, … … 337 337 'requests' => self::$requests, 338 338 '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'] ) : '', 340 340 ); 341 341 -
0-day-analytics/trunk/classes/vendor/helpers/class-ajax-helper.php
r3413453 r3437565 163 163 \add_action( 'wp_ajax_advan_file_editor_diff', array( File_Editor::class, 'ajax_diff' ) ); 164 164 \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' ) ); 165 166 \add_action( 'wp_ajax_advan_file_editor_delete', array( File_Editor::class, 'ajax_delete' ) ); 166 167 \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 294 294 <button id="wfe-new-file" class="button"><?php \esc_html_e( '+ File', '0-day-analytics' ); ?></button> 295 295 <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 /> 297 299 </div> 298 300 <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> 300 306 </div> 301 307 </div> … … 318 324 </div> 319 325 </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> 320 338 <?php 321 339 } … … 437 455 } 438 456 @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 ); 439 507 } 440 508 … … 497 565 $display = $file; 498 566 if ( 'file' === $type ) { 499 $size = @filesize( $path );567 $size = @filesize( $path ); 500 568 if ( false !== $size ) { 501 569 $display .= ' (' . \size_format( (int) $size ) . ')'; … … 587 655 } 588 656 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 ) ) { 591 684 self::$last_deleted = array( 592 685 'src' => $real, … … 594 687 ); 595 688 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 ); 597 691 } 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 } 615 715 } 616 716 $src = self::$last_deleted['src']; … … 835 935 } 836 936 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 ); 838 943 } 839 944 … … 863 968 864 969 // Determine target name. 865 $ext = '';970 $ext = ''; 866 971 $name_only = $basename; 867 972 if ( ! $is_dir && false !== strpos( $basename, '.' ) ) { 868 $parts = explode( '.', $basename );869 $ext = array_pop( $parts );973 $parts = explode( '.', $basename ); 974 $ext = array_pop( $parts ); 870 975 $name_only = implode( '.', $parts ); 871 976 } … … 875 980 $target = ''; 876 981 while ( true ) { 877 $suffix = ( $counter > 1 ) ? '-' . $counter : '';982 $suffix = ( $counter > 1 ) ? '-' . $counter : ''; 878 983 $target_name = $base_candidate . $suffix . ( $is_dir ? '' : ( ( '' !== $ext ) ? '.' . $ext : '' ) ); 879 984 $target = $dirname . \DIRECTORY_SEPARATOR . $target_name; … … 881 986 break; 882 987 } 883 $counter++;988 ++$counter; 884 989 } 885 990 … … 887 992 if ( $is_dir ) { 888 993 self::recursive_copy( $real, $target ); 889 } else {890 if ( ! @copy( $real, $target ) ) {// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged994 } elseif ( ! @copy( $real, $target ) ) { 995 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged 891 996 \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 ); 896 1005 } 897 1006 … … 902 1011 * @param string $dst Destination dir. 903 1012 * 904 * @return void1013 * @return bool True on success, false on failure. 905 1014 * 906 1015 * @since 4.1.1 … … 908 1017 private static function recursive_copy( $src, $dst ) { 909 1018 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 } 913 1024 $dir_handle = opendir( $src ); 914 1025 if ( false === $dir_handle ) { 915 return; 916 } 1026 return false; 1027 } 1028 $success = true; 917 1029 while ( false !== ( $item = readdir( $dir_handle ) ) ) { 918 1030 if ( '.' === $item || '..' === $item ) { … … 922 1034 $to = $dst . \DIRECTORY_SEPARATOR . $item; 923 1035 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; 927 1042 } 928 1043 } 929 1044 closedir( $dir_handle ); 1045 return $success; 930 1046 } 931 1047 } -
0-day-analytics/trunk/css/block-editor.css
r3423226 r3437565 32 32 display: inline-block; 33 33 line-height: 1; 34 vertical-align: bottom; 34 35 } 35 36 -
0-day-analytics/trunk/css/wfe.css
r3423226 r3437565 172 172 background: #1e3e59; 173 173 } 174 .wfe-tree {175 border-left: 1px solid #8d7a7a73;176 border-bottom: 1px solid #8d7a7a73;177 border-top: 1px solid #8d7a7a73;178 }179 174 .wfe-tree ul ul { 180 175 list-style: none; … … 252 247 height: 80vh; 253 248 } 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 1 1 jQuery(function ($) { 2 2 let editor, currentFile = null, currentDir = AFE_Ajax.base, { __ } = wp.i18n; 3 let expandedFolders = new Set(); // Track expanded folder paths 3 4 4 5 // CodeMirror init … … 19 20 editor = wp.codeEditor.initialize($('#wfe-editor'), settings).codemirror; 20 21 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 21 104 // Load directory 22 function loadDir(dir, container ) {105 function loadDir(dir, container, callback) { 23 106 $.post(AFE_Ajax.ajax_url, { action: 'advan_file_editor_list_dir', dir, _ajax_nonce: AFE_Ajax.nonce }, (res) => { 24 107 if (res.success) { … … 32 115 ul.append(li); 33 116 }); 34 container.append(ul); 35 } 117 container.append(ul); if (callback) callback(); } 36 118 }); 37 119 } … … 48 130 if (sub.length) { 49 131 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 } 51 140 } else { 52 141 li.children('.toggle').text('▼'); 53 142 loadDir(path, li); 143 expandedFolders.add(path); 54 144 } 55 145 }); … … 123 213 }, (res) => { 124 214 alert(res.success ? __('✅ Created.', '0-day-analytics') : '❌ ' + res.data); 125 $('#wfe-tree').empty(); 126 loadDir(AFE_Ajax.base, $('#wfe-tree')); 215 smartRefresh(); 127 216 }); 128 217 }); … … 138 227 }, (res) => { 139 228 alert(res.success ? __('🗑️ Deleted.', '0-day-analytics') : '❌ ' + res.data); 140 $('#wfe-tree').empty(); 141 loadDir(AFE_Ajax.base, $('#wfe-tree')); 229 smartRefresh(); 142 230 $('#wfe-filename').text(__('No file selected', '0-day-analytics')); 143 231 editor.setValue(''); … … 153 241 }, (res) => { 154 242 alert(res.success ? '♻ ' + res.data.msg : '❌ ' + res.data); 155 $('#wfe-tree').empty(); 156 loadDir(AFE_Ajax.base, $('#wfe-tree')); 243 smartRefresh(); 157 244 }); 158 245 }); … … 385 472 $('#wfe-filename').text(res.data.new_path); 386 473 } 387 $('#wfe-tree').empty(); 388 loadDir(AFE_Ajax.base, $('#wfe-tree')); 474 smartRefresh(); 389 475 } 390 476 }); … … 411 497 $('#wfe-filename').text(__('No file selected','0-day-analytics')); 412 498 } 413 $('#wfe-tree').empty(); 414 loadDir(AFE_Ajax.base, $('#wfe-tree')); 499 smartRefresh(); 415 500 } 416 501 }); … … 423 508 alert(res.success ? '🧬 '+__('Duplicated','0-day-analytics')+': '+res.data.new_name : '❌ '+res.data); 424 509 if(res.success){ 425 $('#wfe-tree').empty(); 426 loadDir(AFE_Ajax.base, $('#wfe-tree')); 510 smartRefresh(); 427 511 } 428 512 }); … … 452 536 $('#wfe-tree').on('contextmenu', '.wfe-item.file', function(e){ 453 537 e.preventDefault(); 538 e.stopPropagation(); 454 539 const path = $(this).data('path'); 455 540 showContextMenu(e, path); 456 541 }); 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 457 634 // Global listeners now added dynamically per open; fallback in case menu injected elsewhere 458 635 // (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 } 459 770 }); -
0-day-analytics/trunk/readme.txt
r3423226 r3437565 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 4.4. 07 Stable tag: 4.4.1 8 8 License: GPLv3 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-3.0.txt … … 92 92 == Changelog == 93 93 94 = 4.4.1 = 95 * Small bug fixes and File Manager improvements. 96 94 97 = 4.4.0 = 95 98 * Now the Table module has the option to insert records.
Note: See TracChangeset
for help on using the changeset viewer.