Index: search.php
===================================================================
--- search.php	(revision 544)
+++ search.php	(working copy)
@@ -65,9 +65,11 @@
   /** Set a singular parameter 
     @param name Parameter name
     @param value Parameter value
+    @param validate Optional parameter to validate the parameter. Default is
+    true
     @return True on success */
-  function setParam($name, $value) {
-    if ($this->validate($name, $value)) {
+  function setParam($name, $value, $validate = true) {
+    if ($validate === false || $this->validate($name, $value)) {
       $this->_data[$name] = $value;    
       return true;
     } else {
@@ -75,17 +77,23 @@
     }
   }
 
-  function addParam($name, $value) {
+  /** Add a parameter to an array.
+    @param name Parameter name. 
+    @param value Parameter value (which will be pluralized)
+    @param validate Optional parameter to validate the parameter. Default is
+    true
+    @note The name will be pluralized. */
+  function addParam($name, $value, $validate = true) {
     $name = Inflector::pluralize($name);
     if (is_array($value)) {
       foreach ($value as $v) {
-        $this->addParam($name, $v);
+        $this->addParam($name, $v, $validate);
       }
       return;
     }
     
     if ((!isset($this->_data[$name]) || !in_array($value, $this->_data[$name])) &&
-      $this->validate($name, $value)) {
+      ($validate === false || $this->validate($name, $value))) {
       $this->_data[$name][] = $value;
     }
   }
@@ -133,11 +141,15 @@
       case 'set':
         if (count($args) == 1) {
           return $this->setParam($name, $args[0]);
+        } elseif (count($args) == 2) {
+          return $this->setParam($name, $args[0], $args[1]);
         }
         break;
       case 'add':
         if (count($args) == 1) {
           return $this->addParam($name, $args[0]);
+        } elseif (count($args) == 2) {
+          return $this->addParam($name, $args[0], $args[1]);
         }
         break;
       case 'del':
Index: views/explorer/quicksearch.ctp
===================================================================
--- views/explorer/quicksearch.ctp	(revision 547)
+++ views/explorer/quicksearch.ctp	(working copy)
@@ -1,88 +1,62 @@
 <h1>Quick Search Results</h1>
 <?php $session->flash(); ?>
 
-<div class="minis">
-<script type="text/javascript">
-  var mediaData = [];
-</script>
 
 <?php
 $search->initialize();
 $cell=0;
 
-if (count($dataTags) + count($dataCategories) + count($dataLocations) == 0): ?>
+if (!count($this->data)): ?>
 <div class="info">
 <?php printf(__("Sorry, nothing was found for %s", true), h($quicksearch)); ?>
 </div>
-<?php endif; ?>
+<?php else: ?>
 
-<?php // -- Output for Tags --
-if (count($dataTags) > 0) : ?>
-<h2>Results for Tags:</h2>
-<div align="left"> 
-<?php 
-  foreach($dataTags as $media) {
-    echo $imageData->mediaLink($media, 'mini').' ';
-  }
-?>
-</div>
+<h2><?php printf(__('Results of %s', true), h($quicksearch)); ?></h2>
+<div class="minis" align="left">
+<script type="text/javascript">
+  var mediaData = [];
+</script>
 
-<?php
-  echo 'See more results with tag: ';
-  $names = Set::extract('/Tag/name', $dataTags);
-  $names = array_unique($names);
-  $links = array();
-  foreach ($names as $name) {
-    $links[] = $html->link($name, '/explorer/tag/'.$name);
-  }
-  echo implode(', ', $links);
-?>
-<?php endif; /* if (count($dataTags) > 0) */ ?>  
-
-<?php // -- Output for Categories --
-if (count($dataCategories) > 0) : ?>
-<h2>Results for Categories:</h2>
-<div align="left"> 
 <?php 
-  foreach($dataCategories as $media) {
+  foreach($this->data as $media) {
     echo $imageData->mediaLink($media, 'mini').' ';
   }
 ?>
 </div>
 
 <?php
-  echo 'See more results with category: ';
-  $names = Set::extract('/Category/name', $dataCategories);
-  $names = array_unique($names);
-  $links = array();
-  foreach ($names as $name) {
-    $links[] = $html->link($name, '/explorer/category/'.$name);
+  $tags = Set::extract('/Tag/name', $this->data);
+  if (count($tags)) {
+    echo '<p>' . __('See more results of tag', true) .  ': ';
+    $tags = array_unique($tags);
+    $links = array();
+    foreach ($tags as $name) {
+      $links[] = $html->link($name, '/explorer/tag/'.$name);
+    }
+    echo implode(', ', $links) . '</p>';
   }
-  echo implode(', ', $links);
-?>
-<?php endif; /* if (count($dataCategories) > 0) */ ?>  
 
-<?php // -- Output for Locations --
-if (count($dataLocations) > 0) : ?>
-<h2>Results for Locations:</h2>
-<div align="left"> 
-<?php 
-  foreach($dataLocations as $media) {
-    echo $imageData->mediaLink($media, 'mini').' ';
+  $categories = Set::extract('/Category/name', $this->data);
+  if (count($categories)) {
+    echo '<p>' . __('See more results of category', true) .  ': ';
+    $categories = array_unique($categories);
+    $links = array();
+    foreach ($categories as $name) {
+      $links[] = $html->link($name, '/explorer/category/'.$name);
+    }
+    echo implode(', ', $links) . '</p>';
   }
-?>
-</div>
 
-<?php
-  echo 'See more results with location: ';
-  $names = Set::extract('/Location/name', $dataLocations);
-  $names = array_unique($names);
-  $links = array();
-  foreach ($names as $name) {
-    $links[] = $html->link($name, '/explorer/location/'.$name);
+  $locations = Set::extract('/Location/name', $this->data);
+  if (count($locations)) {
+    echo '<p>' . __('See more results of location', true) .  ': ';
+    $locations = array_unique($locations);
+    $links = array();
+    foreach ($locations as $name) {
+      $links[] = $html->link($name, '/explorer/location/'.$name);
+    }
+    echo implode(', ', $links) . '</p>';
   }
-  echo implode(', ', $links);
 ?>
-<?php endif; /* if (count($dataLocations) > 0) */ ?>  
-
-</div>
+<?php endif; ?>
Index: views/elements/topnav.ctp
===================================================================
--- views/elements/topnav.ctp	(revision 544)
+++ views/elements/topnav.ctp	(working copy)
@@ -18,7 +18,7 @@
   }
 
   echo "\n<div class=\"searchBox\" ><div class=\"searchBoxSub\" >";
-  echo $form->create(null, array('url' => array('controller' => 'explorer', 'action' => 'quicksearch'))); 
+  echo $form->create(null, array('url' => '/explorer/quicksearch')); 
   echo $form->input('Media.quicksearch', array ('label' => false, 'div' => false));
   $icon = Router::url("/img/icons/zoom.png");
   echo "<input type=\"image\" src=\"$icon\" width=\"16\" height=\"16\" id=\"go\" alt=\"Search\" title=\"Search\" />";
Index: tests/cases/components/search.test.php
===================================================================
--- tests/cases/components/search.test.php	(revision 544)
+++ tests/cases/components/search.test.php	(working copy)
@@ -1,6 +1,7 @@
 <?php
 App::import('Core', array('Controller'));
 App::import('Component', array('Search'));
+App::import('File', 'Logger', array('file' => APP.'logger.php'));
 
 Mock::generatePartial('SearchComponent', 'NoStopSearch', array('_stop'));
 
@@ -81,11 +82,22 @@
     $result = $this->Search->getShow();
     $this->assertEqual($result, 12);
 
+    // one rule, disabled validation
+    $this->Search->setShow('no validation', false);
+    $result = $this->Search->getShow();
+    $this->assertEqual($result, 'no validation');
+
     // multple rules
     $this->Search->addTag(array('he', 'the'));
     $result = $this->Search->getTags();
     $this->assertEqual($result, array('the'));
 
+    // multple rules, disabled validation
+    $result = $this->Search->delTags();
+    $this->Search->addTag(array('he', '+_?'), false);
+    $result = $this->Search->getTags();
+    $this->assertEqual($result, array('he', '+_?'));
+
     // disabled parameter
     $this->Search->setUser('joe');
     $result = $this->Search->getUser();
@@ -100,6 +112,11 @@
     $this->Search->setWorld("rule it");
     $result = $this->Search->getWorld();
     $this->assertEqual($result, null);
+    
+    // disabled parameter with disabled validation
+    $this->Search->setWorld("rule it", false);
+    $result = $this->Search->getWorld();
+    $this->assertEqual($result, "rule it");
   }
 
 }
Index: config/sql/schema.php
===================================================================
--- config/sql/schema.php	(revision 544)
+++ config/sql/schema.php	(working copy)
@@ -35,12 +35,12 @@
 	var $categories = array(
 			'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
 			'name' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 64, 'key' => 'index'),
-			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'name' => array('column' => 'name', 'unique' => 0))
+			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'name_index' => array('column' => 'name', 'unique' => 0))
 		);
 	var $categories_media = array(
 			'media_id' => array('type'=>'integer', 'null' => false, 'default' => '0'),
 			'category_id' => array('type'=>'integer', 'null' => false, 'default' => '0'),
-			'indexes' => array('PRIMARY' => array('column' => array('media_id', 'category_id'), 'unique' => 1), 'setid' => array('column' => 'category_id', 'unique' => 0))
+			'indexes' => array('PRIMARY' => array('column' => array('media_id', 'category_id'), 'unique' => 1), 'category_id_index' => array('column' => 'category_id', 'unique' => 0), 'media_id_index' => array('column' => 'media_id', 'unique' => 0))
 		);
 	var $comments = array(
 			'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
@@ -70,18 +70,18 @@
 			'size' => array('type'=>'integer', 'null' => false),
 			'time' => array('type'=>'datetime', 'null' => false),
 			'readed' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
-			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'id' => array('column' => 'id', 'unique' => 0), 'user_id' => array('column' => 'user_id', 'unique' => 0), 'media_id' => array('column' => 'media_id', 'unique' => 0))
+			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'user_id_index' => array('column' => 'user_id', 'unique' => 0), 'media_id_index' => array('column' => 'media_id', 'unique' => 0))
 		);
 	var $groups = array(
 			'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
 			'user_id' => array('type'=>'integer', 'null' => true, 'default' => NULL),
 			'name' => array('type'=>'string', 'null' => false, 'length' => 32),
-			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'id' => array('column' => 'id', 'unique' => 0))
+			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1))
 		);
 	var $groups_users = array(
 			'user_id' => array('type'=>'integer', 'null' => false, 'default' => '0'),
 			'group_id' => array('type'=>'integer', 'null' => false, 'default' => '0'),
-			'indexes' => array('PRIMARY' => array('column' => array('user_id', 'group_id'), 'unique' => 1), 'userid' => array('column' => 'user_id', 'unique' => 0), 'groupid' => array('column' => 'group_id', 'unique' => 0))
+			'indexes' => array('PRIMARY' => array('column' => array('user_id', 'group_id'), 'unique' => 1), 'user_id_index' => array('column' => 'user_id', 'unique' => 0), 'group_id_index' => array('column' => 'group_id', 'unique' => 0))
 		);
 	var $locks = array(
 			'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
@@ -94,18 +94,18 @@
 			'exclusivelock' => array('type'=>'integer', 'null' => false, 'default' => '0'),
 			'created' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
 			'modified' => array('type'=>'datetime', 'null' => true, 'default' => NULL),
-			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'token' => array('column' => 'token', 'unique' => 0), 'expires' => array('column' => 'expires', 'unique' => 0))
+			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'token_index' => array('column' => 'token', 'unique' => 0), 'expires_index' => array('column' => 'expires', 'unique' => 0))
 		);
 	var $locations = array(
 			'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
 			'name' => array('type'=>'string', 'null' => false, 'length' => 64, 'key' => 'index'),
 			'type' => array('type'=>'integer', 'null' => true, 'default' => NULL, 'length' => 3),
-			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'name' => array('column' => 'name', 'unique' => 0))
+			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'name_index' => array('column' => 'name', 'unique' => 0))
 		);
 	var $locations_media = array(
 			'location_id' => array('type'=>'integer', 'null' => false, 'default' => '0'),
 			'media_id' => array('type'=>'integer', 'null' => false, 'default' => '0'),
-			'indexes' => array('PRIMARY' => array('column' => array('media_id', 'location_id'), 'unique' => 1), 'locationid' => array('column' => 'location_id', 'unique' => 0))
+			'indexes' => array('PRIMARY' => array('column' => array('media_id', 'location_id'), 'unique' => 1), 'location_id_index' => array('column' => 'location_id', 'unique' => 0), 'media_id_index' => array('column' => 'media_id', 'unique' => 0))
 		);
 	var $media = array(
 			'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
@@ -136,19 +136,19 @@
 			'ranking' => array('type'=>'float', 'null' => true, 'default' => '0', 'key' => 'index'),
 			'voting' => array('type'=>'float', 'null' => true, 'default' => '0'),
 			'votes' => array('type'=>'integer', 'null' => true, 'default' => '0'),
-			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'date' => array('column' => 'date', 'unique' => 0), 'ranking' => array('column' => 'ranking', 'unique' => 0), 'id' => array('column' => 'id', 'unique' => 0), 'oacl' => array('column' => 'oacl', 'unique' => 0))
+			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'date_index' => array('column' => 'date', 'unique' => 0), 'ranking_index' => array('column' => 'ranking', 'unique' => 0), 'oacl_index' => array('column' => 'oacl', 'unique' => 0))
 		);
 	var $media_tags = array(
 			'media_id' => array('type'=>'integer', 'null' => false, 'default' => '0'),
 			'tag_id' => array('type'=>'integer', 'null' => false, 'default' => '0'),
-			'indexes' => array('PRIMARY' => array('column' => array('media_id', 'tag_id'), 'unique' => 1), 'imageid' => array('column' => 'media_id', 'unique' => 0), 'tagid' => array('column' => 'tag_id', 'unique' => 0))
+			'indexes' => array('PRIMARY' => array('column' => array('media_id', 'tag_id'), 'unique' => 1), 'media_id_index' => array('column' => 'media_id', 'unique' => 0), 'tag_id_index' => array('column' => 'tag_id', 'unique' => 0))
 		);
 	var $options = array(
 			'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
 			'user_id' => array('type'=>'integer', 'null' => true, 'default' => NULL),
 			'name' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 64),
 			'value' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 192),
