diff --git a/etemplate/inc/class.so_sql.inc.php b/etemplate/inc/class.so_sql.inc.php
index caee32d4b8..b7b0c15289 100644
--- a/etemplate/inc/class.so_sql.inc.php
+++ b/etemplate/inc/class.so_sql.inc.php
@@ -23,7 +23,7 @@
  * @package etemplate
  * @subpackage contrib
  * @author RalfBecker-AT-outdoor-training.de
- * @license GPL
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
  */
 class so_sql
 {
@@ -155,6 +155,8 @@ class so_sql
 	 */
 	function data_merge($new)
 	{
+		if ($this->debug) echo "<p>so_sql::data_merge(".print_r($new).")</p>\n";
+
 		if (!is_array($new) || !count($new))
 		{
 			return;
@@ -173,6 +175,7 @@ class so_sql
 				$this->data[$col] = $new[$col];
 			}
 		}
+		if ($this->debug) _debug_array($this->data);
 	}
 
 	/**
@@ -307,13 +310,15 @@ class so_sql
 
 		$this->data2db();
 
+		//echo "so_sql::save(".print_r($keys,true).") autoinc_id='$this->autoinc_id', data="; _debug_array($this->data);
+
 		if ($this->autoinc_id && !$this->data[$this->db_key_cols[$this->autoinc_id]])	// insert
 		{
 			foreach($this->db_cols as $db_col => $col)
 			{
 				if (!$this->autoinc_id || $db_col != $this->autoinc_id)	// not write auto-inc-id
 				{
-					$data[$db_col] = $this->data[$col] == '' && $this->empty_on_write == 'NULL' ? null : $this->data[$col];
+					$data[$db_col] = (string) $this->data[$col] === '' && $this->empty_on_write == 'NULL' ? null : $this->data[$col];
 				}
 			}
 			$this->db->insert($this->table_name,$data,false,__LINE__,__FILE__);
@@ -327,18 +332,25 @@ class so_sql
 		{
 			foreach($this->db_data_cols as $db_col => $col)
 			{
-				$data[$db_col] = $this->data[$col] == '' && $this->empty_on_write == 'NULL' ? null : $this->data[$col];
+				$data[$db_col] = (string) $this->data[$col] === '' && $this->empty_on_write == 'NULL' ? null : $this->data[$col];
 			}
 			$keys = '';
 			foreach($this->db_key_cols as $db_col => $col)
 			{
 				$keys[$db_col] = $this->data[$col];
 			}
-			$this->db->update($this->table_name,$data,$keys,__LINE__,__FILE__);
+			if (!$this->autoinc_id)	// always try an insert if we have no autoinc_id, as we dont know if the data exists
+			{
+				$this->db->insert($this->table_name,$data,$keys,__LINE__,__FILE__);
+			}
+			else
+			{
+				$this->db->update($this->table_name,$data,$keys,__LINE__,__FILE__);
+			}
 		}
 		$this->db2data();
 
-		return $this->db->errno;
+		return $this->db->Errno;
 	}
 
 	/**
@@ -390,9 +402,11 @@ class so_sql
 	 * @param string $op defaults to 'AND', can be set to 'OR' too, then criteria's are OR'ed together
 	 * @param int/boolean $start if != false, return only maxmatch rows begining with start
 	 * @param array $filter if set (!=null) col-data pairs, to be and-ed (!) into the query without wildcards
+	 * @param string $join='' sql to do a join, added as is after the table-name, eg. ", table2 WHERE x=y" or 
+	 *	"LEFT JOIN table2 ON (x=y)", Note: there's no quoting done on $join!
 	 * @return array of matching rows (the row is an array of the cols) or False
 	 */
