From 30fb26296d46d865f09db539ea70751d61102ee4 Mon Sep 17 00:00:00 2001 From: Regis Houssin <regis@dolibarr.fr> Date: Sat, 24 Mar 2012 21:19:22 +0800 Subject: [PATCH] Fix: add check array in GETPOST New: update multiselect with a fork --- htdocs/core/lib/functions.lib.php | 14 +- .../multiselect/css/ui.multiselect.css | 14 +- .../plugins/multiselect/js/ui.multiselect.js | 242 ++++++++++++++---- 3 files changed, 211 insertions(+), 59 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index a077743dc92..46bc67cee43 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -275,7 +275,7 @@ function dol_shutdown() * Return value of a param into GET or POST supervariable * * @param string $paramname Name of parameter to found - * @param string $check Type of check (''=no check, 'int'=check it's numeric, 'alpha'=check it's alpha only) + * @param string $check Type of check (''=no check, 'int'=check it's numeric, 'alpha'=check it's alpha only, 'array'=check it's array) * @param int $method Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get) * @return string Value found or '' if check fails */ @@ -289,17 +289,25 @@ function GETPOST($paramname,$check='',$method=0) if (! empty($check)) { - $out=trim($out); // Check if numeric - if ($check == 'int' && ! preg_match('/^[-\.,0-9]+$/i',$out)) $out=''; + if ($check == 'int' && ! preg_match('/^[-\.,0-9]+$/i',$out)) + { + $out=trim($out); + $out=''; + } // Check if alpha elseif ($check == 'alpha') { + $out=trim($out); // '"' is dangerous because param in url can close the href= or src= and add javascript functions. // '../' is dangerous because it allows dir transversals if (preg_match('/"/',$out)) $out=''; else if (preg_match('/\.\.\//',$out)) $out=''; } + elseif ($check == 'array') + { + if (! is_array($out) || empty($out)) $out=array(); + } } return $out; diff --git a/htdocs/includes/jquery/plugins/multiselect/css/ui.multiselect.css b/htdocs/includes/jquery/plugins/multiselect/css/ui.multiselect.css index f4599f8fc6b..7d3a44801d2 100644 --- a/htdocs/includes/jquery/plugins/multiselect/css/ui.multiselect.css +++ b/htdocs/includes/jquery/plugins/multiselect/css/ui.multiselect.css @@ -1,25 +1,25 @@ /* Multiselect ----------------------------------*/ -.multiselect { width: 460px; height: 150px; } .ui-multiselect { border: solid 1px; font-size: 0.8em; } .ui-multiselect ul { -moz-user-select: none; } -.ui-multiselect li { margin: 0; padding: 0; cursor: default; line-height: 20px; height: 20px; font-size: 11px; list-style: none; } +.ui-multiselect li { margin: 0; padding: 0; cursor: default; line-height: 20px; height: 20px; font-size: 11px; list-style: none; padding-right: 18px; overflow: hidden; } .ui-multiselect li a { color: #999; text-decoration: none; padding: 0; display: block; float: left; cursor: pointer;} .ui-multiselect li.ui-draggable-dragging { padding-left: 10px; } .ui-multiselect div.selected { position: relative; padding: 0; margin: 0; border: 0; float:left; } .ui-multiselect ul.selected { position: relative; padding: 0; overflow: auto; overflow-x: hidden; background: #fff; margin: 0; list-style: none; border: 0; position: relative; width: 100%; } -.ui-multiselect ul.selected li { } -.ui-multiselect div.available { position: relative; padding: 0; margin: 0; border: 0; float:left; border-left: 1px solid; } +.ui-multiselect div.available { position: relative; padding: 0; margin: 0; border: 0; float:left; } .ui-multiselect ul.available { position: relative; padding: 0; overflow: auto; overflow-x: hidden; background: #fff; margin: 0; list-style: none; border: 0; width: 100%; } .ui-multiselect ul.available li { padding-left: 10px; } - + +.ui-multiselect div.right-column { border-left: 1px solid; } + .ui-multiselect .ui-state-default { border: none; margin-bottom: 1px; position: relative; padding-left: 20px;} .ui-multiselect .ui-state-hover { border: none; } .ui-multiselect .ui-widget-header {border: none; font-size: 11px; margin-bottom: 1px;} - + .ui-multiselect .add-all { float: right; padding: 7px;} .ui-multiselect .remove-all { float: right; padding: 7px;} .ui-multiselect .search { float: left; padding: 4px;} @@ -27,5 +27,5 @@ .ui-multiselect li span.ui-icon-arrowthick-2-n-s { position: absolute; left: 2px; } .ui-multiselect li a.action { position: absolute; right: 2px; top: 2px; } - + .ui-multiselect input.search { height: 14px; padding: 1px; opacity: 0.5; margin: 4px; width: 100px; } \ No newline at end of file diff --git a/htdocs/includes/jquery/plugins/multiselect/js/ui.multiselect.js b/htdocs/includes/jquery/plugins/multiselect/js/ui.multiselect.js index 1234fa7a957..eb4578fc782 100644 --- a/htdocs/includes/jquery/plugins/multiselect/js/ui.multiselect.js +++ b/htdocs/includes/jquery/plugins/multiselect/js/ui.multiselect.js @@ -4,13 +4,13 @@ * Authors: * Michael Aufreiter (quasipartikel.at) * Yanick Rochon (yanick.rochon[at]gmail[dot]com) - * + * * Dual licensed under the MIT (MIT-LICENSE.txt) * and GPL (GPL-LICENSE.txt) licenses. - * + * * http://www.quasipartikel.at/multiselect/ * - * + * * Depends: * ui.core.js * ui.sortable.js @@ -18,7 +18,7 @@ * Optional: * localization (http://plugins.jquery.com/project/localisation) * scrollTo (http://plugins.jquery.com/project/ScrollTo) - * + * * Todo: * Make batch actions faster * Implement dynamic insertion through remote calls @@ -30,49 +30,82 @@ $.widget("ui.multiselect", { options: { sortable: true, + dragToAdd: true, searchable: true, doubleClickable: true, animated: 'fast', show: 'slideDown', hide: 'slideUp', dividerLocation: 0.6, + selectedContainerOnLeft: true, + width: null, + height: null, nodeComparator: function(node1,node2) { var text1 = node1.text(), text2 = node2.text(); return text1 == text2 ? 0 : (text1 < text2 ? -1 : 1); - } + }, + includeRemoveAll: true, + includeAddAll: true, + pressEnterKeyToAddAll: false }, _create: function() { this.element.hide(); this.id = this.element.attr("id"); this.container = $('<div class="ui-multiselect ui-helper-clearfix ui-widget"></div>').insertAfter(this.element); this.count = 0; // number of currently selected options - this.selectedContainer = $('<div class="selected"></div>').appendTo(this.container); - this.availableContainer = $('<div class="available"></div>').appendTo(this.container); - this.selectedActions = $('<div class="actions ui-widget-header ui-helper-clearfix"><span class="count">0 '+$.ui.multiselect.locale.itemsCount+'</span><a href="#" class="remove-all">'+$.ui.multiselect.locale.removeAll+'</a></div>').appendTo(this.selectedContainer); - this.availableActions = $('<div class="actions ui-widget-header ui-helper-clearfix"><input type="text" class="search empty ui-widget-content ui-corner-all"/><a href="#" class="add-all">'+$.ui.multiselect.locale.addAll+'</a></div>').appendTo(this.availableContainer); + this.selectedContainer = $('<div class="selected"></div>'); + if (this.options.selectedContainerOnLeft) { + this.selectedContainer.appendTo(this.container); + this.availableContainer = $('<div class="available"></div>').appendTo(this.container); + this.availableContainer.addClass('right-column'); + } + else + { + this.availableContainer = $('<div class="available"></div>').appendTo(this.container); + this.selectedContainer.appendTo(this.container); + this.selectedContainer.addClass('right-column'); + } + this.selectedActions = $('<div class="actions ui-widget-header ui-helper-clearfix"><span class="count">0 '+$.ui.multiselect.locale.itemsCount+'</span>'+(this.options.includeRemoveAll?'<a href="#" class="remove-all">'+$.ui.multiselect.locale.removeAll+'</a>':'<span class="remove-all"> </span>')+'</div>').appendTo(this.selectedContainer); + this.availableActions = $('<div class="actions ui-widget-header ui-helper-clearfix"><input type="text" class="search empty ui-widget-content ui-corner-all"/>'+(this.options.includeAddAll?'<a href="#" class="add-all">'+$.ui.multiselect.locale.addAll+'</a>':'<span class="add-all"> </span>')+'</div>').appendTo(this.availableContainer); this.selectedList = $('<ul class="selected connected-list"><li class="ui-helper-hidden-accessible"></li></ul>').bind('selectstart', function(){return false;}).appendTo(this.selectedContainer); this.availableList = $('<ul class="available connected-list"><li class="ui-helper-hidden-accessible"></li></ul>').bind('selectstart', function(){return false;}).appendTo(this.availableContainer); - + var that = this; + var width = this.options.width; + if (!width) { + width = this.element.width(); + } + var height = this.options.height; + if (!height) { + height = this.element.height(); + } + // set dimensions - this.container.width(this.element.width()+1); - this.selectedContainer.width(Math.floor(this.element.width()*this.options.dividerLocation)); - this.availableContainer.width(Math.floor(this.element.width()*(1-this.options.dividerLocation))); + this.container.width(width-2); + if (this.options.selectedContainerOnLeft) { + this.selectedContainer.width(Math.floor(width*this.options.dividerLocation)-1); + this.availableContainer.width(Math.floor(width*(1-this.options.dividerLocation))-2); + } + else + { + this.selectedContainer.width(Math.floor(width*this.options.dividerLocation)-2); + this.availableContainer.width(Math.floor(width*(1-this.options.dividerLocation))-1); + } // fix list height to match <option> depending on their individual header's heights - this.selectedList.height(Math.max(this.element.height()-this.selectedActions.height(),1)); - this.availableList.height(Math.max(this.element.height()-this.availableActions.height(),1)); - + this.selectedList.height(Math.max(height-this.selectedActions.height(),1)); + this.availableList.height(Math.max(height-this.availableActions.height(),1)); + if ( !this.options.animated ) { this.options.show = 'show'; this.options.hide = 'hide'; } - + // init lists this._populateLists(this.element.find('option')); - + // make selection sortable if (this.options.sortable) { this.selectedList.sortable({ @@ -85,49 +118,63 @@ $.widget("ui.multiselect", { $(this).data('optionLink').remove().appendTo(that.element); }); }, + beforeStop: function (event, ui) { + // This lets us recognize which item was just added to + // the list in receive, per the workaround for not being + // able to reference the new element. + ui.item.addClass('dropped'); + }, receive: function(event, ui) { ui.item.data('optionLink').attr('selected', true); // increment count that.count += 1; that._updateCount(); - // workaround, because there's no way to reference + // workaround, because there's no way to reference // the new element, see http://dev.jqueryui.com/ticket/4303 - that.selectedList.children('.ui-draggable').each(function() { - $(this).removeClass('ui-draggable'); + that.selectedList.children('.dropped').each(function() { + $(this).removeClass('dropped'); $(this).data('optionLink', ui.item.data('optionLink')); $(this).data('idx', ui.item.data('idx')); that._applyItemState($(this), true); }); - + // workaround according to http://dev.jqueryui.com/ticket/4088 setTimeout(function() { ui.item.remove(); }, 1); - } + }, + stop: function (event, ui) { that.element.change(); } }); } - + // set up livesearch if (this.options.searchable) { this._registerSearchEvents(this.availableContainer.find('input.search')); } else { $('.search').hide(); } - + // batch actions this.container.find(".remove-all").click(function() { that._populateLists(that.element.find('option').removeAttr('selected')); + that.element.trigger('change'); return false; }); - + this.container.find(".add-all").click(function() { var options = that.element.find('option').not(":selected"); if (that.availableList.children('li:hidden').length > 1) { that.availableList.children('li').each(function(i) { - if ($(this).is(":visible")) $(options[i-1]).attr('selected', 'selected'); + if ($(this).is(":visible")) $(options[i-1]).attr('selected', 'selected'); }); } else { options.attr('selected', 'selected'); } that._populateLists(that.element.find('option')); + that.element.trigger('change'); + if (that.options.pressEnterKeyToAddAll) { + //clear input after add all + $('input.search').val(""); + } + return false; }); }, @@ -137,25 +184,80 @@ $.widget("ui.multiselect", { $.Widget.prototype.destroy.apply(this, arguments); }, + addOption: function(option) { + // Append the option + option = $(option); + var select = this.element; + select.append(option); + + var item = this._getOptionNode(option).appendTo(option.attr('selected') ? this.selectedList : this.availableList).show(); + + if (option.attr('selected')) { + this.count += 1; + } + this._applyItemState(item, option.attr('selected')); + item.data('idx', this.count); + + // update count + this._updateCount(); + this._filter.apply(this.availableContainer.find('input.search'), [this.availableList]); + }, + // Redisplay the lists of selected/available options. + // Call this after you've selected/unselected some options programmatically. + // GRIPE This is O(n) where n is the length of the list - seems like + // there must be a smarter way of doing this, but I have not been able + // to come up with one. I see no way to detect programmatic setting of + // the option's selected property, and without that, it seems like we + // can't have a general-case listener that does its thing every time an + // option is selected. + refresh: function() { + // Redisplay our lists. + this._populateLists(this.element.find('option')); + }, _populateLists: function(options) { this.selectedList.children('.ui-element').remove(); this.availableList.children('.ui-element').remove(); this.count = 0; var that = this; + var groups = $(this.element).find("optgroup").map(function(i) { + return that._getOptionGroup($(this)); + }); + groups.appendTo(this.selectedList.add(this.availableList)); + var items = $(options.map(function(i) { - var item = that._getOptionNode(this).appendTo(this.selected ? that.selectedList : that.availableList).show(); + var item = that._getOptionNode(this).appendTo(that._getOptionList(this)).show(); if (this.selected) that.count += 1; that._applyItemState(item, this.selected); item.data('idx', i); return item[0]; })); - + // update count this._updateCount(); that._filter.apply(this.availableContainer.find('input.search'), [that.availableList]); }, + _getOptionList: function(option) { + var selected = option.selected; + option = $(option); + var $list = selected ? this.selectedList : this.availableList; + var $group = option.closest("optgroup"); + if ($group.length === 0) { + return $list; + } else { + var $groupList = $list.find("ul[title='" + $group.attr("label") + "']"); + if ($groupList.length === 0) { + $groupList = $("<ul class='ui-state-default ui-element available' title='" + $group.attr("label") + "'>" + $group.attr("label") + "</ul>").appendTo($list); + } + $groupList.show(); + return $groupList; + } + }, + _getOptionGroup : function(optgroup) { + var groupNode = $("<ul class='ui-state-default ui-element available' title='" + optgroup.attr("label") + "'>" + optgroup.attr("label") + "</ul>").hide(); + return groupNode[0]; + }, _updateCount: function() { this.selectedContainer.find('span.count').text(this.count+" "+$.ui.multiselect.locale.itemsCount); }, @@ -174,17 +276,27 @@ $.widget("ui.multiselect", { return clone; }, _setSelected: function(item, selected) { - item.data('optionLink').attr('selected', selected); + var temp = item.data('optionLink').attr('selected', selected); + var parent = temp.parent(); + temp.detach().appendTo(parent); + this.element.trigger('change'); if (selected) { var selectedItem = this._cloneWithData(item); - item[this.options.hide](this.options.animated, function() { $(this).remove(); }); - selectedItem.appendTo(this.selectedList).hide()[this.options.show](this.options.animated); - + item[this.options.hide](this.options.animated, function() { + if (item.siblings().length === 0) { + item.closest("ul[title]").hide(); + } + $(this).remove(); + }); + // get group to add it to... + var $list = this._getOptionList(selectedItem.data("optionLink")[0]); + selectedItem.appendTo($list).hide()[this.options.show](this.options.animated); + this._applyItemState(selectedItem, true); return selectedItem; } else { - + // look for successor based on initial option index var items = this.availableList.find('li'), comparator = this.options.nodeComparator; var succ = null, i = item.data('idx'), direction = comparator(item, $(items[i])); @@ -196,18 +308,31 @@ $.widget("ui.multiselect", { if ( direction != comparator(item, $(items[i])) ) { // going up, go back one item down, otherwise leave as is succ = items[direction > 0 ? i : i+1]; + var group1 = item.closest("ul[title]"), + group2 = $(succ).closest("ul[title]"); + if (group1.length !== 0 && group2.length !== 0) { + if (group1.attr("title") !== group2.attr("title")) { + succ = null; + } + } break; } } } else { succ = items[i]; } - + var availableItem = this._cloneWithData(item); - succ ? availableItem.insertBefore($(succ)) : availableItem.appendTo(this.availableList); - item[this.options.hide](this.options.animated, function() { $(this).remove(); }); + var $list = this._getOptionList(availableItem.data("optionLink")[0]); + succ ? availableItem.insertBefore($(succ)) : availableItem.appendTo($list); + item[this.options.hide](this.options.animated, function() { + if (item.siblings().length === 0) { + item.closest("ul[title]").hide(); + } + $(this).remove(); + }); availableItem.hide()[this.options.show](this.options.animated); - + this._applyItemState(availableItem, false); return availableItem; } @@ -220,27 +345,27 @@ $.widget("ui.multiselect", { item.children('span').removeClass('ui-icon-arrowthick-2-n-s').addClass('ui-helper-hidden').removeClass('ui-icon'); item.find('a.action span').addClass('ui-icon-minus').removeClass('ui-icon-plus'); this._registerRemoveEvents(item.find('a.action')); - + } else { item.children('span').removeClass('ui-icon-arrowthick-2-n-s').addClass('ui-helper-hidden').removeClass('ui-icon'); item.find('a.action span').addClass('ui-icon-plus').removeClass('ui-icon-minus'); this._registerAddEvents(item.find('a.action')); } - + this._registerDoubleClickEvents(item); this._registerHoverEvents(item); }, // taken from John Resig's liveUpdate script _filter: function(list) { var input = $(this); - var rows = list.children('li'), + var rows = list.find('li'), cache = rows.map(function(){ - + return $(this).text().toLowerCase(); }); - + var term = $.trim(input.val().toLowerCase()), scores = []; - + if (!term) { rows.show(); } else { @@ -276,11 +401,16 @@ $.widget("ui.multiselect", { var item = that._setSelected($(this).parent(), true); that.count += 1; that._updateCount(); + + // Prevent extra clicks from triggering bogus add events, if a user + // tries clicking during the removal process. + $(this).unbind('click'); + return false; }); - + // make draggable - if (this.options.sortable) { + if (this.options.sortable && this.options.dragToAdd) { elements.each(function() { $(this).parent().draggable({ connectToSortable: that.selectedList, @@ -293,7 +423,7 @@ $.widget("ui.multiselect", { containment: that.container, revert: 'invalid' }); - }); + }); } }, _registerRemoveEvents: function(elements) { @@ -302,6 +432,11 @@ $.widget("ui.multiselect", { that._setSelected($(this).parent(), false); that.count -= 1; that._updateCount(); + + // Prevent extra clicks from triggering bogus remove events, if a + // user tries clicking during the removal process. + $(this).unbind('click'); + return false; }); }, @@ -315,15 +450,24 @@ $.widget("ui.multiselect", { $(this).removeClass('ui-state-active'); }) .keypress(function(e) { - if (e.keyCode == 13) + if (e.keyCode == 13) { + if (that.options.pressEnterKeyToAddAll) { + //on Enter, if a filter is present add all, then clear the input + var str = $('input.search').val(); + if (str !== undefined && str !== null && str !== "") { + $('a.add-all').click(); + $('input.search').val(""); + } + } return false; + } }) .keyup(function() { that._filter.apply(this, [that.availableList]); }); } }); - + $.extend($.ui.multiselect, { locale: { addAll:'Add all', -- GitLab