-			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'user_id' => array('column' => 'user_id', 'unique' => 0))
+			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'user_id_index' => array('column' => 'user_id', 'unique' => 0))
 		);
 	var $properties = array(
 			'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
@@ -161,7 +161,7 @@
 	var $tags = array(
 			'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
 			'name' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 64, 'key' => 'index'),
-			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'name' => array('column' => 'name', 'unique' => 0))
+			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'name_index' => array('column' => 'name', 'unique' => 0))
 		);
 	var $users = array(
 			'id' => array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
@@ -177,7 +177,7 @@
 			'firstname' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 32),
 			'lastname' => array('type'=>'string', 'null' => false, 'length' => 32),
 			'email' => array('type'=>'string', 'null' => true, 'default' => NULL, 'length' => 64),
-			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'id' => array('column' => 'id', 'unique' => 0))
+			'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1))
 		);
 }
 ?>
Index: controllers/components/search.php
===================================================================
--- controllers/components/search.php	(revision 544)
+++ controllers/components/search.php	(working copy)
@@ -337,5 +337,25 @@
  
     return $data;
   }
+
+  function quicksearch($text, $show = 12) {
+    $words = preg_split('/\s+/', trim($text));
+
+    $tmp = array();
+    foreach($words as $word) {
+      $tmp[] = '*' . $word . '*';
+    }
+    $words = $tmp;
+
+    $this->addTags($words, false);
+    $this->addCategories($words, false);
+    $this->addLocations($words, false);
+    $this->setOperand('OR');
+
+    $this->setSort('default', false);
+    $this->setShow($show);
+
+    return $this->paginate();
+  }    
 }
 ?>
