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">&nbsp;</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">&nbsp;</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