initial
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
(function ($) {
|
||||
$('.file-manager-advanced-select2.fma-code-editor-theme').select2({
|
||||
templateResult: function (a, b) {
|
||||
return disable_pro_themes(a, b, 'https://advancedfilemanager.com/pricing/?utm_source=plugin&utm_medium=code_editor_pro_theme&utm_campaign=plugin');
|
||||
}
|
||||
});
|
||||
|
||||
$('.file-manager-advanced-select2.fma-theme').select2({
|
||||
templateResult: function (a, b) {
|
||||
return disable_pro_themes(a, b, 'https://advancedfilemanager.com/pricing/?utm_source=plugin&utm_medium=file_manager_pro_theme&utm_campaign=plugin');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.on-click-show-popup', function () {
|
||||
file_manager_advanced_popup($(this).attr('fma-href'));
|
||||
});
|
||||
|
||||
$('#fma__hide-banner').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
$('.afm__right-side').hide();
|
||||
$('.afm__left-side').css({ 'width': '100%', 'max-width': '100%' });
|
||||
|
||||
rest_api_post('hide-banner')
|
||||
.catch(error => console.error('Error:', error));
|
||||
});
|
||||
|
||||
$('#fma__minimize-maximize').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
$(this).children().toggleClass('fma__minimized');
|
||||
|
||||
var action = 'maximize';
|
||||
if ('true' === $(this).attr('fma-maximized')) {
|
||||
action = 'minimize';
|
||||
}
|
||||
|
||||
minimize_maximize(this, action);
|
||||
|
||||
rest_api_post(
|
||||
'minimize-maximize-banner',
|
||||
{ action: action }
|
||||
).catch(error => console.error('Error:', error));
|
||||
});
|
||||
|
||||
$('.dropbox__wrap, .file-logs__wrap, .fma__wrap').on('click', function () {
|
||||
|
||||
var redirect_url = $(this).attr('afmp-href');
|
||||
if (!redirect_url) {
|
||||
redirect_url = '';
|
||||
}
|
||||
|
||||
file_manager_advanced_popup(redirect_url, '', '');
|
||||
});
|
||||
|
||||
$('.googledrive__wrap').on('click', function () {
|
||||
file_manager_advanced_popup(
|
||||
'https://advancedfilemanager.com/pricing/?utm_source=plugin&utm_medium=google_drive_banner&utm_campaign=plugin',
|
||||
'',
|
||||
''
|
||||
);
|
||||
});
|
||||
|
||||
$('.dropbox__wrap, .onedrive__wrap').on('click', function () {
|
||||
file_manager_advanced_popup('', '', '');
|
||||
});
|
||||
|
||||
// Use event delegation for blocks wrap (since it's added dynamically)
|
||||
// Same behavior as OneDrive/Dropbox - empty string for default popup
|
||||
$(document).on('click', '.fma__blocks__wrap', function () {
|
||||
var redirect_url = $(this).attr('afmp-href');
|
||||
if (!redirect_url) {
|
||||
redirect_url = 'https://advancedfilemanager.com/pricing/?utm_source=plugin&utm_medium=blocks_banner&utm_campaign=plugin';
|
||||
}
|
||||
file_manager_advanced_popup(redirect_url, '', '');
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.aws__wrap').on('click', function () {
|
||||
file_manager_advanced_popup('https://advancedfilemanager.com/pricing/?utm_source=plugin&utm_medium=aws_banner&utm_campaign=plugin', '', '');
|
||||
});
|
||||
|
||||
$('.github__wrap').on('click', function () {
|
||||
file_manager_advanced_popup(
|
||||
'https://advancedfilemanager.com/pricing/?utm_source=plugin&utm_medium=github_banner&utm_campaign=plugin',
|
||||
'',
|
||||
''
|
||||
);
|
||||
});
|
||||
|
||||
$('.googlecloud__wrap').on('click', function () {
|
||||
file_manager_advanced_popup(
|
||||
'https://advancedfilemanager.com/pricing/?utm_source=plugin&utm_medium=googlecloud_banner&utm_campaign=plugin',
|
||||
'',
|
||||
''
|
||||
);
|
||||
});
|
||||
|
||||
function file_manager_advanced_popup(redirect_url = '', message = '', button_title = '') {
|
||||
if (!redirect_url.length) {
|
||||
redirect_url = 'https://advancedfilemanager.com/pricing/?utm_source=plugin&utm_medium=dropbox_banner&utm_campaign=plugin';
|
||||
}
|
||||
|
||||
if (!message.length) {
|
||||
message = 'Get advanced features with Advanced File Manager Pro!';
|
||||
}
|
||||
|
||||
if (!button_title.length) {
|
||||
button_title = 'Get Pro Now';
|
||||
}
|
||||
var element = $('#fma__pro_popup');
|
||||
if (element.length) {
|
||||
element.show();
|
||||
} else {
|
||||
let fma__pro_popup = `<div class="fma__pro-popup" id="fma__pro_popup">
|
||||
<div class="fma__pro-popup-wrapper">
|
||||
|
||||
<div class="fma__pro-close-button">
|
||||
<a id="close-popup-btn" href="#">
|
||||
<img src="${afmAdmin.assetsURL}images/close-popup.svg" alt="">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="fma__pro-popup-content">
|
||||
|
||||
<div>
|
||||
<img src="${afmAdmin.assetsURL}images/fma-logo.svg" alt="">
|
||||
</div>
|
||||
|
||||
<div class="afmp__pro-popup-desc">
|
||||
<p>
|
||||
${message}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="fma__pro-popup-cta">
|
||||
<a target="_blank" href="${redirect_url}">
|
||||
<img style="width: 20px;margin-bottom: -3px;" src="${afmAdmin.assetsURL}images/crown.svg" alt="">
|
||||
${button_title}
|
||||
<img style="width: 10px;margin-bottom: -2px;" src="${afmAdmin.assetsURL}images/right-arrow.svg" alt="">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>`;
|
||||
$('body').append(fma__pro_popup);
|
||||
}
|
||||
|
||||
$(document).on('click', '#close-popup-btn', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
$('#fma__pro_popup').remove();
|
||||
});
|
||||
}
|
||||
|
||||
function minimize_maximize(element, action) {
|
||||
switch (action) {
|
||||
case 'maximize':
|
||||
$('#remove-on-minimize, .fma__footer').show();
|
||||
$(element).attr('fma-maximized', 'true');
|
||||
break;
|
||||
case 'minimize':
|
||||
$('#remove-on-minimize, .fma__footer').hide();
|
||||
$(element).attr('fma-maximized', 'false');
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var rest_api_post = async function (url, params = {}) {
|
||||
url = afmAdmin.jsonURL + 'file-manager-advanced/v1/' + url;
|
||||
|
||||
return await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
}).then(response => response.json());
|
||||
};
|
||||
|
||||
var disable_pro_themes = function (a, b, redirect_url) {
|
||||
if (a.id === '') {
|
||||
return a.text;
|
||||
}
|
||||
|
||||
if (!a.disabled) {
|
||||
return a.text;
|
||||
}
|
||||
|
||||
return $(`<span fma-href="${redirect_url}" class="fma__clearfix on-click-show-popup">
|
||||
<span class="fma__left">
|
||||
${a.text}
|
||||
</span>
|
||||
<span style="font-size: 15px;" class="fma__right">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none">
|
||||
<path d="M11.341 11.0011H2.6642C2.57543 11.0011 2.49029 11.0364 2.42752 11.0992C2.36475 11.1619 2.32948 11.2471 2.32948 11.3358V12.0001C2.32948 12.0889 2.36475 12.174 2.42752 12.2368C2.49029 12.2996 2.57543 12.3348 2.6642 12.3348H11.341C11.4289 12.3348 11.5132 12.3003 11.5759 12.2386C11.6385 12.177 11.6743 12.0931 11.6757 12.0053V11.3358C11.6744 11.2475 11.6387 11.1631 11.5762 11.1006C11.5137 11.0381 11.4293 11.0024 11.341 11.0011ZM12.6747 4.30687C12.4015 4.30687 12.1396 4.41537 11.9464 4.60851C11.7533 4.80165 11.6448 5.06361 11.6448 5.33675C11.6469 5.479 11.6785 5.61926 11.7375 5.7487L10.2287 6.655C10.1524 6.70023 10.068 6.72985 9.98016 6.74216C9.89236 6.75447 9.80299 6.74921 9.71723 6.72669C9.63148 6.70418 9.55105 6.66485 9.48062 6.611C9.41019 6.55714 9.35116 6.48984 9.30696 6.41298L7.60765 3.43661C7.77678 3.3047 7.90051 3.12327 7.96156 2.91764C8.0226 2.71202 8.01793 2.49247 7.94818 2.28963C7.87844 2.08679 7.7471 1.91079 7.5725 1.7862C7.3979 1.66161 7.18876 1.59464 6.97427 1.59464C6.75977 1.59464 6.55063 1.66161 6.37603 1.7862C6.20144 1.91079 6.0701 2.08679 6.00035 2.28963C5.9306 2.49247 5.92593 2.71202 5.98698 2.91764C6.04803 3.12327 6.17175 3.3047 6.34089 3.43661L4.64158 6.41298C4.59737 6.48984 4.53834 6.55714 4.46791 6.611C4.39748 6.66485 4.31705 6.70418 4.2313 6.72669C4.14555 6.74921 4.05618 6.75447 3.96837 6.74216C3.88057 6.72985 3.79609 6.70023 3.71983 6.655L2.21105 5.7487C2.27125 5.61966 2.30287 5.47914 2.30374 5.33675C2.30464 5.13643 2.24559 4.94041 2.13421 4.77391C2.02282 4.60741 1.86417 4.47803 1.67867 4.4024C1.49316 4.32678 1.28928 4.30837 1.09323 4.34955C0.897177 4.39072 0.717924 4.48959 0.578513 4.63345C0.439103 4.77731 0.345909 4.95958 0.310913 5.15683C0.275917 5.35408 0.300718 5.55728 0.382129 5.74032C0.46354 5.92336 0.597838 6.07786 0.767757 6.18397C0.937676 6.29007 1.13545 6.34293 1.33565 6.33574C1.39044 6.34108 1.44563 6.34108 1.50043 6.33574L3.00921 10.3574H11.0063L12.5099 6.33574H12.6747C12.9279 6.31757 13.1648 6.20416 13.3378 6.01833C13.5108 5.8325 13.607 5.58805 13.607 5.33418C13.607 5.0803 13.5108 4.83585 13.3378 4.65002C13.1648 4.46419 12.9279 4.35078 12.6747 4.33261V4.30687Z" fill="#FFCA38"/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>` );
|
||||
}
|
||||
|
||||
// Fix for notices appearing inside the banner - run immediately and on load
|
||||
function fixAdminNotices() {
|
||||
var $notices = $('.afm__right-side .notice, .afm__right-side .updated, .afm__right-side .error, .afm__right-side .is-dismissible');
|
||||
if ($notices.length > 0) {
|
||||
$notices.insertBefore('.afm__left-side');
|
||||
$notices.css('margin-left', '0').css('margin-right', '0').css('margin-bottom', '20px').show();
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
fixAdminNotices();
|
||||
// Check again after a short delay in case of dynamic insertions
|
||||
setTimeout(fixAdminNotices, 500);
|
||||
setTimeout(fixAdminNotices, 2000);
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,652 @@
|
||||
jQuery(document).ready(function () {
|
||||
// Check if debug feature is enabled
|
||||
var debugEnabled = afm_object && afm_object.debug_enabled === '1';
|
||||
|
||||
// Global variables for error tracking
|
||||
var hasErrors = false;
|
||||
var currentEditor = null;
|
||||
var currentErrors = [];
|
||||
var lastButtonState = null;
|
||||
var tooltipTimeout = null;
|
||||
var lastTooltipLine = null;
|
||||
var isSaveButtonClicked = false;
|
||||
|
||||
// CSS styles for error highlighting
|
||||
var errorStyles = `
|
||||
<style>
|
||||
.fma-error-line {
|
||||
background-color: #fed7d7 !important;
|
||||
border-left: 3px solid #c53030 !important;
|
||||
}
|
||||
.fma-error-underline {
|
||||
text-decoration: underline wavy #c53030 !important;
|
||||
text-decoration-thickness: 2px !important;
|
||||
}
|
||||
.fma-error-marker {
|
||||
color: #c53030 !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: bold !important;
|
||||
text-align: center !important;
|
||||
line-height: 1 !important;
|
||||
}
|
||||
.fma-error-gutter {
|
||||
background-color: #fed7d7 !important;
|
||||
border-right: 2px solid #c53030 !important;
|
||||
}
|
||||
.fma-save-close-disabled {
|
||||
opacity: 0.5 !important;
|
||||
cursor: not-allowed !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
.fma-error-tooltip {
|
||||
position: absolute;
|
||||
background: #2d3748;
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-family: 'Courier New', monospace;
|
||||
z-index: 10000;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
max-width: 300px;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.fma-error-tooltip::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-bottom: 5px solid #2d3748;
|
||||
}
|
||||
.fma-error-tooltip .error-title {
|
||||
font-weight: bold;
|
||||
color: #feb2b2;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.fma-error-tooltip .error-message {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
.fma-error-tooltip .error-line {
|
||||
color: #a0aec0;
|
||||
font-size: 11px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
jQuery('head').append(errorStyles);
|
||||
|
||||
if (1 == afm_object.hide_path) {
|
||||
var custom_css = `<style id="hide-path" type="text/css">.elfinder-info-path { display:none; } .elfinder-info-tb tr:nth-child(2) { display:none; }</style>`;
|
||||
jQuery("head").append(custom_css);
|
||||
}
|
||||
|
||||
var hide_preferences_css = `<style id="hide-preferences" type="text/css">
|
||||
.elfinder-contextmenu-item:has( .elfinder-button-icon.elfinder-button-icon-preference.elfinder-contextmenu-icon ) {display: none;}
|
||||
.elfinder-button-icon-replace {
|
||||
background-image: url(${afm_object.plugin_url}application/assets/images/replace.png);
|
||||
background-size: 16px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
.elfinder-contextmenu-icon-replace {
|
||||
background-image: url(${afm_object.plugin_url}application/assets/images/replace.png);
|
||||
background-size: 16px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
</style>`;
|
||||
jQuery('head').append(hide_preferences_css);
|
||||
|
||||
var fmakey = afm_object.nonce;
|
||||
var fma_locale = afm_object.locale;
|
||||
var fma_cm_theme = afm_object.cm_theme;
|
||||
|
||||
// PHP Debug Analysis function
|
||||
function analyzePHPDebug(code, filename, callback) {
|
||||
jQuery.ajax({
|
||||
url: afm_object.ajaxurl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'fma_debug_php',
|
||||
nonce: fmakey,
|
||||
php_code: code,
|
||||
filename: filename
|
||||
},
|
||||
success: function (response) {
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback(response);
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback({
|
||||
valid: false,
|
||||
debug_info: {},
|
||||
message: 'Failed to analyze PHP code'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Highlight error lines in CodeMirror
|
||||
function highlightErrorLines(editor, errors) {
|
||||
if (!editor || !errors || errors.length === 0) {
|
||||
clearErrorHighlights(editor);
|
||||
return;
|
||||
}
|
||||
|
||||
currentErrors = errors;
|
||||
|
||||
for (var i = 0; i < editor.lineCount(); i++) {
|
||||
editor.removeLineClass(i, 'background', 'fma-error-line');
|
||||
editor.removeLineClass(i, 'text', 'fma-error-underline');
|
||||
editor.setGutterMarker(i, 'fma-error-gutter', null);
|
||||
}
|
||||
|
||||
errors.forEach(function (error) {
|
||||
if (error.line && error.line > 0) {
|
||||
var lineNumber = error.line - 1;
|
||||
editor.addLineClass(lineNumber, 'background', 'fma-error-line');
|
||||
editor.addLineClass(lineNumber, 'text', 'fma-error-underline');
|
||||
|
||||
var marker = document.createElement('div');
|
||||
marker.className = 'fma-error-marker';
|
||||
marker.innerHTML = '⚠️';
|
||||
marker.title = error.message;
|
||||
editor.setGutterMarker(lineNumber, 'fma-error-gutter', marker);
|
||||
|
||||
// Markers have titles which browser handles, but for the line text we use tooltips
|
||||
}
|
||||
});
|
||||
|
||||
hasErrors = true;
|
||||
updateSaveCloseButton();
|
||||
}
|
||||
|
||||
// Clear error highlights
|
||||
function clearErrorHighlights(editor) {
|
||||
if (!editor) return;
|
||||
|
||||
for (var i = 0; i < editor.lineCount(); i++) {
|
||||
editor.removeLineClass(i, 'background', 'fma-error-line');
|
||||
editor.removeLineClass(i, 'text', 'fma-error-underline');
|
||||
editor.setGutterMarker(i, 'fma-error-gutter', null);
|
||||
}
|
||||
|
||||
hideErrorTooltip();
|
||||
currentErrors = [];
|
||||
hasErrors = false;
|
||||
updateSaveCloseButton();
|
||||
}
|
||||
|
||||
// Show error tooltip on hover
|
||||
function showErrorTooltip(event, error) {
|
||||
if (tooltipTimeout) clearTimeout(tooltipTimeout);
|
||||
|
||||
if (lastTooltipLine === error.line) {
|
||||
// Just update position if moving within same line
|
||||
var tooltip = jQuery('.fma-error-tooltip');
|
||||
if (tooltip.length) {
|
||||
var x = event.pageX + 10;
|
||||
var y = event.pageY - 15;
|
||||
tooltip.css({ left: x + 'px', top: (y - tooltip.outerHeight()) + 'px' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
tooltipTimeout = setTimeout(function () {
|
||||
jQuery('.fma-error-tooltip').remove();
|
||||
var tooltipHtml = `
|
||||
<div class="fma-error-tooltip">
|
||||
<div class="error-title">⚠️ PHP Error</div>
|
||||
<div class="error-message">${error.message}</div>
|
||||
<div class="error-line">Line ${error.line}</div>
|
||||
</div>
|
||||
`;
|
||||
var tooltip = jQuery(tooltipHtml).appendTo('body');
|
||||
var x = event.pageX + 10;
|
||||
var y = event.pageY - 15;
|
||||
tooltip.css({ left: x + 'px', top: (y - tooltip.outerHeight()) + 'px' });
|
||||
lastTooltipLine = error.line;
|
||||
}, 50); // Faster response
|
||||
}
|
||||
|
||||
// Hide error tooltip
|
||||
function hideErrorTooltip() {
|
||||
if (tooltipTimeout) clearTimeout(tooltipTimeout);
|
||||
jQuery('.fma-error-tooltip').remove();
|
||||
lastTooltipLine = null;
|
||||
}
|
||||
|
||||
// Get error for specific line number
|
||||
function getErrorForLine(lineNumber) {
|
||||
for (var i = 0; i < currentErrors.length; i++) {
|
||||
if (currentErrors[i].line === lineNumber + 1) return currentErrors[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update Save & Close button state
|
||||
function updateSaveCloseButton() {
|
||||
var currentState = hasErrors ? 'disabled' : 'enabled';
|
||||
if (lastButtonState === currentState) return;
|
||||
lastButtonState = currentState;
|
||||
|
||||
var selectors = [
|
||||
'.elfinder-button-save-close', '.elfinder-button-save',
|
||||
'[title*="Save"]', '[title*="save"]',
|
||||
'button[title*="Save"]', 'button[title*="save"]',
|
||||
'button:contains("Save")', 'button:contains("save")'
|
||||
];
|
||||
var saveCloseBtn = jQuery(selectors.join(', ')).filter(':visible');
|
||||
|
||||
if (saveCloseBtn.length > 0) {
|
||||
if (hasErrors) {
|
||||
saveCloseBtn.addClass('fma-save-close-disabled').attr('disabled', 'disabled');
|
||||
saveCloseBtn.css({ opacity: '0.5', cursor: 'not-allowed' });
|
||||
saveCloseBtn.off('click.fma-disable').on('click.fma-disable', function (e) {
|
||||
e.preventDefault(); return false;
|
||||
});
|
||||
} else {
|
||||
saveCloseBtn.removeClass('fma-save-close-disabled').removeAttr('disabled');
|
||||
saveCloseBtn.css({ opacity: '1', cursor: 'pointer' });
|
||||
saveCloseBtn.off('click.fma-disable');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Periodic check for Save button
|
||||
setInterval(function () {
|
||||
if (currentEditor && hasErrors) updateSaveCloseButton();
|
||||
}, 5000);
|
||||
|
||||
// Show error popup on save
|
||||
function showErrorSavePopup(errors, callback) {
|
||||
var errorList = errors.map(function (error) {
|
||||
return `Line ${error.line}: ${error.message}`;
|
||||
}).join('<br>');
|
||||
|
||||
var popupHtml = `
|
||||
<div class="fma-modal-overlay" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 10000; display: flex; justify-content: center; align-items: center;">
|
||||
<div class="fma-error-popup" style="background: white; border-radius: 8px; padding: 20px; max-width: 500px; width: 90%; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
|
||||
<h3 style="color: #c53030;">PHP Syntax Errors Found</h3>
|
||||
<div style="background: #fed7d7; padding: 10px; border-radius: 4px; font-family: monospace;">${errorList}</div>
|
||||
<div style="margin-top: 15px; display: flex; gap: 10px; justify-content: flex-end;">
|
||||
<button class="fma-error-okay" style="padding: 8px 15px;">Okay</button>
|
||||
<button class="fma-error-save-anyway" style="padding: 8px 15px; background: #c53030; color: white; border: none;">Save Anyway</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
jQuery('.fma-modal-overlay').remove();
|
||||
var popup = jQuery(popupHtml).appendTo('body');
|
||||
|
||||
popup.find('.fma-error-okay').on('click', function () {
|
||||
popup.remove();
|
||||
if (callback) callback(false);
|
||||
});
|
||||
|
||||
popup.find('.fma-error-save-anyway').on('click', function () {
|
||||
popup.remove();
|
||||
if (callback) callback(true);
|
||||
});
|
||||
}
|
||||
|
||||
// Success Modal
|
||||
function showSuccessModal(message) {
|
||||
if (jQuery('.fma-modal-overlay').length > 0) return;
|
||||
var modalHtml = `
|
||||
<div class="fma-modal-overlay" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 10001; display: flex; align-items: center; justify-content: center;">
|
||||
<div style="background: white; padding: 30px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #46b450; font-size: 40px;">✓</div>
|
||||
<p>${message}</p>
|
||||
<button class="fma-modal-close">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
var modal = jQuery(modalHtml).appendTo('body');
|
||||
modal.find('.fma-modal-close').on('click', function () { modal.remove(); });
|
||||
setTimeout(function () { modal.remove(); }, 3000);
|
||||
}
|
||||
|
||||
// Initialize elFinder
|
||||
var elfinder_object = jQuery('#file_manager_advanced').elfinder({
|
||||
cssAutoLoad: false,
|
||||
url: afm_object.ajaxurl,
|
||||
customData: {
|
||||
action: 'fma_load_fma_ui',
|
||||
_fmakey: fmakey,
|
||||
},
|
||||
defaultView: 'list',
|
||||
height: 500,
|
||||
lang: fma_locale,
|
||||
ui: afm_object.ui,
|
||||
commandsOptions: {
|
||||
edit: {
|
||||
mimes: [],
|
||||
editors: [{
|
||||
mimes: ['text/plain', 'text/html', 'text/javascript', 'text/css', 'text/x-php', 'application/x-php'],
|
||||
info: { name: 'Code Editor' },
|
||||
load: function (textarea) {
|
||||
var mimeType = this.file.mime;
|
||||
var filename = this.file.name;
|
||||
var self = this;
|
||||
|
||||
var editor = CodeMirror.fromTextArea(textarea, {
|
||||
mode: mimeType,
|
||||
indentUnit: 4,
|
||||
lineNumbers: true,
|
||||
theme: fma_cm_theme,
|
||||
gutters: ["CodeMirror-lint-markers", "CodeMirror-linenumbers"]
|
||||
});
|
||||
|
||||
editor.fma_file_info = { filename: filename, mime: mimeType, hash: self.file.hash };
|
||||
|
||||
if (debugEnabled && filename.toLowerCase().endsWith('.php')) {
|
||||
editor.on('change', function () {
|
||||
var code = editor.getValue();
|
||||
analyzePHPDebug(code, filename, function (result) {
|
||||
if (result.valid) {
|
||||
clearErrorHighlights(editor);
|
||||
} else {
|
||||
highlightErrorLines(editor, result.errors);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Hover tooltip: CodeMirror coordsChar() "page" mode needs document coords (pageX/pageY)
|
||||
var wrapper = editor.getWrapperElement();
|
||||
jQuery(wrapper).on('mousemove.fma-debug', function (e) {
|
||||
if (!hasErrors || currentErrors.length === 0) return;
|
||||
|
||||
var docLeft = e.pageX != null ? e.pageX : (e.clientX + (window.scrollX || document.documentElement.scrollLeft || 0));
|
||||
var docTop = e.pageY != null ? e.pageY : (e.clientY + (window.scrollY || document.documentElement.scrollTop || 0));
|
||||
var coords = editor.coordsChar({ left: docLeft, top: docTop }, 'page');
|
||||
if (coords.outside) return;
|
||||
var error = getErrorForLine(coords.line);
|
||||
|
||||
if (error) {
|
||||
showErrorTooltip(e, error);
|
||||
} else {
|
||||
hideErrorTooltip();
|
||||
}
|
||||
});
|
||||
|
||||
jQuery(wrapper).on('mouseleave.fma-debug', function () {
|
||||
hideErrorTooltip();
|
||||
});
|
||||
|
||||
currentEditor = editor;
|
||||
return editor;
|
||||
},
|
||||
close: function (textarea, instance) {
|
||||
if (instance) clearErrorHighlights(instance);
|
||||
currentEditor = null;
|
||||
},
|
||||
save: function (textarea, editor) {
|
||||
var code = editor.getValue();
|
||||
var filename = editor.fma_file_info ? editor.fma_file_info.filename : 'unknown.php';
|
||||
|
||||
if (filename.toLowerCase().endsWith('.php') && hasErrors) {
|
||||
analyzePHPDebug(code, filename, function (result) {
|
||||
if (!result.valid && result.errors) {
|
||||
showErrorSavePopup(result.errors, function (saveAnyway) {
|
||||
if (saveAnyway) {
|
||||
jQuery(textarea).val(code);
|
||||
if (typeof editor.save === 'function') editor.save();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
jQuery(textarea).val(code);
|
||||
return true;
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
workerBaseUrl: afm_object.plugin_url + 'application/library/js/worker/',
|
||||
});
|
||||
|
||||
// Override search command to add contentTag parameter for content search (after init)
|
||||
function applyContentSearchOverride(fm) {
|
||||
if (!fm || !fm._commands || !fm._commands.search) return;
|
||||
if (fm._commands.search._fmaContentSearchPatched) return;
|
||||
fm._commands.search._fmaContentSearchPatched = true;
|
||||
var originalSearchExec = fm._commands.search.exec;
|
||||
|
||||
// Override exec method to add contentTag for SearchTag type
|
||||
fm._commands.search.exec = function(q, target, mime, type) {
|
||||
var self = this;
|
||||
var sType = type || '';
|
||||
|
||||
// If SearchTag type, we need to send contentTag parameter
|
||||
if (sType === 'SearchTag' && q) {
|
||||
var contentTag = q; // Store the tag
|
||||
var fm = this.fm;
|
||||
var reqDef = [];
|
||||
var onlyMimes = fm.options.onlyMimes;
|
||||
var phash, targetVolids = [];
|
||||
|
||||
// Custom setType function that encodes tag in q parameter for SearchTag
|
||||
var setType = function(data) {
|
||||
if (sType && sType !== 'SearchName' && sType !== 'SearchMime') {
|
||||
data.type = sType;
|
||||
}
|
||||
if (sType === 'SearchTag') {
|
||||
data.q = '__CONTENT_SEARCH__:' + contentTag;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
var rootCnt;
|
||||
|
||||
// Process target
|
||||
if (typeof target == 'object') {
|
||||
mime = target.mime || '';
|
||||
target = target.target || '';
|
||||
}
|
||||
target = target ? target : '';
|
||||
|
||||
// Process mimes
|
||||
if (mime) {
|
||||
mime = jQuery.trim(mime).replace(',', ' ').split(' ');
|
||||
if (onlyMimes.length) {
|
||||
mime = jQuery.map(mime, function(m) {
|
||||
m = jQuery.trim(m);
|
||||
return m && (jQuery.inArray(m, onlyMimes) !== -1
|
||||
|| jQuery.grep(onlyMimes, function(om) { return m.indexOf(om) === 0 ? true : false; }).length
|
||||
) ? m : null;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
mime = [].concat(onlyMimes);
|
||||
}
|
||||
|
||||
fm.trigger('searchstart', setType({query: '', target: target, mimes: mime}));
|
||||
|
||||
if (!onlyMimes.length || mime.length) {
|
||||
if (target === '' && fm.api >= 2.1) {
|
||||
rootCnt = Object.keys(fm.roots).length;
|
||||
jQuery.each(fm.roots, function(id, hash) {
|
||||
reqDef.push(fm.request({
|
||||
data: setType({cmd: 'search', target: hash, mimes: mime}),
|
||||
notify: {type: 'search', cnt: 1, hideCnt: (rootCnt > 1 ? false : true)},
|
||||
cancel: true,
|
||||
preventDone: true
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
reqDef.push(fm.request({
|
||||
data: setType({cmd: 'search', target: target, mimes: mime}),
|
||||
notify: {type: 'search', cnt: 1, hideCnt: true},
|
||||
cancel: true,
|
||||
preventDone: true
|
||||
}));
|
||||
if (target !== '' && fm.api >= 2.1 && Object.keys(fm.leafRoots).length) {
|
||||
jQuery.each(fm.leafRoots, function(hash, roots) {
|
||||
phash = hash;
|
||||
while (phash) {
|
||||
if (target === phash) {
|
||||
jQuery.each(roots, function() {
|
||||
var f = fm.file(this);
|
||||
f && f.volumeid && targetVolids.push(f.volumeid);
|
||||
reqDef.push(fm.request({
|
||||
data: setType({cmd: 'search', target: this, mimes: mime}),
|
||||
notify: {type: 'search', cnt: 1, hideCnt: false},
|
||||
cancel: true,
|
||||
preventDone: true
|
||||
}));
|
||||
});
|
||||
}
|
||||
phash = (fm.file(phash) || {}).phash;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reqDef = [jQuery.Deferred().resolve({files: []})];
|
||||
}
|
||||
|
||||
fm.searchStatus.mixed = (reqDef.length > 1) ? targetVolids : false;
|
||||
|
||||
return jQuery.when.apply(jQuery, reqDef).done(function(data) {
|
||||
var argLen = arguments.length, i;
|
||||
data.warning && fm.error(data.warning);
|
||||
if (argLen > 1) {
|
||||
data.files = (data.files || []);
|
||||
for (i = 1; i < argLen; i++) {
|
||||
arguments[i].warning && fm.error(arguments[i].warning);
|
||||
if (arguments[i].files) {
|
||||
data.files.push.apply(data.files, arguments[i].files);
|
||||
}
|
||||
}
|
||||
}
|
||||
data.files && data.files.length && fm.cache(data.files);
|
||||
fm.lazy(function() {
|
||||
fm.trigger('search', data);
|
||||
}).then(function() {
|
||||
return fm.lazy(function() {
|
||||
fm.trigger('searchdone');
|
||||
});
|
||||
}).then(function() {
|
||||
data.sync && fm.sync();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return originalSearchExec.call(self, q, target, mime, type);
|
||||
}
|
||||
};
|
||||
}
|
||||
if (elfinder_object && elfinder_object.length) {
|
||||
var fm = elfinder_object.elfinder('instance');
|
||||
if (fm) {
|
||||
fm.one('init', function() { applyContentSearchOverride(fm); });
|
||||
applyContentSearchOverride(fm);
|
||||
}
|
||||
}
|
||||
|
||||
// When user clears search and presses Enter (or closes search), go back to folder they were in before search
|
||||
if (elfinder_object && elfinder_object.length) {
|
||||
var fm = elfinder_object.elfinder('instance');
|
||||
if (fm) {
|
||||
var cwdBeforeSearch = null;
|
||||
fm.bind('searchstart', function() {
|
||||
var cwd = fm.cwd();
|
||||
if (cwd && cwd.hash) {
|
||||
cwdBeforeSearch = cwd.hash;
|
||||
}
|
||||
});
|
||||
fm.bind('searchend', function() {
|
||||
if (cwdBeforeSearch && fm.file(cwdBeforeSearch)) {
|
||||
fm.exec('open', cwdBeforeSearch);
|
||||
}
|
||||
cwdBeforeSearch = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add Content search radio button after elFinder is initialized
|
||||
if (elfinder_object && elfinder_object.length) {
|
||||
var fm = elfinder_object.elfinder('instance');
|
||||
if (fm) {
|
||||
var addContentRadioButton = function() {
|
||||
var searchMenu = jQuery('.elfinder-button-search-menu');
|
||||
if (searchMenu.length) {
|
||||
var namespace = fm.namespace || 'elfinder-';
|
||||
var id = function(name) {
|
||||
return namespace + (fm.escape ? fm.escape(name) : name);
|
||||
};
|
||||
|
||||
var searchTypeSet = searchMenu.find('.elfinder-search-type');
|
||||
if (searchTypeSet.length && !searchTypeSet.find('#' + id('SearchTag')).length) {
|
||||
var tagRadio = jQuery('<input id="' + id('SearchTag') + '" name="serchcol" type="radio" value="SearchTag"/>');
|
||||
var tagLabel = jQuery('<label for="' + id('SearchTag') + '">Content</label>');
|
||||
|
||||
searchTypeSet.append(tagRadio).append(tagLabel);
|
||||
searchTypeSet.buttonset('refresh');
|
||||
|
||||
// Remember search type when target is "Here", so we can restore only when user had chosen Content then switched to "All"
|
||||
var lastSearchTypeWhenHere = null;
|
||||
|
||||
var updateContentState = function() {
|
||||
var isHere = jQuery('#' + id('SearchFromCwd')).prop('checked');
|
||||
var tagRadio = jQuery('#' + id('SearchTag'));
|
||||
var tagLabel = tagRadio.next('label');
|
||||
|
||||
if (tagRadio.length) {
|
||||
if (isHere) {
|
||||
tagRadio.prop('disabled', false);
|
||||
tagLabel.css({'opacity': '1', 'cursor': 'pointer'});
|
||||
// Restore Content when switching back to "Here" if it was selected before
|
||||
if (lastSearchTypeWhenHere === 'SearchTag') {
|
||||
tagRadio.prop('checked', true);
|
||||
searchTypeSet.buttonset('refresh');
|
||||
}
|
||||
} else {
|
||||
tagRadio.prop('disabled', true);
|
||||
tagLabel.css({'opacity': '0.5', 'cursor': 'not-allowed'});
|
||||
if (tagRadio.prop('checked')) {
|
||||
lastSearchTypeWhenHere = 'SearchTag'; // was Content before switching to All
|
||||
jQuery('#' + id('SearchName')).prop('checked', true).trigger('change');
|
||||
searchTypeSet.buttonset('refresh');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// When user changes search type while target is "Here", remember it
|
||||
searchMenu.off('change.contentsearch', 'input[name="serchcol"]').on('change.contentsearch', 'input[name="serchcol"]', function() {
|
||||
if (jQuery('#' + id('SearchFromCwd')).prop('checked')) {
|
||||
var val = jQuery(this).val();
|
||||
if (val) lastSearchTypeWhenHere = val;
|
||||
}
|
||||
});
|
||||
|
||||
searchMenu.off('change.contentsearch', 'input[name="serchfrom"]').on('change.contentsearch', 'input[name="serchfrom"]', updateContentState);
|
||||
updateContentState();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
jQuery(document).on('click focus', '.elfinder-button-search input[type="text"]', function() {
|
||||
setTimeout(addContentRadioButton, 200);
|
||||
});
|
||||
|
||||
fm.bind('open', function() {
|
||||
setTimeout(addContentRadioButton, 500);
|
||||
});
|
||||
|
||||
setTimeout(addContentRadioButton, 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
jQuery(document).ready(function() {
|
||||
var fmakey = jQuery('#fmakey').val();
|
||||
jQuery('#file_manager_advanced').elfinder(
|
||||
// 1st Arg - options
|
||||
{
|
||||
cssAutoLoad : false, // Disable CSS auto loading
|
||||
url : ajaxurl, // connector URL (REQUIRED)
|
||||
customData : {action: 'fma_load_fma_ui',_fmakey: fmakey}, // language (OPTIONAL)
|
||||
},
|
||||
// 2nd Arg - before boot up function
|
||||
function(fm, extraObj) {
|
||||
// `init` event callback function
|
||||
fm.bind('init', function() {
|
||||
// Optional for Japanese decoder "extras/encoding-japanese.min"
|
||||
delete fm.options.rawStringDecoder;
|
||||
if (fm.lang === 'jp') {
|
||||
fm.loadScript(
|
||||
[ fm.baseUrl + '/lib/js/extras/encoding-japanese.min.js' ],
|
||||
function() {
|
||||
if (window.Encoding && Encoding.convert) {
|
||||
fm.options.rawStringDecoder = function(s) {
|
||||
return Encoding.convert(s,{to:'UNICODE',type:'string'});
|
||||
};
|
||||
}
|
||||
},
|
||||
{ loadType: 'tag' }
|
||||
);
|
||||
}
|
||||
});
|
||||
// Optional for set document.title dynamically.
|
||||
var title = document.title;
|
||||
fm.bind('open', function() {
|
||||
var path = '',
|
||||
cwd = fm.cwd();
|
||||
if (cwd) {
|
||||
path = fm.path(cwd.hash) || null;
|
||||
}
|
||||
document.title = path? path + ':' + title : title;
|
||||
}).bind('destroy', function() {
|
||||
document.title = title;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
});
|
||||
@@ -0,0 +1,319 @@
|
||||
/**
|
||||
* FMA Enhanced Debug JavaScript
|
||||
*
|
||||
* Enhanced debugging capabilities for code editor
|
||||
*
|
||||
* @package Advanced File Manager
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
// Debug panel object
|
||||
var FMA_Debug = {
|
||||
init: function() {
|
||||
this.bindEvents();
|
||||
this.createDebugPanel();
|
||||
},
|
||||
|
||||
bindEvents: function() {
|
||||
// Add debug button to code editor
|
||||
$(document).on('click', '.fma-debug-toggle', this.toggleDebugPanel);
|
||||
$(document).on('click', '.fma-debug-close', this.closeDebugPanel);
|
||||
$(document).on('click', '.fma-debug-tab', this.switchTab);
|
||||
$(document).on('click', '#validate-php-btn', this.validatePHP);
|
||||
$(document).on('click', '#test-functions-btn', this.testExecFunctions);
|
||||
},
|
||||
|
||||
createDebugPanel: function() {
|
||||
var debugPanel = `
|
||||
<div id="fma-debug-panel" class="fma-debug-panel" style="display: none;">
|
||||
<div class="fma-debug-header">
|
||||
<h3>Enhanced Debug Panel</h3>
|
||||
<button type="button" class="fma-debug-close">×</button>
|
||||
</div>
|
||||
|
||||
<div class="fma-debug-content">
|
||||
<div class="fma-debug-tabs">
|
||||
<button class="fma-debug-tab active" data-tab="validation">PHP Validation</button>
|
||||
<button class="fma-debug-tab" data-tab="functions">Exec Functions</button>
|
||||
<button class="fma-debug-tab" data-tab="info">System Info</button>
|
||||
</div>
|
||||
|
||||
<div class="fma-debug-tab-content" id="validation-tab">
|
||||
<div class="fma-debug-section">
|
||||
<h4>PHP Syntax Validation</h4>
|
||||
<div class="fma-debug-status">
|
||||
<span class="fma-status-indicator" id="validation-status">
|
||||
<span class="fma-status-icon">⏳</span>
|
||||
</span>
|
||||
<span class="fma-status-text" id="validation-text">
|
||||
Checking validation availability...
|
||||
</span>
|
||||
</div>
|
||||
<button type="button" class="fma-debug-btn" id="validate-php-btn">
|
||||
Validate Current Code
|
||||
</button>
|
||||
<div id="validation-results" class="fma-debug-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fma-debug-tab-content" id="functions-tab">
|
||||
<div class="fma-debug-section">
|
||||
<h4>Exec Functions Status</h4>
|
||||
<button type="button" class="fma-debug-btn" id="test-functions-btn">
|
||||
Test All Functions
|
||||
</button>
|
||||
<div id="functions-results" class="fma-debug-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fma-debug-tab-content" id="info-tab">
|
||||
<div class="fma-debug-section">
|
||||
<h4>System Information</h4>
|
||||
<div class="fma-debug-info" id="system-info">
|
||||
<p>Loading system information...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$('body').append(debugPanel);
|
||||
this.loadSystemInfo();
|
||||
},
|
||||
|
||||
toggleDebugPanel: function(e) {
|
||||
e.preventDefault();
|
||||
$('#fma-debug-panel').slideToggle();
|
||||
},
|
||||
|
||||
closeDebugPanel: function() {
|
||||
$('#fma-debug-panel').slideUp();
|
||||
},
|
||||
|
||||
switchTab: function(e) {
|
||||
e.preventDefault();
|
||||
var tab = $(this).data('tab');
|
||||
|
||||
// Update tab buttons
|
||||
$('.fma-debug-tab').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
// Update tab content
|
||||
$('.fma-debug-tab-content').hide();
|
||||
$('#' + tab + '-tab').show();
|
||||
},
|
||||
|
||||
validatePHP: function() {
|
||||
var $btn = $(this);
|
||||
var $results = $('#validation-results');
|
||||
|
||||
$btn.prop('disabled', true).text('Validating...');
|
||||
$results.html('<div class="fma-loading">Validating PHP syntax...</div>');
|
||||
|
||||
// Get current editor content
|
||||
var phpCode = '';
|
||||
if (typeof editor !== 'undefined' && editor.getValue) {
|
||||
phpCode = editor.getValue();
|
||||
} else {
|
||||
// Fallback to textarea
|
||||
phpCode = $('textarea[name="content"]').val() || '';
|
||||
}
|
||||
|
||||
if (!phpCode.trim()) {
|
||||
$results.html('<div class="fma-error">No PHP code to validate</div>');
|
||||
$btn.prop('disabled', false).text('Validate Current Code');
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: ajaxurl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'fma_validate_php',
|
||||
php_code: phpCode,
|
||||
filename: 'debug.php',
|
||||
nonce: fmaDebug.nonce
|
||||
},
|
||||
success: function(response) {
|
||||
FMA_Debug.displayValidationResults(response);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
$results.html('<div class="fma-error">Validation failed: ' + error + '</div>');
|
||||
},
|
||||
complete: function() {
|
||||
$btn.prop('disabled', false).text('Validate Current Code');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
displayValidationResults: function(response) {
|
||||
var $results = $('#validation-results');
|
||||
var html = '';
|
||||
|
||||
if (response.valid) {
|
||||
html = '<div class="fma-success">';
|
||||
html += '<strong>✓ PHP syntax is valid</strong>';
|
||||
if (response.php_version) {
|
||||
html += '<br>PHP Version: ' + response.php_version;
|
||||
}
|
||||
if (response.execution_time) {
|
||||
html += '<br>Validation time: ' + response.execution_time + 'ms';
|
||||
}
|
||||
html += '</div>';
|
||||
} else {
|
||||
html = '<div class="fma-error">';
|
||||
html += '<strong>✗ PHP syntax errors found</strong>';
|
||||
|
||||
if (response.errors && response.errors.length > 0) {
|
||||
html += '<ul class="fma-error-list">';
|
||||
response.errors.forEach(function(error) {
|
||||
html += '<li>';
|
||||
html += '<strong>Line ' + error.line + ':</strong> ';
|
||||
html += error.message;
|
||||
if (error.type) {
|
||||
html += ' <span class="fma-error-type">(' + error.type + ')</span>';
|
||||
}
|
||||
html += '</li>';
|
||||
});
|
||||
html += '</ul>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
$results.html(html);
|
||||
},
|
||||
|
||||
testExecFunctions: function() {
|
||||
var $btn = $(this);
|
||||
var $results = $('#functions-results');
|
||||
|
||||
$btn.prop('disabled', true).text('Testing...');
|
||||
$results.html('<div class="fma-loading">Testing exec functions...</div>');
|
||||
|
||||
$.ajax({
|
||||
url: ajaxurl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'fma_test_exec_functions',
|
||||
nonce: fmaDebug.nonce
|
||||
},
|
||||
success: function(response) {
|
||||
FMA_Debug.displayFunctionResults(response.data);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
$results.html('<div class="fma-error">Test failed: ' + error + '</div>');
|
||||
},
|
||||
complete: function() {
|
||||
$btn.prop('disabled', false).text('Test All Functions');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
displayFunctionResults: function(results) {
|
||||
var $results = $('#functions-results');
|
||||
var html = '<div class="fma-function-results">';
|
||||
|
||||
Object.keys(results).forEach(function(func) {
|
||||
var result = results[func];
|
||||
var statusClass = result.working ? 'working' : (result.available ? 'available' : 'unavailable');
|
||||
var statusIcon = result.working ? '✓' : (result.available ? '⚠' : '✗');
|
||||
var statusText = result.working ? 'Working' : (result.available ? 'Available' : 'Unavailable');
|
||||
|
||||
html += '<div class="fma-function-item ' + statusClass + '">';
|
||||
html += '<div class="fma-function-name">' + func + '</div>';
|
||||
html += '<div class="fma-function-status">';
|
||||
html += '<span class="fma-status-icon">' + statusIcon + '</span>';
|
||||
html += '<span class="fma-status-text">' + statusText + '</span>';
|
||||
html += '</div>';
|
||||
|
||||
if (result.error) {
|
||||
html += '<div class="fma-function-error">Error: ' + result.error + '</div>';
|
||||
}
|
||||
|
||||
if (result.output) {
|
||||
html += '<div class="fma-function-output">Output: ' + result.output + '</div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
$results.html(html);
|
||||
},
|
||||
|
||||
loadSystemInfo: function() {
|
||||
$.ajax({
|
||||
url: ajaxurl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'fma_get_debug_info',
|
||||
nonce: fmaDebug.nonce
|
||||
},
|
||||
success: function(response) {
|
||||
FMA_Debug.displaySystemInfo(response.data);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
$('#system-info').html('<div class="fma-error">Failed to load system info: ' + error + '</div>');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
displaySystemInfo: function(info) {
|
||||
var html = '';
|
||||
|
||||
html += '<p><strong>PHP Version:</strong> ' + info.php_version + '</p>';
|
||||
html += '<p><strong>PHP Binary:</strong> ' + info.php_binary + '</p>';
|
||||
|
||||
if (info.php_path) {
|
||||
html += '<p><strong>PHP Path:</strong> ' + info.php_path + '</p>';
|
||||
}
|
||||
|
||||
html += '<p><strong>ExecWithFallback:</strong> ';
|
||||
html += info.exec_with_fallback_available ?
|
||||
'<span class="fma-success">✓ Available</span>' :
|
||||
'<span class="fma-error">✗ Not Available</span>';
|
||||
html += '</p>';
|
||||
|
||||
html += '<p><strong>Validation Available:</strong> ';
|
||||
html += info.validation_available ?
|
||||
'<span class="fma-success">✓ Yes</span>' :
|
||||
'<span class="fma-error">✗ No</span>';
|
||||
html += '</p>';
|
||||
|
||||
// Update validation status
|
||||
$('#validation-status').html(
|
||||
info.validation_available ?
|
||||
'<span class="fma-status-icon fma-success">✓</span>' :
|
||||
'<span class="fma-status-icon fma-error">✗</span>'
|
||||
);
|
||||
$('#validation-text').text(
|
||||
info.validation_available ?
|
||||
'PHP validation is available' :
|
||||
'PHP validation is not available'
|
||||
);
|
||||
|
||||
$('#system-info').html(html);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize when document is ready
|
||||
$(document).ready(function() {
|
||||
FMA_Debug.init();
|
||||
});
|
||||
|
||||
// Add debug button to code editor when it loads
|
||||
$(document).on('DOMNodeInserted', function(e) {
|
||||
if ($(e.target).find('.CodeMirror').length > 0) {
|
||||
if (!$('.fma-debug-toggle').length) {
|
||||
var debugButton = '<button type="button" class="fma-debug-toggle button button-secondary" style="margin-left: 10px;">🐛 Debug</button>';
|
||||
$(e.target).find('.CodeMirror').after(debugButton);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* elFinder custom commands for File Manager Advanced
|
||||
*/
|
||||
(function ($) {
|
||||
$(document).ready(function () {
|
||||
if (typeof elFinder !== 'undefined' && elFinder.prototype && elFinder.prototype.commands) {
|
||||
|
||||
// Icon CSS
|
||||
var pluginUrl = (typeof afm_object !== 'undefined') ? afm_object.plugin_url : '';
|
||||
if (!pluginUrl) {
|
||||
// Try to find it from script src
|
||||
var script = $('script[src*="fma-elfinder-commands.js"]');
|
||||
if (script.length) {
|
||||
pluginUrl = script.attr('src').split('application/assets/js/')[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (pluginUrl) {
|
||||
var iconStyle = `
|
||||
<style id="fma-replace-icon">
|
||||
.elfinder-button-icon-replace {
|
||||
background-image: url(${pluginUrl}application/assets/images/replace.png) !important;
|
||||
background-size: 16px !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-position: center !important;
|
||||
}
|
||||
.elfinder-contextmenu-icon-replace {
|
||||
background-image: url(${pluginUrl}application/assets/images/replace.png) !important;
|
||||
background-size: 16px !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-position: center !important;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
if ($('#fma-replace-icon').length === 0) {
|
||||
$('head').append(iconStyle);
|
||||
}
|
||||
}
|
||||
|
||||
// Translation
|
||||
var i18Obj = elFinder.prototype.i18 || elFinder.prototype.i18n;
|
||||
if (i18Obj) {
|
||||
// Ensure English is initialized if not present
|
||||
if (!i18Obj.en) {
|
||||
i18Obj.en = { messages: {} };
|
||||
}
|
||||
for (var lang in i18Obj) {
|
||||
if (i18Obj.hasOwnProperty(lang)) {
|
||||
var msg = 'Replace';
|
||||
// Add some common translations
|
||||
if (lang === 'es') msg = 'Reemplazar';
|
||||
if (lang === 'fr') msg = 'Remplacer';
|
||||
if (lang === 'de') msg = 'Ersetzen';
|
||||
if (lang === 'it') msg = 'Sostituisci';
|
||||
if (lang === 'pt') msg = 'Substituir';
|
||||
if (lang === 'ru') msg = 'Заменить';
|
||||
if (lang === 'zh_CN') msg = '替换';
|
||||
|
||||
if (!i18Obj[lang].messages) {
|
||||
i18Obj[lang].messages = {};
|
||||
}
|
||||
i18Obj[lang].messages.cmdreplace = msg;
|
||||
i18Obj[lang].messages.replace = msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace command definition
|
||||
elFinder.prototype.commands.replace = function () {
|
||||
this.updateOnSelect = false;
|
||||
this.title = 'Replace';
|
||||
|
||||
this.getstate = function (hashes) {
|
||||
var fm = this.fm;
|
||||
var sel = this.hashes(hashes);
|
||||
var cnt = sel.length;
|
||||
|
||||
if (cnt !== 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
var file = fm.file(sel[0]);
|
||||
if (!file || file.mime === 'directory' || file.phash === null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return fm.isCommandEnabled('upload', sel[0]) ? 0 : -1;
|
||||
};
|
||||
|
||||
this.exec = function (hashes) {
|
||||
var fm = this.fm;
|
||||
var sel = this.hashes(hashes);
|
||||
var file = fm.file(sel[0]);
|
||||
var self = this;
|
||||
|
||||
if (!file) {
|
||||
return $.Deferred().reject();
|
||||
}
|
||||
|
||||
var input = $('<input type="file" />');
|
||||
|
||||
input.on('change', function () {
|
||||
if (this.files && this.files.length) {
|
||||
var selectedFile = this.files[0];
|
||||
var formData = new FormData();
|
||||
formData.append('cmd', 'upload');
|
||||
formData.append('target', file.phash);
|
||||
formData.append('overwrite', '1');
|
||||
formData.append('upload[]', selectedFile, file.name);
|
||||
|
||||
// Add custom data (nonce, action, etc. required by WordPress and the plugin)
|
||||
var customData = fm.options.customData || {};
|
||||
if (typeof customData === 'function') {
|
||||
customData = customData('upload', fm);
|
||||
}
|
||||
|
||||
|
||||
for (var key in customData) {
|
||||
if (customData.hasOwnProperty(key)) {
|
||||
if (!formData.has(key)) { // Prevent overwriting existing formData keys like 'cmd' or 'target'
|
||||
formData.append(key, customData[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use jQuery.ajax directly for upload to avoid elFinder request stripping FormData issues
|
||||
$.ajax({
|
||||
url: fm.options.url,
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
beforeSend: function () {
|
||||
fm.notify({ type: 'upload', cnt: 1, hide: false });
|
||||
},
|
||||
success: function (data) {
|
||||
// Always decrement the counter
|
||||
fm.notify({ type: 'upload', cnt: -1, hide: true });
|
||||
|
||||
// Try to parse if it's a string
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
if (data && data.error) {
|
||||
fm.error(data.error);
|
||||
} else {
|
||||
fm.notify({
|
||||
type: 'info',
|
||||
msg: 'File replaced successfully!',
|
||||
hide: 3000
|
||||
});
|
||||
// Refresh the UI to show the new file immediately
|
||||
if (typeof fm.exec === 'function') {
|
||||
fm.exec('reload');
|
||||
}
|
||||
if (typeof fm.sync === 'function') {
|
||||
fm.sync();
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
fm.notify({ type: 'upload', cnt: -1, hide: true });
|
||||
console.error('FMA Replace Error:', error, xhr.responseText);
|
||||
fm.notify({
|
||||
type: 'error',
|
||||
msg: 'Failed to replace file. Error: ' + error,
|
||||
hide: 3000
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
input.click();
|
||||
return $.Deferred().resolve();
|
||||
};
|
||||
};
|
||||
|
||||
// Function to add replace to context menu
|
||||
var addReplaceToContextMenu = function (opts) {
|
||||
if (opts && opts.contextmenu && opts.contextmenu.files) {
|
||||
var filesMenu = opts.contextmenu.files;
|
||||
if (filesMenu.indexOf('replace') === -1) {
|
||||
var renameIdx = filesMenu.indexOf('rename');
|
||||
if (renameIdx !== -1) {
|
||||
filesMenu.splice(renameIdx + 1, 0, 'replace');
|
||||
} else {
|
||||
filesMenu.push('replace');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add to context menu for ALL future instances (by modifying the prototype _options)
|
||||
if (elFinder && elFinder.prototype && elFinder.prototype._options) {
|
||||
addReplaceToContextMenu(elFinder.prototype._options);
|
||||
}
|
||||
|
||||
// Also handle existing and new instances
|
||||
var setupInstance = function (fm) {
|
||||
if (fm && fm.options) {
|
||||
addReplaceToContextMenu(fm.options);
|
||||
}
|
||||
|
||||
// Get the translation based on current language
|
||||
var msg = 'Replace';
|
||||
var lang = fm.lang || (fm.options && fm.options.lang) || 'en';
|
||||
if (lang === 'es') msg = 'Reemplazar';
|
||||
if (lang === 'fr') msg = 'Remplacer';
|
||||
if (lang === 'de') msg = 'Ersetzen';
|
||||
if (lang === 'it') msg = 'Sostituisci';
|
||||
if (lang === 'pt') msg = 'Substituir';
|
||||
if (lang === 'ru') msg = 'Заменить';
|
||||
if (lang === 'zh_CN') msg = '替换';
|
||||
|
||||
// Ensure instance has the translation (FORCE IT)
|
||||
if (fm && fm.messages) {
|
||||
fm.messages.cmdreplace = msg;
|
||||
fm.messages.replace = msg;
|
||||
}
|
||||
// Ensure the command title is capitalized
|
||||
if (fm && fm.commands && fm.commands.replace) {
|
||||
fm.commands.replace.title = msg;
|
||||
}
|
||||
};
|
||||
|
||||
// Apply to any elFinder instances that might already exist
|
||||
if (typeof elFinder !== 'undefined' && typeof elFinder.instances !== 'undefined') {
|
||||
$.each(elFinder.instances, function (id, fm) {
|
||||
setupInstance(fm);
|
||||
});
|
||||
}
|
||||
|
||||
// Hook into elFinder creation event to apply to new instances as they are created
|
||||
$(document).on('elfindercreate', function (event, fm) {
|
||||
setupInstance(fm);
|
||||
});
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
Vendored
+2
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user