-	function search($criteria,$only_keys=True,$order_by='',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null)
+	function search($criteria,$only_keys=True,$order_by='',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null,$join='')
 	{
 		if (!is_array($criteria))
 		{
@@ -403,17 +417,23 @@ class so_sql
 			$criteria = $this->data2db($criteria);
 
 			foreach($this->db_cols as $db_col => $col)
-			{	//echo "testing col='$col', criteria[$col]='".$criteria[$col]."'<br>";
+			{	
+				//echo "testing col='$col', criteria[$col]='".print_r($criteria[$col],true)."'<br>";
 				if (isset($filter[$col]) && $filter[$col]) continue;	// added later
 
 				if (isset($criteria[$col]) && ($empty || $criteria[$col] != ''))
 				{
-					$query .= ($query ? " $op " : '') . $db_col .
-						($wildcard || strstr($criteria[$col],'*') || strstr($criteria[$col],'?') ?
-						' LIKE '.$this->db->quote($wildcard.strtr(str_replace('_','\\_',$criteria[$col]),'*?','%_').$wildcard) :
-						"=".$this->db->quote($criteria[$col]));
+					if ($wildcard || strstr($criteria[$col],'*') || strstr($criteria[$col],'?'))
+					{
+						$query[] = $db_col.' LIKE '.$this->db->quote($wildcard.str_replace(array('%','_','*','?'),array('\\%','\\_','%','_'),$criteria[$col]).$wildcard);
+					}
+					else
+					{
+						$query[$db_col] = $criteria[$col];
+					}
 				}
 			}
+			if (is_array($query) && $op != 'AND') $query = $this->db->column_data_implode(' '.$op.' ',$query);
 		}
 		if (is_array($filter))
 		{
@@ -428,28 +448,49 @@ class so_sql
 			{
 				if ($val !== '') $db_filter[array_search($col,$this->db_cols)] = $val;
 			}
-			if ($query) $db_filter[] = '('.$query.')';
+			if ($query) 
+			{
+				if ($op != 'AND')
+				{
+					$db_filter[] = '('.$this->db->column_data_implode(' '.$op.' ',$query).')';
+				}
+				else 
+				{
+					$db_filter = array_merge($db_filter,$query);
+				}
+			}
 			$query = $db_filter;
 		}
 		if ($start !== false)	// need to get the total too, saved in $this->total
 		{
-			$this->db->select($this->table_name,'COUNT(*)',$query,__LINE__,__FILE__);
+			$this->db->select($this->table_name,'COUNT(*)',$query,__LINE__,__FILE__,0,'',false,0,$join);
 			$this->total = $this->db->next_record() ? (int) $this->db->f(0) : false;
 		}
-		$cols = $only_keys === true ? $this->db_key_cols : (!$only_keys ? $this->db_cols : explode(',',str_replace('DISTINCT ','',$only_keys)));
-
 		$this->db->select($this->table_name,($only_keys === true ? implode(',',$this->db_key_cols) : (!$only_keys ? '*' : $only_keys)).
 			($extra_cols ? ','.(is_array($extra_cols) ? implode(',',$extra_cols) : $extra_cols) : ''),
-			$query,__LINE__,__FILE__,$start,$order_by ? 'ORDER BY '.$order_by : '');
+			$query,__LINE__,__FILE__,$start,$order_by ? 'ORDER BY '.$order_by : '',false,0,$join);
 
 		if ($this->debug)
 		{
 			echo "<p>so_sql::search(,only_keys=$only_keys,order_by='$order_by',wildcard='$wildcard',empty=$empty,$op,start='$start',".print_r($filter,true).") query=".print_r($query,true).", total='$this->total'</p>\n";
 			echo "<br>criteria = "; _debug_array($criteria);
 		}
-		$arr = array();
-		$cols = $only_keys === true ? $this->db_key_cols : (!$only_keys ? $this->db_cols : explode(',',str_replace('DISTINCT ','',$only_keys)));
-		if ($extra_cols)
+		if ($only_keys == true)	// only primary key
+		{
+			$cols = $this->db_key_cols;
+		}
+		elseif (!$only_keys)	// all columns
+		{
+			$cols = $this->db_cols;
+		}
+		else	// only the specified columns
+		{
+			foreach(explode(',',str_replace('DISTINCT ','',$only_keys)) as $col)
+			{
+				$cols[$col] = $col;
+			}
+		}
+		if ($extra_cols)	// extra columns to report
 		{
 			foreach(is_array($extra_cols) ? $extra_cols : array($extra_cols) as $col)
 			{
@@ -457,14 +498,15 @@ class so_sql
 				$cols[$col] = $col;
 			}
 		}
-		for ($n = 0; $this->db->next_record(); ++$n)
+		$arr = array();
+		for ($n = 0; ($row = $this->db->row(true)); ++$n)
 		{
-			$row = array();
+			$data = array();
 			foreach($cols as $db_col => $col)
 			{
-				$row[$col] = $this->db->f($db_col);
+				$data[$col] = $row[$db_col];
 			}
-			$arr[] = $this->db2data($row);
+			$arr[] = $this->db2data($data);
 		}
 		return $n ? $arr : False;
 	}
@@ -516,7 +558,7 @@ class so_sql
 			{
 				foreach($this->db_key_cols as $db_key_col => $key_col)
 				{
-					if ($data[$key_col] != $other[$key_col]) 
+					if ($data[$key_col] != $other[$key_col])
 					{
 						if ($this->debug)
 						{