From 6d318cfab11073af7da193e259aa1f839382aeac Mon Sep 17 00:00:00 2001
From: Dominik Hebeler <dominik@suma-ev.de>
Date: Mon, 11 Feb 2019 15:02:14 +0100
Subject: [PATCH] Added Parameter Filter

---
 app/MetaGer.php                               | 80 +++++++++++++-----
 app/Models/Searchengine.php                   | 20 ++++-
 app/Models/parserSkripte/Ebay.php             | 24 +++---
 app/Models/parserSkripte/Minisucher.php       | 34 ++++----
 app/Models/parserSkripte/Mnogosearch.php      | 10 +--
 app/Models/parserSkripte/Nebel.php            | 12 +--
 app/Models/parserSkripte/Witch.php            | 22 ++---
 app/Models/parserSkripte/Yacy.php             | 20 ++---
 config/.gitignore                             |  1 +
 resources/lang/de/index.php                   | 83 ++++++++++---------
 resources/lang/de/metaGer.php                 | 57 +++++++++++++
 .../metager/pages/resultpage/result-page.less | 60 +++++++++++++-
 .../views/layouts/researchandtabs.blade.php   | 29 +++++++
 resources/views/parts/foki.blade.php          | 41 +++------
 14 files changed, 332 insertions(+), 161 deletions(-)

diff --git a/app/MetaGer.php b/app/MetaGer.php
index 899ccc842..6cabdd2eb 100644
--- a/app/MetaGer.php
+++ b/app/MetaGer.php
@@ -29,6 +29,8 @@ class MetaGer
     protected $phrases = [];
     protected $engines = [];
     protected $results = [];
+    protected $queryFilter = [];
+    protected $parameterFilter = [];
     protected $ads = [];
     protected $warnings = [];
     protected $errors = [];
@@ -528,7 +530,7 @@ class MetaGer
             return;
         }
 
-        $enabledSearchengines = [];
+        $this->enabledSearchengines = [];
         $overtureEnabled = false;
 
         # Check if selected focus is valid
@@ -546,22 +548,31 @@ class MetaGer
             # Check if this engine can use eventually defined query-filter
             $valid = true;
             foreach ($this->queryFilter as $queryFilter => $filter) {
-                if (empty($this->sumaFile->filter->$queryFilter->sumas->$suma)) {
+                if (empty($this->sumaFile->filter->{"query-filter"}->$queryFilter->sumas->$suma)) {
                     $valid = false;
                     break;
                 }
             }
+            # Check if this engine can use eventually defined parameter-filter
+            if ($valid) {
+                foreach ($this->parameterFilter as $filterName => $filter) {
+                    if (empty($filter->sumas->$suma)) {
+                        $valid = false;
+                        break;
+                    }
+                }
+            }
             # If it can we add it
             if ($valid) {
-                $enabledSearchengines[$suma] = $this->sumaFile->sumas->{$suma};
+                $this->enabledSearchengines[$suma] = $this->sumaFile->sumas->{$suma};
             }
 
         }
 
-        if (sizeof($enabledSearchengines) === 0) {
+        if (sizeof($this->enabledSearchengines) === 0) {
             $filter = "";
             foreach ($this->queryFilter as $queryFilter => $filterPhrase) {
-                $filter .= trans($this->sumaFile->filter->{$queryFilter}->name) . ",";
+                $filter .= trans($this->sumaFile->filter->{"query-filter"}->{$queryFilter}->name) . ",";
             }
             $filter = rtrim($filter, ",");
             $error = trans('metaGer.engines.noSpecialSearch', ['fokus' => trans($this->sumaFile->foki->{$this->fokus}->{"display-name"}),
@@ -570,7 +581,6 @@ class MetaGer
         }
 
         $engines = [];
-
         $typeslist = [];
         $counter = 0;
 
@@ -581,8 +591,9 @@ class MetaGer
                 $engine->setResultHash($this->getHashCode());
             }
         } else {
-            $engines = $this->actuallyCreateSearchEngines($enabledSearchengines);
+            $engines = $this->actuallyCreateSearchEngines($this->enabledSearchengines);
         }
+
         # Wir starten alle Suchen
         foreach ($engines as $engine) {
             $engine->startSearch($this);
@@ -661,6 +672,24 @@ class MetaGer
         return $engines;
     }
 
+    public function getAvailableParameterFilter()
+    {
+        $parameterFilter = $this->sumaFile->filter->{"parameter-filter"};
+
+        $availableFilter = [];
+
+        foreach ($parameterFilter as $filterName => $filter) {
+            # Check if any of the enabled search engines provide this filter
+            foreach ($this->enabledSearchengines as $engineName => $engine) {
+                if (!empty($filter->sumas->$engineName)) {
+                    $availableFilter[$filterName] = $filter;
+                }
+            }
+        }
+
+        return $availableFilter;
+    }
+
     public function isBildersuche()
     {
         return $this->fokus === "bilder";
@@ -843,13 +872,6 @@ class MetaGer
             }
         }
 
-        # Nicht fertige Engines verwefen
-        foreach ($engines as $engine) {
-            if (!$engine->loaded) {
-                $engine->shutdown();
-            }
-        }
-
         $this->engines = $engines;
     }
 