Index: controllers/components/upgrade_schema.php
===================================================================
--- controllers/components/upgrade_schema.php	(revision 544)
+++ controllers/components/upgrade_schema.php	(working copy)
@@ -191,10 +191,7 @@
         $modelName = $this->modelMapping[$table];
       }
       if (!in_array($modelName, $models)) {
-        Logger::err("Model '$modelName' does not exists");
-        $columns[$table] = "Model '$modelName' does not exists";
-        trigger_error(sprintf(__("Model '%s' does not exists", true), $modelName), E_USER_WARNING);
-        continue;
+        Logger::warn("Model '$modelName' does not exists");
       }
 
       $columns[$table] = $this->db->alterSchema(array($table => $changes), $table);
Index: controllers/components/query_builder.php
===================================================================
--- controllers/components/query_builder.php	(revision 544)
+++ controllers/components/query_builder.php	(working copy)
@@ -240,7 +240,7 @@
           $counts = array();
           $conditions = array();
           foreach ($query['_counts'] as $count) {
-            $counts[] = "( $count + 1 )";
+            $counts[] = "( COALESCE($count, 0) + 1 )";
             $conditions[] = "COALESCE($count, 0)";
           }
           $query['conditions'][] = '( '.implode(' + ', $conditions).' ) > 0';
