This commit is contained in:
2026-07-02 15:54:39 -06:00
commit 9883323161
17470 changed files with 4470592 additions and 0 deletions
@@ -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);
}
}
});
@@ -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">&times;</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);
@@ -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);
File diff suppressed because one or more lines are too long