: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @class elFinder contextmenu
* @author Dmitry (dio) Levashov
jQuery.fn.elfindercontextmenu = function(fm) {
return this.each(function() {
cmItem = 'elfinder-contextmenu-item',
smItem = 'elfinder-contextsubmenu-item',
exIcon = 'elfinder-contextmenu-extra-icon',
cHover = fm.res('class', 'hover'),
menu.data('drag', true).data('touching') && menu.find('.'+cHover).removeClass(cHover);
menu.data('draged', true).removeData('drag');
menu = jQuery(this).addClass('touch-punch ui-helper-reset ui-front ui-widget ui-state-default ui-corner-all elfinder-contextmenu elfinder-contextmenu-'+fm.direction)
.on('touchstart', function(e) {
menu.data('touching', true).children().removeClass(cHover);
.on('touchend', function(e) {
menu.removeData('touching');
.on('mouseenter mouseleave', '.'+cmItem, function(e) {
jQuery(this).toggleClass(cHover, (e.type === 'mouseenter' || (! menu.data('draged') && menu.data('submenuKeep'))? true : false));
if (menu.data('draged') && menu.data('submenuKeep')) {
menu.find('.elfinder-contextmenu-sub:visible').parent().addClass(cHover);
.on('mouseenter mouseleave', '.'+exIcon, function(e) {
jQuery(this).parent().toggleClass(cHover, e.type === 'mouseleave');
.on('mouseenter mouseleave', '.'+cmItem+',.'+smItem, function(e) {
var setIndex = function(target, sub) {
jQuery.each(sub? subnodes : nodes, function(i, n) {
(sub? subnodes : nodes)._cur = i;
var target = jQuery(this),
if (selected && !selected.children('div.elfinder-contextmenu-sub:visible').length) {
selected.removeClass(cHover);
if (e.type === 'mouseenter') {
if (target.hasClass(smItem)) {
subselected.removeClass(cHover);
subnodes = selected.find('div.'+smItem);
if (target.hasClass(smItem)) {
.on('contextmenu', function(){return false;})
.on('mouseup', function() {
menu.removeData('draged');
ltr = fm.direction === 'ltr',
subpos = ltr? 'left' : 'right',
types = Object.assign({}, fm.options.contextmenu),
tpl = '<div class="'+cmItem+'{className}"><span class="elfinder-button-icon {icon} elfinder-contextmenu-icon"{style}></span><span>{label}</span></div>',
item = function(label, icon, callback, opts) {
className = ' ' + opts.className;
iconClass = opts.iconClass;
v = opts.iconImg.split(/ +/);
pos = v[1] && v[2]? fm.escape(v[1] + 'px ' + v[2] + 'px') : '';
style = ' style="background:url(\''+fm.escape(v[0])+'\') '+(pos? pos : '0 0')+' no-repeat;'+(pos? '' : 'posbackground-size:contain;')+'"';
return jQuery(tpl.replace('{icon}', icon ? 'elfinder-button-icon-'+icon : (iconClass? iconClass : ''))
.replace('{label}', label)
.replace('{style}', style)
.replace('{className}', className))
.on('click', function(e) {
urlIcon = function(iconUrl) {
var v = iconUrl.split(/ +/),
pos = v[1] && v[2]? (v[1] + 'px ' + v[2] + 'px') : '';
backgroundImage: 'url("'+v[0]+'")',
backgroundRepeat: 'no-repeat',
backgroundPosition: pos? pos : '',
backgroundSize: pos? '' : 'contain'
nodes, selected, subnodes, subselected, autoSyncStop, subHoverTm,
autoToggle = function() {
var evTouchStart = 'touchstart.contextmenuAutoToggle';
menu.data('hideTm') && clearTimeout(menu.data('hideTm'));
if (menu.is(':visible')) {
menu.on('touchstart', function(e) {
if (e.originalEvent.touches.length > 1) {
menu.data('hideTm') && clearTimeout(menu.data('hideTm'));
.data('hideTm', setTimeout(function() {
if (menu.is(':visible')) {
cwd.find('.elfinder-cwd-file').off(evTouchStart);
cwd.find('.elfinder-cwd-file.ui-selected')
.one(evTouchStart, function(e) {
if (e.originalEvent.touches.length > 1) {
var tgt = jQuery(e.target);
if (menu.first().length && !tgt.is('input:checkbox') && !tgt.hasClass('elfinder-cwd-select')) {
open(e.originalEvent.touches[0].pageX, e.originalEvent.touches[0].pageY);
cwd.data('longtap', true)
tgt.one('touchend', function() {
cwd.removeData('longtap');
cwd.find('.elfinder-cwd-file').off(evTouchStart);
.one('unselect.'+fm.namespace, function() {
cwd.find('.elfinder-cwd-file').off(evTouchStart);
menu.css('opacity', '1').show();
ESC = jQuery.ui.keyCode.ESCAPE,
ENT = jQuery.ui.keyCode.ENTER,
LEFT = jQuery.ui.keyCode.LEFT,
RIGHT = jQuery.ui.keyCode.RIGHT,
UP = jQuery.ui.keyCode.UP,
DOWN = jQuery.ui.keyCode.DOWN,
subent = fm.direction === 'ltr'? RIGHT : LEFT,
sublev = subent === RIGHT? LEFT : RIGHT;
if (jQuery.inArray(code, [ESC, ENT, LEFT, RIGHT, UP, DOWN]) !== -1) {
e.stopImmediatePropagation();
if (code == ESC || code === sublev) {
if (selected && subnodes && subselected) {
subselected.trigger('mouseleave').trigger('submenuclose');
selected.addClass(cHover);
fm.trigger('closecontextmenu');
} else if (code == UP || code == DOWN) {
subselected.trigger('mouseleave');
if (code == DOWN && (! subselected || subnodes.length <= ++subnodes._cur)) {
} else if (code == UP && (! subselected || --subnodes._cur < 0)) {
subnodes._cur = subnodes.length - 1;
subselected = subnodes.eq(subnodes._cur).trigger('mouseenter');
selected.trigger('mouseleave');
if (code == DOWN && (! selected || nodes.length <= ++nodes._cur)) {
} else if (code == UP && (! selected || --nodes._cur < 0)) {
nodes._cur = nodes.length - 1;
selected = nodes.eq(nodes._cur).addClass(cHover);
} else if (selected && (code == ENT || code === subent)) {
if (selected.hasClass('elfinder-contextmenu-group')) {
code == ENT && subselected.click();
selected.trigger('mouseenter');
subnodes = selected.find('div.'+smItem);
subselected = subnodes.first().addClass(cHover);
code == ENT && selected.click();
open = function(x, y, css) {
var width = menu.outerWidth(),
height = menu.outerHeight(),
bstyle = base.attr('style'),
mw = fm.UA.Mobile? 40 : 2,
mh = fm.UA.Mobile? 20 : 2,
x = x - (bpos? bpos.left : 0),
y = y - (bpos? bpos.top : 0),
css = Object.assign(css || {}, {
top : Math.max(0, y + mh + height < bheight ? y + mh : y - (y + height - bheight)),
left : Math.max(0, (x < width + mw || x + mw + width < bwidth)? x + mw : x - mw - width),
menu.stop().removeAttr('style').css(css);
base.attr('style', bstyle);
css[subpos] = parseInt(menu.width());
menu.find('.elfinder-contextmenu-sub').css(css);
jQuery('div.elfinder div.overflow-scrolling-touch').css('-webkit-overflow-scrolling', 'auto');
jQuery(document).on('keydown.' + fm.namespace, keyEvts);
evts = jQuery._data(document).events;
if (evts && evts.keydown) {
evts.keydown.unshift(evts.keydown.pop());
fm.UA.Mobile && autoToggle();
requestAnimationFrame(function() {
fm.getUI().one('click.' + fm.namespace, close);
fm.getUI().off('click.' + fm.namespace, close);
jQuery(document).off('keydown.' + fm.namespace, keyEvts);
currentType = currentTargets = null;
if (menu.is(':visible') || menu.children().length) {
fm.toHide(menu.removeAttr('style').empty().removeData('submenuKeep'));
if (! menu.draggable('instance')) {
if (! menu.hasClass('ui-draggable')) {
if (menu.data('prevNode')) {
menu.data('prevNode').after(menu);
menu.removeData('prevNode');
fm.trigger('closecontextmenu');
jQuery('div.elfinder div.overflow-scrolling-touch').css('-webkit-overflow-scrolling', 'touch');
autoSyncStop && fm.searchStatus.state < 1 && ! fm.searchStatus.ininc && fm.autoSync();
create = function(type, targets) {
currentTargets = targets;
// get current uiCmdMap option
if (!(cmdMap = fm.option('uiCmdMap', isCwd? void(0) : targets[0]))) {
disabled = fm.getDisabledCmds(targets);
selcnt = fm.selected().length;
menu.append('<div class="ui-corner-top ui-widget-header elfinder-contextmenu-header"><span>'
+ fm.i18n('selectedItems', ''+selcnt)
jQuery.each(types[type]||[], function(i, name) {
var cmd, cmdName, useMap, node, submenu, hover;
cmd = fm.getCommand(cmdName);
if (cmd && !isCwd && (!fm.searchStatus.state || !cmd.disableOnSearch)) {
cmd.__disabled = cmd._disabled;
cmd._disabled = !(cmd.alwaysEnabled || (fm._commands[cmdName] ? jQuery.inArray(name, disabled) === -1 && (!useMap || !disabled[cmdName]) : false));
jQuery.each(cmd.linkedCmds, function(i, n) {
if (c = fm.getCommand(n)) {
c.__disabled = c._disabled;
c._disabled = !(c.alwaysEnabled || (fm._commands[n] ? !disabled[n] : false));
if (cmd && !cmd._disabled && cmd.getstate(targets) != -1) {
if (!cmd.variants.length) {
node = item(cmd.title, cmd.className? cmd.className : cmd.name, function(){}, cmd.contextmenuOpts);
submenu = jQuery('<div class="ui-front ui-corner-all elfinder-contextmenu-sub"></div>')
.css('max-height', fm.getUI().height() - 30)
.appendTo(node.append('<span class="elfinder-contextmenu-arrow"></span>'));
var bstyle = base.attr('style');
base.width(base.width());
// top: '-1000px' to prevent visible scrollbar of window with the elFinder option `height: '100%'`
submenu.css({ top: '-1000px', left: 'auto', right: 'auto' });
var nodeOffset = node.offset(),
nodeleft = nodeOffset.left,
nodetop = nodeOffset.top,
nodewidth = node.outerWidth(),
width = submenu.outerWidth(true),
height = submenu.outerHeight(true),
baseOffset = base.offset(),
wwidth = baseOffset.left + base.width(),
wheight = baseOffset.top + base.height(),
over = (nodeleft + nodewidth + width) - wwidth;
if (nodeleft > width - 5) {
if ((nodeleft + nodewidth + width - 15) < wwidth) {
over = (nodetop + 5 + height) - wheight;
y = (over > 0 && nodetop < wheight)? 5 - over : (over > 0? 30 - height : 5);
menu.find('.elfinder-contextmenu-sub:visible').hide();
base.attr('style', bstyle);
node.addClass('elfinder-contextmenu-group')
.on('mouseleave', '.elfinder-contextmenu-sub', function(e) {
if (! menu.data('draged')) {
menu.removeData('submenuKeep');
.on('submenuclose', '.elfinder-contextmenu-sub', function(e) {
.on('click', '.'+smItem, function(e){
if (! menu.data('draged')) {
if (!cmd.keepContextmenu) {
$this.removeClass(cHover);
opts = $this.data('exec');
if (typeof opts === 'undefined') {
if (typeof opts === 'object') {