@@ -299,8 +299,21 @@
     $habtm = Inflector::singularize($name);
 
     $field = Inflector::camelize($habtm).'.name';
-    $query['conditions'][] = $this->_buildCondition($field, $value);
 
+    $tags = array();
+    foreach($value as $v) {
+      if (preg_match('/[*\?]/', $v)) {
+        $v = preg_replace('/\*/', '%', $v);
+        $v = preg_replace('/\?/', '_', $v);
+        $query['conditions'][] = $this->_buildCondition($field, $v, array('operand' => 'LIKE'));
+      } else {
+        $tags[] = $v;
+      }
+    }
+    if (count($tags)) {
+      $query['conditions'][] = $this->_buildCondition($field, $tags);
+    }
+
     $fieldCount = Inflector::camelize($habtm).'Count';
     $query['_counts'][] = $fieldCount;
 
Index: controllers/explorer_controller.php
===================================================================
--- controllers/explorer_controller.php	(revision 547)
+++ controllers/explorer_controller.php	(working copy)
@@ -70,41 +70,7 @@
     } 
 
     if ($quicksearch) {
-      // Perform queries for each Tags, Categories and Locations
-      // seperately:
-
-      // Split the query so that we have a list of tags/categories/locations.
-      // For now we split at whitespaces, improvements could be made to not
-      // split multiple tags/categories/locations enclosed in quotation marks
-      $words = preg_split('/\s+/', trim($quicksearch));
-
-      // Reduce results to 6 
-      $this->Search->setShow(6);
-
-      // Add tag to the query
-      $this->Search->addTags($words);
-      // Set variable dataTags for view
-      $this->Search->setTagOp('OR');
-      $this->set('dataTags', $this->Search->paginate());
-      // Reset query
-      $this->Search->delTags();
-      $this->Search->delTagOp();
-
-      $this->Search->addCategories($words);
-      $this->Search->setCategoryOp('OR');
-      $this->set('dataCategories', $this->Search->paginate());
-      $this->Search->delCategories();
-      $this->Search->delCategoryOp();
-
-      $this->Search->addLocations($words);
-      $this->Search->setLocationOp('OR');
-      $this->set('dataLocations', $this->Search->paginate());
-      $this->Search->delLocations();
-      $this->Search->delLocationOp();
-    } else {
-      $this->set('dataTags', array());
-      $this->set('dataCategories', array());
-      $this->set('dataLocations', array());
+      $this->data = $this->Search->quicksearch($quicksearch, 6);
     }
     $this->set('quicksearch', $quicksearch);
   }