@@ -1006,10 +1028,7 @@ class MetaGer
         $this->searchCheckPhrase();
 
         # Check for query-filter (i.e. Sitesearch, etc.):
-        foreach ($this->sumaFile->filter as $filterName => $filter) {
-            if ($filter->type !== "query-filter") {
-                continue;
-            }
+        foreach ($this->sumaFile->filter->{"query-filter"} as $filterName => $filter) {
             if (!empty($filter->{"optional-parameter"}) && $request->filled($filter->{"optional-parameter"})) {
                 $this->queryFilter[$filterName] = $request->input($filter->{"optional-parameter"});
             } else if (preg_match_all("/" . $filter->regex . "/si", $this->q, $matches) > 0) {
@@ -1027,6 +1046,19 @@ class MetaGer
             }
 
         }
+        # Check for parameter-filter (i.e. SafeSearch)
+        $this->parameterFilter = [];
+        $usedParameters = [];
+        foreach ($this->sumaFile->filter->{"parameter-filter"} as $filterName => $filter) {
+            if (!empty($usedParameters[$filter->{"get-parameter"}])) {
+                die("Der Get-Parameter \"" . $filter->{"get-parameter"} . "\" wird mehrfach verwendet!");
+            } else {
+                $usedParameters[$filter->{"get-parameter"}] = true;
+            }
+            if ($request->filled($filter->{"get-parameter"})) {
+                $this->parameterFilter[$filterName] = $filter;
+            }
+        }
         $this->searchCheckHostBlacklist($request);
         $this->searchCheckDomainBlacklist($request);
         $this->searchCheckUrlBlacklist();
@@ -1336,7 +1368,12 @@ class MetaGer
 
     public function generateSearchLink($fokus, $results = true)
     {
-        $requestData = $this->request->except(['page', 'next']);
+        $except = ['page', 'next'];
+        # Remove every Filter
+        foreach ($this->sumaFile->filter->{"parameter-filter"} as $filterName => $filter) {
+            $except[] = $filter->{"get-parameter"};
+        }
+        $requestData = $this->request->except($except);
         $requestData['focus'] = $fokus;
         $requestData['out'] = "";
 
@@ -1509,6 +1546,11 @@ class MetaGer
         return $this->queryFilter;
     }
 
+    public function getParameterFilter()
+    {
+        return $this->parameterFilter;
+    }
+
     public function getTime()
     {
         return $this->time;
diff --git a/app/Models/Searchengine.php b/app/Models/Searchengine.php
index 239e0ba67..b8a21b5a5 100644
--- a/app/Models/Searchengine.php
+++ b/app/Models/Searchengine.php
@@ -7,6 +7,7 @@ use App\MetaGer;
 use Cache;
 use Illuminate\Foundation\Bus\DispatchesJobs;
 use Illuminate\Support\Facades\Redis;
+use Request;
 
 abstract class Searchengine
 {
@@ -46,9 +47,11 @@ abstract class Searchengine
         $this->name = $name;
 
         # Cache Standarddauer 60
-        if (!isset($this->cacheDuration)) {
-            $this->cacheDuration = 60;
+        $this->cacheDuration = 60;
+        if (!empty($engine->{"cache-duration"}) && $engine->{"cache-duration"} >= 0) {
+            $this->cacheDuration = $engine->{"cache-duration"};
         }
+
         $this->useragent = $metager->getUserAgent();
         $this->ip = $metager->getIp();
         $this->startTime = microtime();
@@ -64,12 +67,23 @@ abstract class Searchengine
         $q = $metager->getQ();
         $filters = $metager->getSumaFile()->filter;
         foreach ($metager->getQueryFilter() as $queryFilter => $filter) {
-            $filterOptions = $filters->$queryFilter;
+            $filterOptions = $filters->{"query-filter"}->$queryFilter;
             $filterOptionsEngine = $filterOptions->sumas->{$this->name};
             $query = $filterOptionsEngine->prefix . $filter . $filterOptionsEngine->suffix;
             $q = $query . " " . $q;
         }
 
+        # Parse enabled Parameter-Filter
+        foreach ($metager->getParameterFilter() as $filterName => $filter) {
+            $inputParameter = Request::input($filter->{"get-parameter"}, "");
+            if (empty($inputParameter) || empty($filter->sumas->{$name}->values->{$inputParameter})) {
+                continue;
+            }
+            $engineParameterKey = $filter->sumas->{$name}->{"get-parameter"};
+            $engineParameterValue = $filter->sumas->{$name}->values->{$inputParameter};
+            $this->engine->{"get-parameter"}->{$engineParameterKey} = $engineParameterValue;
+        }
+
         $this->getString = $this->generateGetString($q);
         $this->hash = md5($this->engine->host . $this->getString . $this->engine->port . $this->name);
         $this->resultHash = $metager->getHashCode();
diff --git a/app/Models/parserSkripte/Ebay.php b/app/Models/parserSkripte/Ebay.php
index e4766cc21..b9ea1351f 100644
--- a/app/Models/parserSkripte/Ebay.php
+++ b/app/Models/parserSkripte/Ebay.php
@@ -9,9 +9,9 @@ class Ebay extends Searchengine
 {
     public $results = [];
 
-    public function __construct(\SimpleXMLElement $engine, \App\MetaGer $metager)
+    public function __construct($name, \stdClass $engine, \App\MetaGer $metager)
     {
-        parent::__construct($engine, $metager);
+        parent::__construct($name, $engine, $metager);
     }
 
     public function loadResults($result)
@@ -24,15 +24,15 @@ class Ebay extends Searchengine
             }
 
             $results = $content->{"searchResult"};
-            $count   = 0;
+            $count = 0;
             foreach ($results->{"item"} as $result) {
-                $title       = $result->{"title"}->__toString();
-                $link        = $result->{"viewItemURL"}->__toString();
+                $title = $result->{"title"}->__toString();
+                $link = $result->{"viewItemURL"}->__toString();
                 $anzeigeLink = $link;
-                $time        = $result->{"listingInfo"}->{"endTime"}->__toString();
-                $time        = date(DATE_RFC2822, strtotime($time));
-                $price       = intval($result->{"sellingStatus"}->{"convertedCurrentPrice"}->__toString()) * 100;
-                $descr       = "Preis: " . $result->{"sellingStatus"}->{"convertedCurrentPrice"}->__toString() . " €";
+                $time = $result->{"listingInfo"}->{"endTime"}->__toString();
+                $time = date(DATE_RFC2822, strtotime($time));
+                $price = intval($result->{"sellingStatus"}->{"convertedCurrentPrice"}->__toString()) * 100;
+                $descr = "Preis: " . $result->{"sellingStatus"}->{"convertedCurrentPrice"}->__toString() . " €";
                 $descr .= ", Versandkosten: " . $result->{"shippingInfo"}->{"shippingServiceCost"}->__toString() . " €";
                 if (isset($result->{"listingInfo"}->{"listingType"})) {
                     $descr .= ", Auktionsart: " . $result->{"listingInfo"}->{"listingType"}->__toString();
@@ -51,11 +51,11 @@ class Ebay extends Searchengine
                     $link,
                     $anzeigeLink,
                     $descr,
-                    $this->displayName,$this->homepage,
+                    $this->engine->{"display-name"}, $this->engine->homepage,
                     $this->counter,
                     ['partnershop' => false,
-                        'price'        => $price,
-                        'image'        => $image]
+                        'price' => $price,
+                        'image' => $image]
                 );
                 $count++;
             }
diff --git a/app/Models/parserSkripte/Minisucher.php b/app/Models/parserSkripte/Minisucher.php
index 171fb322f..f318b2442 100644
--- a/app/Models/parserSkripte/Minisucher.php
+++ b/app/Models/parserSkripte/Minisucher.php
@@ -6,11 +6,11 @@ use App\Models\Searchengine;
 
 class Minisucher extends Searchengine
 {
-    public function __construct(\SimpleXMLElement $engine, \App\MetaGer $metager)
+    public function __construct($name, \stdClass $engine, \App\MetaGer $metager)
     {
-        parent::__construct($engine, $metager);
+        parent::__construct($name, $engine, $metager);
         # Für die Newssuche stellen wir die Minisucher auf eine Sortierung nach Datum um.
-        if($metager->getFokus() === "nachrichten"){
+        if ($metager->getFokus() === "nachrichten") {
             $this->getString .= "sort=" . $this->urlencode("documentDate desc");
         }
     }
@@ -28,25 +28,26 @@ class Minisucher extends Searchengine
 
         $results = $content->xpath('//response/result/doc');
 
-
         $string = "";
 
-        $counter         = 0;
+        $counter = 0;
         $providerCounter = [];
         foreach ($results as $result) {
             try {
                 $counter++;
                 $result = simplexml_load_string($result->saveXML());
 
-                $title         = $result->xpath('//doc/arr[@name="title"]/str')[0]->__toString();
-                $link          = $result->xpath('//doc/str[@name="url"]')[0]->__toString();
-                $anzeigeLink  = $link;
-                $descr        = "";
+                $title = $result->xpath('//doc/arr[@name="title"]/str')[0]->__toString();
+                $link = $result->xpath('//doc/str[@name="url"]')[0]->__toString();
+                $anzeigeLink = $link;
+                $descr = "";
+
                 $descriptions = $content->xpath("//response/lst[@name='highlighting']/lst[@name='$link']/arr[@name='content']/str");
                 foreach ($descriptions as $description) {
                     $descr .= $description->__toString();
                 }
-                $descr    = strip_tags($descr);
+
+                $descr = strip_tags($descr);
 
                 $dateString = $result->xpath('//doc/date[@name="documentDate"]')[0]->__toString();
 
@@ -56,18 +57,10 @@ class Minisucher extends Searchengine
 
                 $additionalInformation = ['date' => $dateVal];
 
-                $minism = simplexml_load_string($this->engine)["subcollections"];
+                $minism = $this->engine->{"display-name"};
+                $gefVon = "Minisucher: $minism";
                 $subcollection = $result->xpath('//doc/str[@name="subcollection"]')[0]->__toString();
 
-                if(!$subcollection) {
-                    $gefVon = "Minisucher: $minism";
-                } else {
-                    $minism = array_map('strtolower', explode(', ', $minism));
-                    $subcollection = array_map('strtolower', explode(' ', $subcollection));
-                    $result = implode(', ', array_intersect($subcollection, $minism));
-                    $gefVon = "Minisucher: $result";
-                }
-                
                 $this->results[] = new \App\Models\Result(
                     $this->engine,
                     $title,
@@ -78,6 +71,7 @@ class Minisucher extends Searchengine
                     $counter,
                     $additionalInformation
                 );
+
             } catch (\ErrorException $e) {
                 continue;
             }
diff --git a/app/Models/parserSkripte/Mnogosearch.php b/app/Models/parserSkripte/Mnogosearch.php
index 9998be59c..350eff6c4 100644
--- a/app/Models/parserSkripte/Mnogosearch.php
+++ b/app/Models/parserSkripte/Mnogosearch.php
@@ -9,9 +9,9 @@ class Mnogosearch extends Searchengine
 {
     public $results = [];
 
-    public function __construct(\SimpleXMLElement $engine, \App\MetaGer $metager)
+    public function __construct($name, \stdClass $engine, \App\MetaGer $metager)
     {
-        parent::__construct($engine, $metager);
+        parent::__construct($name, $engine, $metager);
     }
 
     public function loadResults($result)
@@ -28,9 +28,9 @@ class Mnogosearch extends Searchengine
                 $title = $node->filter('table > tr > td ')->eq(1)->filter('td > div')->text();
                 $title = preg_replace("/\s+/si", " ", $title);
 
-                $link        = $node->filter('table > tr > td ')->eq(1)->filter('td > div > a')->attr('href');
+                $link = $node->filter('table > tr > td ')->eq(1)->filter('td > div > a')->attr('href');
                 $anzeigeLink = $link;
-                $descr       = $node->filter('table > tr > td ')->eq(1)->filter('td > div')->eq(1)->text();
+                $descr = $node->filter('table > tr > td ')->eq(1)->filter('td > div')->eq(1)->text();
                 $this->counter++;
 
                 $this->results[] = new \App\Models\Result(
@@ -39,7 +39,7 @@ class Mnogosearch extends Searchengine
                     $link,
                     $anzeigeLink,
                     $descr,
-                    $this->displayName,$this->homepage,
+                    $this->engine->{"display-name"}, $this->engine->homepage,
                     $this->counter
                 );
             });
diff --git a/app/Models/parserSkripte/Nebel.php b/app/Models/parserSkripte/Nebel.php
index 34b0e085b..2ec701d4b 100644
--- a/app/Models/parserSkripte/Nebel.php
+++ b/app/Models/parserSkripte/Nebel.php
@@ -8,9 +8,9 @@ class Nebel extends Searchengine
 {
     public $results = [];
 
-    public function __construct(\SimpleXMLElement $engine, \App\MetaGer $metager)
+    public function __construct($name, \stdClass $engine, \App\MetaGer $metager)
     {
-        parent::__construct($engine, $metager);
+        parent::__construct($name, $engine, $metager);
     }
 
     public function loadResults($result)
@@ -22,10 +22,10 @@ class Nebel extends Searchengine
                 continue;
             }
 
-            $title       = $res[1];
-            $link        = $res[0];
+            $title = $res[1];
+            $link = $res[0];
             $anzeigeLink = $link;
-            $descr       = $res[2];
+            $descr = $res[2];
 
             $this->counter++;
             $this->results[] = new \App\Models\Result(
@@ -34,7 +34,7 @@ class Nebel extends Searchengine
                 $link,
                 $anzeigeLink,
                 $descr,
-                $this->displayName,$this->homepage,
+                $this->engine->{"display-name"}, $this->engine->homepage,
                 $this->counter
             );
         }
diff --git a/app/Models/parserSkripte/Witch.php b/app/Models/parserSkripte/Witch.php
index 1d058f460..fd47a84cd 100644
--- a/app/Models/parserSkripte/Witch.php
+++ b/app/Models/parserSkripte/Witch.php
@@ -8,9 +8,9 @@ class Witch extends Searchengine
 {
     public $results = [];
 
-    public function __construct(\SimpleXMLElement $engine, \App\MetaGer $metager)
+    public function __construct($name, \stdClass $engine, \App\MetaGer $metager)
     {
-        parent::__construct($engine, $metager);
+        parent::__construct($name, $engine, $metager);
     }
 
     public function loadResults($result)
@@ -25,10 +25,10 @@ class Witch extends Searchengine
             if (sizeof($res) !== 4 || $res[3] === "'Kein Ergebnis'") {
                 continue;
             }
-            $title       = trim($res[0], "'");
-            $link        = trim($res[2], "'");
+            $title = trim($res[0], "'");
+            $link = trim($res[2], "'");
             $anzeigeLink = $link;
-            $descr       = trim($res[1], "'");
+            $descr = trim($res[1], "'");
 
             $this->counter++;
             $this->results[] = new \App\Models\Result(
@@ -37,7 +37,7 @@ class Witch extends Searchengine
                 $link,
                 $anzeigeLink,
                 $descr,
-                $this->displayName,$this->homepage,
+                $this->engine->{"display-name"}, $this->engine->homepage,
                 $this->counter
             );
         }
@@ -50,10 +50,10 @@ class Witch extends Searchengine
             return;
         }
 
-        $next            = new Witch(simplexml_load_string($this->engine), $metager);
-        $offset          = $metager->getPage() * 10;
-        $next->getString = preg_replace("/&cn=\d+/si", "&cn=$offset", $next->getString);
-        $next->hash      = md5($next->host . $next->getString . $next->port . $next->name);
-        $this->next      = $next;
+        $offset = $metager->getPage() * 10;
+        $newEngine = unserialize(serialize($this->engine));
+        $newEngine->{"get-parameter"}->cn = "$offset";
+        $next = new Witch($this->name, $newEngine, $metager);
+        $this->next = $next;
     }
 }
diff --git a/app/Models/parserSkripte/Yacy.php b/app/Models/parserSkripte/Yacy.php
index 833fe5e03..a335e24a4 100644
--- a/app/Models/parserSkripte/Yacy.php
+++ b/app/Models/parserSkripte/Yacy.php
@@ -9,25 +9,25 @@ class Yacy extends Searchengine
 {
     public $results = [];
 
-    public function __construct(\SimpleXMLElement $engine, \App\MetaGer $metager)
+    public function __construct($name, \stdClass $engine, \App\MetaGer $metager)
     {
-        parent::__construct($engine, $metager);
+        parent::__construct($name, $engine, $metager);
     }
 
     public function loadResults($result)
     {
-        
+
         try {
             $content = json_decode($result, true);
             $content = $content["channels"];
 
-            foreach($content as $channel){
+            foreach ($content as $channel) {
                 $items = $channel["items"];
-                foreach($items as $item){
-                    $title       = $item["title"];
-                    $link        = $item["link"];
+                foreach ($items as $item) {
+                    $title = $item["title"];
+                    $link = $item["link"];
                     $anzeigeLink = $link;
-                    $descr       = $item["description"];
+                    $descr = $item["description"];
 
                     $this->counter++;
                     $this->results[] = new \App\Models\Result(
@@ -36,11 +36,11 @@ class Yacy extends Searchengine
                         $link,
                         $anzeigeLink,
                         $descr,
-                        $this->displayName,$this->homepage,
+                        $this->engine->{"display-name"}, $this->engine->homepage,
                         $this->counter
                     );
                 }
-                
+
             }
         } catch (\Exception $e) {
             Log::error("A problem occurred parsing results from $this->name:");
diff --git a/config/.gitignore b/config/.gitignore
index b2b7fad57..558a1fd61 100644
--- a/config/.gitignore
+++ b/config/.gitignore
@@ -1,4 +1,5 @@
 sumas.xml
 sumasEn.xml
+sumas.json
 blacklistUrl.txt
 blacklistDomains.txt
\ No newline at end of file
diff --git a/resources/lang/de/index.php b/resources/lang/de/index.php
index 564344b5c..9c67140a5 100644
--- a/resources/lang/de/index.php
+++ b/resources/lang/de/index.php
@@ -1,50 +1,51 @@
 <?php
 
 return [
-    'foki.web'                       => 'Web',
-    'foki.bilder'                    => 'Bilder',
-    'foki.nachrichten'               => 'News/Politik',
-    'foki.wissenschaft'              => 'Wissenschaft',
-    'foki.produkte'                  => 'Produkte',
-    'foki.angepasst'                 => 'Angepasst',
-    'foki.maps'                      => 'Maps.metager.de',
+    'foki.web' => 'Web',
+    'foki.bilder' => 'Bilder',
+    'foki.nachrichten' => 'News/Politik',
+    'foki.wissenschaft' => 'Wissenschaft',
+    'foki.produkte' => 'Produkte',
+    'foki.angepasst' => 'Angepasst',
+    'foki.maps' => 'Maps.metager.de',
 
-    'design'                         => 'Persönliches Design auswählen',
+    'design' => 'Persönliches Design auswählen',
 
-    'conveyor'                       => 'Einkaufen bei MetaGer-Fördershops',
-    'partnertitle'                   => 'MetaGer unterstützen, ohne Mehrkosten für Sie',
-    'mapstitle'                      => 'Der MetaGer Kartenservice',
+    'conveyor' => 'Einkaufen bei MetaGer-Fördershops',
+    'partnertitle' => 'MetaGer unterstützen, ohne Mehrkosten für Sie',
+    'mapstitle' => 'Der MetaGer Kartenservice',
 
-    'plugin'                         => 'MetaGer-Plugin hinzufügen',
-    'plugin-title'                   => 'MetaGer zu Ihrem Browser hinzufügen',
+    'plugin' => 'MetaGer-Plugin hinzufügen',
+    'plugin-title' => 'MetaGer zu Ihrem Browser hinzufügen',
 
-    'focus-creator.head'             => 'Eigene Suche erstellen',
-    'focus-creator.description'      => 'Suchen Sie sich aus unseren Suchmaschinen Ihre eigene Suche zusammen.',
+    'options.head' => 'Filter verwalten',
+
+    'focus-creator.description' => 'Suchen Sie sich aus unseren Suchmaschinen Ihre eigene Suche zusammen.',
     'focus-creator.name-placeholder' => 'Name der eigenen Suche',
-    'focus-creator.save'             => 'Eigene Suche durchführen',
-    'focus-creator.delete'           => 'Eigene Suche löschen',
-    'focus-creator.focusname'        => 'Suchname: ',
-
-    'slogan.title'                   => 'Besser&nbsp;suchen, schneller&nbsp;finden',
-    'slogan.1'                       => 'Datenschutz & Privatsphäre: Bei uns einfach und selbstverständlich.<br>Wir arbeiten nicht gewinnorientiert, wir sind ein gemeinnütziger Verein: <a href="/beitritt">Werden Sie Mitglied</a> oder <a href="/spende">spenden Sie</a>!',
-    'slogan.2'                       => 'Mit MetaGer bewahren Sie einen neutralen Blick auf’s Web!',
-
-    'sponsors.head'                  => 'Sponsoren',
-    'sponsors.woxikon'               => 'SEO Agentur',
-    'sponsors.gutscheine'            => 'STERN.de: Günstige Kredite im Kreditvergleich',
-    'sponsors.seo'                   => 'Weihnachtsfeier',
-
-    'about.title'                    => 'Ãœber uns',
-    'about.1.1'                      => '<a href="/datenschutz">Datenschutz & Privatsphäre</a>: Bei uns einfach & selbstverständlich.',
-    'about.2.1'                      => 'Wir arbeiten nicht gewinnorientiert, wir sind ein <a href="/spende">gemeinnütziger Verein</a>: <a href="/beitritt">Werden Sie Mitglied!</a>',
-    'about.3.1'                      => '',
-
-    'lang.tooltip'                   => 'Ergebnissprache wählen',
-    'key.placeholder'                => 'Mitglieder-Key eingeben',
-    'key.tooltip'                    => 'Mitglieder-Key eingeben',
-    'placeholder'                    => 'MetaGer: Sicher suchen & finden, Privatsphäre schützen',
-
-    'tooltips.add-focus'             => 'Suche anpassen',
-    'tooltips.edit-focus'            => 'Aktuellen Fokus bearbeiten',
-    'tooltips.settings'              => 'Allgemeine Einstellungen',
+    'focus-creator.save' => 'Eigene Suche durchführen',
+    'focus-creator.delete' => 'Eigene Suche löschen',
+    'focus-creator.focusname' => 'Suchname: ',
+
+    'slogan.title' => 'Besser&nbsp;suchen, schneller&nbsp;finden',
+    'slogan.1' => 'Datenschutz & Privatsphäre: Bei uns einfach und selbstverständlich.<br>Wir arbeiten nicht gewinnorientiert, wir sind ein gemeinnütziger Verein: <a href="/beitritt">Werden Sie Mitglied</a> oder <a href="/spende">spenden Sie</a>!',
+    'slogan.2' => 'Mit MetaGer bewahren Sie einen neutralen Blick auf’s Web!',
+
+    'sponsors.head' => 'Sponsoren',
+    'sponsors.woxikon' => 'SEO Agentur',
+    'sponsors.gutscheine' => 'STERN.de: Günstige Kredite im Kreditvergleich',
+    'sponsors.seo' => 'Weihnachtsfeier',
+
+    'about.title' => 'Ãœber uns',
+    'about.1.1' => '<a href="/datenschutz">Datenschutz & Privatsphäre</a>: Bei uns einfach & selbstverständlich.',
+    'about.2.1' => 'Wir arbeiten nicht gewinnorientiert, wir sind ein <a href="/spende">gemeinnütziger Verein</a>: <a href="/beitritt">Werden Sie Mitglied!</a>',
+    'about.3.1' => '',
+
+    'lang.tooltip' => 'Ergebnissprache wählen',
+    'key.placeholder' => 'Mitglieder-Key eingeben',
+    'key.tooltip' => 'Mitglieder-Key eingeben',
+    'placeholder' => 'MetaGer: Sicher suchen & finden, Privatsphäre schützen',
+
+    'tooltips.add-focus' => 'Suche anpassen',
+    'tooltips.edit-focus' => 'Aktuellen Fokus bearbeiten',
+    'tooltips.settings' => 'Allgemeine Einstellungen',
 ];
diff --git a/resources/lang/de/metaGer.php b/resources/lang/de/metaGer.php
index dff275091..7d06f80a4 100644
--- a/resources/lang/de/metaGer.php
+++ b/resources/lang/de/metaGer.php
@@ -21,5 +21,62 @@ return [
     'sitesearch.failed' => 'Sie wollten eine Sitesearch auf :site durchführen. Leider unterstützen die eingestellten Suchmaschinen diese nicht. Sie können die Sitesearch im Web-Fokus durchführen. Es werden ihnen Ergebnisse ohne Sitesearch angezeigt.',
     'sitesearch.success' => 'Sie führen eine Sitesearch durch. Es werden nur Ergebnisse von der Seite: ":site" angezeigt.',
     'feedback' => 'Nichts Passendes dabei? Geben Sie uns Feedback: ',
+
+    'filter.noFilter' => 'Alle',
+
     'filter.sitesearch' => 'Sitesearch',
+    'filter.safesearch' => "SafeSearch",
+    'filter.safesearch.strict' => 'Strikt',
+    'filter.safesearch.moderate' => 'Moderat',
+    'filter.safesearch.off' => 'Aus',
+    'filter.size' => 'Bildgröße',
+    'filter.size.small' => 'Klein',
+    'filter.size.medium' => 'Mittel',
+    'filter.size.large' => 'Groß',
+    'filter.size.xtralarge' => 'Extra Groß',
+
+    "filter.color" => "Farbe",
+    "filter.color.colorOnly" => "Nur Farbe",
+    "filter.color.monochrome" => "Schwarzweiß",
+    "filter.color.black" => "Schwarz",
+    "filter.color.blue" => "Blau",
+    "filter.color.brown" => "Braun",
+    "filter.color.gray" => "Grau",
+    "filter.color.green" => "Grün",
+    "filter.color.orange" => "Orange",
+    "filter.color.pink" => "Pink",
+    "filter.color.purple" => "Lila",
+    "filter.color.red" => "Rot",
+    "filter.color.teal" => "Petrol",
+    "filter.color.white" => "Weiß",
+    "filter.color.yellow" => "Gelb",
+
+    "filter.imagetype" => "Typ",
+    "filter.imagetype.photo" => "Foto",
+    "filter.imagetype.clipart" => "Grafik",
+    "filter.imagetype.strich" => "Strichzeichnung",
+    "filter.imagetype.gif" => "Animierte GIF",
+    "filter.imagetype.transparent" => "Transparent",
+
+    "filter.imageaspect" => "Layout",
+    "filter.imageaspect.square" => "Rechteck",
+    "filter.imageaspect.wide" => "Breit",
+    "filter.imageaspect.tall" => "Hoch",
+
+    "filter.imagecontent" => "Personen",
+    "filter.imagecontent.face" => "Nahaufnahmen",
+    "filter.imagecontent.portrait" => "Kopf & Schultern",
+
+    "filter.imagelicense" => "Lizenz",
+    "filter.imagelicense.any" => "Beliebig",
+    "filter.imagelicense.public" => "Public Domain",
+    "filter.imagelicense.share" => "Teilen",
+    "filter.imagelicense.sharecommercially" => "Teilen (kommerziell)",
+    "filter.imagelicense.modify" => "Verändern",
+    "filter.imagelicense.modifycommercially" => "Verändern (kommerziell)",
+
+    "filter.freshness" => "Datum",
+    "filter.freshness.day" => "Letzte 24h",
+    "filter.freshness.week" => "Letzte Woche",
+    "filter.freshness.month" => "Letzter Monat",
 ];
diff --git a/resources/less/metager/pages/resultpage/result-page.less b/resources/less/metager/pages/resultpage/result-page.less
index 233e37af6..197d3e997 100644
--- a/resources/less/metager/pages/resultpage/result-page.less
+++ b/resources/less/metager/pages/resultpage/result-page.less
@@ -332,7 +332,7 @@ a {
     margin-left: @results-margin-left;
     display: grid;
     grid-template-columns: @results-width-max @additions-width-max;
-    grid-template-areas: "searchbar ." "foki ." "results additions";
+    grid-template-areas: "searchbar ." "foki ." "options ." "results additions";
     grid-column-gap: (@padding-small-default * 2);
     grid-row-gap: @padding-small-default;
     justify-items: stretch;
@@ -372,13 +372,13 @@ a {
     @media (max-width: @resultpage-breakpoint-max) {
         @supports (display: grid) {
             grid-template-columns:~"calc(60% - 8px)"~"calc(40% - 8px)";
-            grid-template-areas: "searchbar ." "foki ." "results additions";
+            grid-template-areas: "searchbar ." "foki ." "options ." "results additions";
         }
     }
     @media (max-width: @resultpage-breakpoint-min) {
         margin-left: @padding-small-default;
         grid-template-columns: 100%;
-        grid-template-areas: "searchbar" "foki" "results";
+        grid-template-areas: "searchbar" "foki" "options" "results";
         #additions-container {
             display: none;
         }
@@ -480,6 +480,60 @@ a {
     }
 }
 
+#options {
+    grid-area: options;
+    display: flex;
+    justify-content: left;
+    input[type=checkbox]{
+        display: none;
+    }
+    .scrollbox {
+        max-height: 0;
+        transform-origin: top;
+        transform: scaleY(0);
+        transition: transform .5s, max-height .5s;
+    }
+    input[type=checkbox]:checked + div.scrollbox {
+        max-height:200px;
+        transform: scaleY(1);
+    }
+    #options-box{
+        width: 100%;
+        max-width: 700px;
+        background-color: white;
+        border: 1px solid #ccc;
+        box-shadow: 0px 1px 1.5px 0px rgba(0, 0, 0, 0.12), 1px 0px 1px 0px rgba(0, 0, 0, 0.24);
+        overflow: hidden;
+        overflow-x: auto;
+        padding: 8px;
+        #options-items {
+            display: -ms-flexbox;
+            display: flex;
+            align-items: center;
+            .option-selector {
+                display: flex;
+                flex-direction: column;
+                margin: 0 8px;
+                justify-content: center;
+                align-items: center;
+                label {
+                    margin-bottom: 0;
+                }
+                select {
+                    background-color: white;
+                    border: 1px solid #77777780;
+                    padding: 3px;
+                    border-radius: 5px;
+                }
+            }
+        }
+        #options-reset {
+            margin-left: 10px;
+            margin-bottom: 8px;
+        }
+    }
+}
+
 #spendenaufruf {
     margin-bottom: 10px;
     a {
diff --git a/resources/views/layouts/researchandtabs.blade.php b/resources/views/layouts/researchandtabs.blade.php
index 50f80a235..c248d973f 100644
--- a/resources/views/layouts/researchandtabs.blade.php
+++ b/resources/views/layouts/researchandtabs.blade.php
@@ -26,6 +26,35 @@
 			<div class="scrollfade-right"></div>
 		</div>
 	</div>
+	@if(sizeof($metager->getAvailableParameterFilter()) > 0)
+	<div id="options">
+		<input type="checkbox" id="options-toggle" @if(sizeof($metager->getParameterFilter()) > 0)checked @endif />
+		<div class="scrollbox">
+			<div class="scrollfade-left"></div>
+			<div id="options-box">
+				@if(sizeof($metager->getParameterFilter()) > 0)
+				<div id="options-reset">
+					<a href="{{$metager->generateSearchLink($metager->getFokus())}}"><nobr>Filter zurücksetzen</nobr></a>
+				</div>
+				@endif
+				<div id="options-items">
+				@foreach($metager->getAvailableParameterFilter() as $filterName => $filter)
+					<div class="option-selector">
+					<label for="{{$filterName}}">@lang($filter->name)</label>
+					<select name="{{$filter->{'get-parameter'} }}" form="searchForm">
+						@foreach($filter->values as $value => $text)
+						<option value="{{$value}}" @if(Request::input($filter->{'get-parameter'}, '') === $value)selected="selected" @endif>{{trans($text)}}</option>
+						@endforeach
+					</select>
+					</div>
+				@endforeach
+				</div>
+
+			</div>
+			<div class="scrollfade-right"></div>
+		</div>
+	</div>
+	@endif
 	<div id="results-container">
 		@include('parts.errors')
 		@include('parts.warnings')
diff --git a/resources/views/parts/foki.blade.php b/resources/views/parts/foki.blade.php
index ea799c11d..25c3bd328 100644
--- a/resources/views/parts/foki.blade.php
+++ b/resources/views/parts/foki.blade.php
@@ -1,33 +1,12 @@
-@if( array_has($metager->getAvailableFoki(), "web"))
-<div id="web" @if($metager->getFokus() === "web")class="active"@endif>
-	<a href="@if($metager->getFokus() === "web")#@else{!!$metager->generateSearchLink('web')!!}@endif" target="_self" tabindex="2">@lang('index.foki.web')</a>
-</div>
-@endif
-@if( array_has($metager->getAvailableFoki(), "bilder"))
-<div id="bilder" @if($metager->getFokus() === "bilder")class="active"@endif>
-	<a href="@if($metager->getFokus() === "bilder")#@else{!!$metager->generateSearchLink('bilder')!!}@endif" target="_self" tabindex="2">@lang('index.foki.bilder')</a>
-</div>
-@endif
-@if( array_has($metager->getAvailableFoki(), "nachrichten"))
-<div id="nachrichten" @if($metager->getFokus() === "nachrichten")class="active"@endif>
-	<a href="@if($metager->getFokus() === "nachrichten")#@else{!!$metager->generateSearchLink('nachrichten')!!}@endif" target="_self" tabindex="3">@lang('index.foki.nachrichten')</a>
-</div>
-@endif
-@if( array_has($metager->getAvailableFoki(), "wissenschaft"))
-<div id="wissenschaft" @if($metager->getFokus() === "wissenschaft")class="active"@endif>
-	<a href="@if($metager->getFokus() === "wissenschaft")#@else{!!$metager->generateSearchLink('wissenschaft')!!}@endif" target="_self" tabindex="4">@lang('index.foki.wissenschaft')</a>
-</div>
-@endif
-@if( array_has($metager->getAvailableFoki(), "produktsuche"))
-<div id="produkte" @if($metager->getFokus() === "produktsuche")class="active"@endif>
-	<a href="@if($metager->getFokus() === "produktsuche")#@else{!!$metager->generateSearchLink('produktsuche')!!}@endif" target="_self" tabindex="5">@lang('index.foki.produkte')</a>
-</div>
-@endif
-<div id="maps">
-	<a href="https://maps.metager.de/map/{{ $metager->getQ() }}/9.7380161,52.37119740000003,12" target="_self" tabindex="6">@lang('index.foki.maps')</a>
-</div>
-<div class="search-option-frame hide-tooltip-on-resultpage" data-tooltip="@lang("index.focus-creator.head")">
-	<label class="navigation-element" for="show-create-focus">
-		<i class="fa fa-sliders-h"></i>
+@foreach($metager->getSumaFile()->foki as $name => $fokus)
+	<div id="{{$name}}" @if($metager->getFokus() === $name)class="active"@endif>
+		<a href="@if($metager->getFokus() === $name)#@else{!!$metager->generateSearchLink($name)!!}@endif" target="_self" tabindex="0">@lang($fokus->{"display-name"})</a>
+	</div>
+@endforeach
+@if(sizeof($metager->getAvailableParameterFilter()) > 0)
+<div class="option-toggle">
+	<label class="navigation-element" for="options-toggle">
+		<i class="fas fa-filter"></i>
 	</label>
 </div>
+@endif
-- 
GitLab