MetaGer.php 44.8 KB
Newer Older
1
2
3
4
<?php
namespace App;

use App;
5
use Cache;
6
7
use Illuminate\Http\Request;
use Jenssegers\Agent\Agent;
Dominik Hebeler's avatar
Bugfix    
Dominik Hebeler committed
8
use LaravelLocalization;
9
use Log;
10
use Predis\Connection\ConnectionException;
11
use Redis;
12
13
14

class MetaGer
{
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    # Einstellungen für die Suche
    protected $fokus;
    protected $eingabe;
    protected $q;
    protected $category;
    protected $time;
    protected $page;
    protected $lang;
    protected $cache = "";
    protected $site;
    protected $hostBlacklist   = [];
    protected $domainBlacklist = [];
    protected $stopWords       = [];
    protected $phrases         = [];
    protected $engines         = [];
    protected $results         = [];
    protected $ads             = [];
Dominik Hebeler's avatar
Dominik Hebeler committed
32
    protected $products        = [];
33
34
35
    protected $warnings        = [];
    protected $errors          = [];
    protected $addedHosts      = [];
36
    protected $startCount      = 0;
37
    protected $canCache        = false;
38
39
40
41
42
43
44
45
46
    # Daten über die Abfrage
    protected $ip;
    protected $language;
    protected $agent;
    # Konfigurationseinstellungen:
    protected $sumaFile;
    protected $mobile;
    protected $resultCount;
    protected $sprueche;
47
    protected $maps;
48
    protected $domainsBlacklisted = [];
49
    protected $urlsBlacklisted    = [];
50
51
52
    protected $url;
    protected $languageDetect;

53
54
    public function __construct()
    {
55
        # Timer starten
56
        $this->starttime = microtime(true);
57
58

        # Versuchen Blacklists einzulesen
59
60
        if (file_exists(config_path() . "/blacklistDomains.txt") && file_exists(config_path() . "/blacklistUrl.txt")) {
            $tmp                      = file_get_contents(config_path() . "/blacklistDomains.txt");
61
            $this->domainsBlacklisted = explode("\n", $tmp);
62
63
64
            $tmp                      = file_get_contents(config_path() . "/blacklistUrl.txt");
            $this->urlsBlacklisted    = explode("\n", $tmp);
        } else {
65
            Log::warning("Achtung: Eine, oder mehrere Blacklist Dateien, konnten nicht geöffnet werden");
66
67
        }

68
        # Parser Skripte einhängen
69
70
71
72
        $dir = app_path() . "/Models/parserSkripte/";
        foreach (scandir($dir) as $filename) {
            $path = $dir . $filename;
            if (is_file($path)) {
73
                require_once $path;
74
75
76
            }
        }

77
        # Cachebarkeit testen
78
79
80
81
82
83
        try {
            Cache::has('test');
            $this->canCache = true;
        } catch (ConnectionException $e) {
            $this->canCache = false;
        }
84
    }
85

86
    # Erstellt aus den gesammelten Ergebnissen den View
87
88
89
    public function createView()
    {
        $viewResults = [];
90
        # Wir extrahieren alle notwendigen Variablen und geben Sie an unseren View:
91
        foreach ($this->results as $result) {
92
93
94
95
96
97
            $viewResults[] = get_object_vars($result);
        }

        # Wir müssen natürlich noch den Log für die durchgeführte Suche schreiben:
        $this->createLogs();

98
99
        if ($this->fokus === "bilder") {
            switch ($this->out) {
100
101
102
103
104
105
106
107
                case 'results':
                    return view('metager3bilderresults')
                        ->with('results', $viewResults)
                        ->with('eingabe', $this->eingabe)
                        ->with('mobile', $this->mobile)
                        ->with('warnings', $this->warnings)
                        ->with('errors', $this->errors)
                        ->with('metager', $this)
108
                        ->with('browser', (new Agent())->browser());
109
110
111
112
113
114
115
116
                default:
                    return view('metager3bilder')
                        ->with('results', $viewResults)
                        ->with('eingabe', $this->eingabe)
                        ->with('mobile', $this->mobile)
                        ->with('warnings', $this->warnings)
                        ->with('errors', $this->errors)
                        ->with('metager', $this)
117
                        ->with('browser', (new Agent())->browser());
118
            }
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
        } else {
            switch ($this->out) {
                case 'results':
                    return view('metager3results')
                        ->with('results', $viewResults)
                        ->with('eingabe', $this->eingabe)
                        ->with('mobile', $this->mobile)
                        ->with('warnings', $this->warnings)
                        ->with('errors', $this->errors)
                        ->with('metager', $this)
                        ->with('browser', (new Agent())->browser());
                    break;
                case 'results-with-style':
                    return view('metager3')
                        ->with('results', $viewResults)
                        ->with('eingabe', $this->eingabe)
                        ->with('mobile', $this->mobile)
                        ->with('warnings', $this->warnings)
                        ->with('errors', $this->errors)
                        ->with('metager', $this)
                        ->with('suspendheader', "yes")
                        ->with('browser', (new Agent())->browser());
                    break;
                default:
                    return view('metager3')
                        ->with('eingabe', $this->eingabe)
                        ->with('mobile', $this->mobile)
                        ->with('warnings', $this->warnings)
                        ->with('errors', $this->errors)
                        ->with('metager', $this)
                        ->with('browser', (new Agent())->browser());
                    break;
            }
152
153
154
        }
    }

Phil Höfer's avatar
Phil Höfer committed
155
    public function prepareResults()
156
    {
Phil Höfer's avatar
Phil Höfer committed
157
        $engines = $this->engines;
158

Phil Höfer's avatar
Phil Höfer committed
159
160
        // combine
        $combinedResults = $this->combineResults($engines);
161
162
163
164

        # Wir bestimmen die Sprache eines jeden Suchergebnisses
        $this->results = $this->addLangCodes($this->results);

Phil Höfer's avatar
Phil Höfer committed
165
166
167
168
169
170
        // sort
        //$sortedResults = $this->sortResults($engines);
        // filter
        // augment (boost&adgoal)
        // authorize
        // misc (WiP)
171
172
        uasort($this->results, function ($a, $b) {
            if ($a->getRank() == $b->getRank()) {
173
                return 0;
174
175
            }

176
177
            return ($a->getRank() < $b->getRank()) ? 1 : -1;
        });
178

179
180
        # Validate Results
        $newResults = [];
181
182
        foreach ($this->results as $result) {
            if ($result->isValid($this)) {
183
                $newResults[] = $result;
184
185
            }

186
187
188
        }
        $this->results = $newResults;

189
190
191
192
193
194
        # Boost implementation
        $this->results = $this->parseBoost($this->results);

        #Adgoal Implementation
        $this->results = $this->parseAdgoal($this->results);

195
        $counter   = 0;
196
        $firstRank = 0;
197

198
        if (isset($this->startForwards)) {
199
            $this->startCount = $this->startForwards;
200
        } elseif (isset($this->startBackwards)) {
201
            $this->startCount = $this->startBackwards - count($this->results) - 1;
202
        } else {
203
204
205
            $this->startCount = 0;
        }

206
207
        foreach ($this->results as $result) {
            if ($counter === 0) {
208
                $firstRank = $result->rank;
209
210
            }

211
            $counter++;
212
            $result->number = $counter + $this->startCount;
213
214
215
216
            $confidence     = 0;
            if ($firstRank > 0) {
                $confidence = $result->rank / $firstRank;
            } else {
217
                $confidence = 0;
218
219
220
            }

            if ($confidence > 0.65) {
221
                $result->color = "#FF4000";
222
            } elseif ($confidence > 0.4) {
223
                $result->color = "#FF0080";
224
            } elseif ($confidence > 0.2) {
225
                $result->color = "#C000C0";
226
            } else {
227
                $result->color = "#000000";
228
229
            }

230
231
        }

232
        if (LaravelLocalization::getCurrentLocale() === "en") {
233
234
235
            $this->ads = [];
        }

236
        $this->validated = false;
237
        if (isset($this->password)) {
238
239
240
            # Wir bieten einen bezahlten API-Zugriff an, bei dem dementsprechend die Werbung ausgeblendet wurde:
            # Aktuell ist es nur die Uni-Mainz. Deshalb überprüfen wir auch nur diese.
            $password = getenv('mainz');
241
            $eingabe  = $this->eingabe;
242
            $password = md5($eingabe . $password);
243
244
            if ($this->password === $password) {
                $this->ads       = [];
245
                $this->products  = [];
246
247
248
                $this->validated = true;
            }
        }
249
250

        if (count($this->results) <= 0) {
251
            $this->errors[] = trans('metaGer.results.failed');
252
        }
253

254
        if ($this->canCache() && isset($this->next) && count($this->next) > 0 && count($this->results) > 0) {
255
            $page       = $this->page + 1;
256
            $this->next = [
257
258
259
                'page'          => $page,
                'startForwards' => $this->results[count($this->results) - 1]->number,
                'engines'       => $this->next,
260
261
            ];
            Cache::put(md5(serialize($this->next)), serialize($this->next), 60);
262
263
        } else {
            $this->next = [];
264
265
        }

266
    }
267

268
269
    private function addLangCodes($results)
    {
270
271
272
273
274
        # Wenn es keine Ergebnisse gibt, brauchen wir uns gar nicht erst zu bemühen
        if (sizeof($results) === 0) {
            return $results;
        }

275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
        # Bei der Spracheinstellung "all" wird nicht gefiltert
        if ($this->getLang() === "all") {
            return $results;
        } else {
            # Ansonsten müssen wir jedem Result einen Sprachcode hinzufügen
            $id          = 0;
            $langStrings = [];
            foreach ($results as $result) {
                # Wir geben jedem Ergebnis eine ID um später die Sprachcodes zuordnen zu können
                $result->id = $id;

                $langStrings["result_" . $id] = utf8_encode($result->getLangString());

                $id++;
            }
            # Wir schreiben die Strings in eine temporäre JSON-Datei,
            # Da das Array unter umständen zu groß ist für eine direkte Übergabe an das Skript
            $filename = "/tmp/" . getmypid();
            file_put_contents($filename, json_encode($langStrings));
            $langDetectorPath = app_path() . "/Models/lang.pl";
            $lang             = exec("echo '$filename' | $langDetectorPath");
            $lang             = json_decode($lang, true);

            # Wir haben nun die Sprachcodes der einzelnen Ergebnisse.
            # Diese müssen wir nur noch korrekt zuordnen, dann sind wir fertig.
            foreach ($lang as $key => $langCode) {
                # Prefix vom Key entfernen:
                $id = intval(str_replace("result_", "", $key));
                foreach ($this->results as $result) {
                    if ($result->id === $id) {
                        $result->langCode = $langCode;
                        break;
                    }
                }
            }
            return $results;
        }
    }

314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
    /**
     * Diese Funktion überprüft, ob wir einen erweiterten Check auf Bots machen müssen.
     * Z.B.: Wurden wir von einem Bot (dessen Anfragen aus dem Tor-Netzwerk kamen) mit tausenden
     * Anfragen zu Telefonnummern überschwemmt. Bei diesen werden wir nun eine erweiterte Überprüfung
     * durchführen.
     * Für den Anfang werden wir alle Anfragen, die unter diese Kriterien fallen, nur noch beantworten, wenn
     * JavaScript ausgeführt wird. (Mal schauen ob und wie lange dies ausreicht)
     */
    public function doBotProtection($bot)
    {
        $hash = md5(date('YmdHi'));
        if (preg_match("/^\d+$/s", $this->getEingabe()) && $bot !== $hash) {
            return true;
        } else {
            return false;
        }

    }

Phil Höfer's avatar
Phil Höfer committed
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
    public function combineResults($engines)
    {
        foreach ($engines as $engine) {
            if (isset($engine->next)) {
                $this->next[] = $engine->next;
            }
            if (isset($engine->last)) {
                $this->last[] = $engine->last;
            }
            foreach ($engine->results as $result) {
                if ($result->valid) {
                    $this->results[] = $result;
                }
            }
            foreach ($engine->ads as $ad) {
                $this->ads[] = $ad;
            }
            foreach ($engine->products as $product) {
                $this->products[] = $product;
            }
        }

    }

357
358
    public function parseBoost($results)
    {
359
360
361
362
363
364
365
366
367
368
369
370
        foreach ($results as $result) {
            if (preg_match('/^(http[s]?\:\/\/)?(www.)?amazon\.de/', $result->anzeigeLink)) {
                if (preg_match('/\?/', $result->anzeigeLink)) {
                    $result->link .= '&tag=boostmg01-21';
                } else {
                    $result->link .= '?tag=boostmg01-21';
                }
                $result->partnershop = true;

            }
        }
        return $results;
371
    }
372

373
374
    public function parseAdgoal($results)
    {
375
        $publicKey  = getenv('adgoal_public');
376
        $privateKey = getenv('adgoal_private');
377
        if ($publicKey === false) {
378
379
380
            return $results;
        }
        $tldList = "";
381
382
        try {
            foreach ($results as $result) {
383
                $link = $result->anzeigeLink;
384
                if (strpos($link, "http") !== 0) {
385
386
387
388
389
390
391
392
393
394
                    $link = "http://" . $link;
                }
                $tldList .= parse_url($link, PHP_URL_HOST) . ",";
                $result->tld = parse_url($link, PHP_URL_HOST);
            }
            $tldList = rtrim($tldList, ",");

            # Hashwert
            $hash = md5("meta" . $publicKey . $tldList . "GER");

395
            # Query
396
397
            $query = urlencode($this->q);

398
            $link   = "https://api.smartredirect.de/api_v2/CheckForAffiliateUniversalsearchMetager.php?p=" . $publicKey . "&k=" . $hash . "&tld=" . $tldList . "&q=" . $query;
399
400
401
            $answer = json_decode(file_get_contents($link));

            # Nun müssen wir nur noch die Links für die Advertiser ändern:
402
            foreach ($answer as $el) {
403
                $hoster = $el[0];
404
                $hash   = $el[1];
405

406
407
                foreach ($results as $result) {
                    if ($hoster === $result->tld) {
408
409
                        # Hier ist ein Advertiser:
                        # Das Logo hinzufügen:
410
                        if ($result->image !== "") {
411
                            $result->logo = "https://img.smartredirect.de/logos_v2/60x30/" . $hash . ".gif";
412
                        } else {
413
                            $result->image = "https://img.smartredirect.de/logos_v2/120x60/" . $hash . ".gif";
414
415
                        }

416
417
418
                        # Den Link hinzufügen:
                        $publicKey = $publicKey;
                        $targetUrl = $result->anzeigeLink;
419
                        if (strpos($targetUrl, "http") !== 0) {
420
                            $targetUrl = "http://" . $targetUrl;
421
422
423
424
425
                        }

                        $gateHash            = md5($targetUrl . $privateKey);
                        $newLink             = "https://api.smartredirect.de/api_v2/ClickGate.php?p=" . $publicKey . "&k=" . $gateHash . "&url=" . urlencode($targetUrl) . "&q=" . $query;
                        $result->link        = $newLink;
426
427
428
429
                        $result->partnershop = true;
                    }
                }
            }
430
        } catch (\ErrorException $e) {
431
432
433
434
435
            return $results;
        }

        return $results;
    }
436

Karl's avatar
Karl committed
437
438
439
440
    /*
     * Die Erstellung der Suchmaschinen bis die Ergebnisse da sind mit Unterfunktionen
     */

441
442
    public function createSearchEngines(Request $request)
    {
443
        # Wenn es kein Suchwort gibt
444
        if (!$request->has("eingabe") || $this->q === "") {
445
            return;
446
        }
447

448
        $xml                  = simplexml_load_file($this->sumaFile);
449
        $sumas                = $xml->xpath("suma");
450
        $enabledSearchengines = [];
451
        $overtureEnabled      = false;
452
453
        $sumaCount            = 0;

Karl's avatar
Karl committed
454
        /* Erstellt die Liste der eingestellten Sumas
455
456
457
         * Der einzige Unterschied bei angepasstem Suchfokus ist,
         * dass nicht nach den Typen einer Suma,
         * sondern den im Request mitgegebenen Typen entschieden wird.
Karl's avatar
Karl committed
458
459
460
461
462
463
         * Ansonsten wird genau das selbe geprüft und gemacht:
         * Handelt es sich um spezielle Suchmaschinen die immer an sein müssen
         * Wenn es Overture ist vermerken dass Overture an ist
         * Suma Zähler erhöhen
         * Zu Liste hinzufügen
         */
464
        foreach ($sumas as $suma) {
465
            if (($this->sumaIsSelected($suma, $request)
466
                || (!$this->isBildersuche()
467
                    && $this->sumaIsAdsuche($suma, $overtureEnabled)))
468
469
470
                && (!$this->sumaIsDisabled($suma))) {
                if ($this->sumaIsOverture($suma)) {
                    $overtureEnabled = true;
471
                }
472
473
                if ($this->sumaIsNotAdsuche($suma)) {
                    $sumaCount += 1;
474
                }
475
                $enabledSearchengines[] = $suma;
476
477
478
479
480
            }
        }

        # Sonderregelung für alle Suchmaschinen, die zu den Minisuchern gehören. Diese können alle gemeinsam über einen Link abgefragt werden
        $subcollections = [];
481
482

        $tmp = [];
483
484
        foreach ($enabledSearchengines as $engine) {
            if (isset($engine['minismCollection'])) {
485
                $subcollections[] = $engine['minismCollection']->__toString();
486
            } else {
487
                $tmp[] = $engine;
488
489
            }

490
491
        }
        $enabledSearchengines = $tmp;
492
        if (sizeof($subcollections) > 0) {
493
            $enabledSearchengines[] = $this->loadMiniSucher($xml, $subcollections);
494
495
        }

496
        if ($sumaCount <= 0) {
497
            $this->errors[] = trans('metaGer.settings.noneSelected');
498
        }
499

500
        $engines = [];
501

502
        # Wenn eine Sitesearch durchgeführt werden soll, überprüfen wir ob überhaupt eine der Suchmaschinen eine Sitesearch unterstützt
503
        $siteSearchFailed = $this->checkCanNotSitesearch($enabledSearchengines);
504
505

        $typeslist = [];
506
        $counter   = 0;
507

508
509
        if ($this->requestIsCached($request)) {
            $engines = $this->getCachedEngines($request);
510
        } else {
511
            $engines = $this->actuallyCreateSearchEngines($enabledSearchengines, $siteSearchFailed);
512
        }
513

514
        # Wir starten alle Suchen
515
516
        foreach ($engines as $engine) {
            $engine->startSearch($this);
517
        }
518

519
520
        $this->adjustFocus($sumas, $enabledSearchengines);

521
522
523
524
525
        /* Wir warten auf die Antwort der Suchmaschinen
         * Die Verbindung steht zu diesem Zeitpunkt und auch unsere Requests wurden schon gesendet.
         * Wir zählen die Suchmaschinen, die durch den Cache beantwortet wurden:
         * $enginesToLoad zählt einerseits die Suchmaschinen auf die wir warten und andererseits
         * welche Suchmaschinen nicht rechtzeitig geantwortet haben.
526
527
         */

528
        $enginesToLoad = [];
529
530
531
532
533
534
        $canBreak      = false;
        foreach ($engines as $engine) {
            if ($engine->cached) {
                if ($overtureEnabled && ($engine->name === "overture" || $engine->name === "overtureAds")) {
                    $canBreak = true;
                }
535
536
            } else {
                $enginesToLoad[$engine->name] = false;
537
538
539
540
541
542
543
544
            }
        }

        $this->waitForResults($enginesToLoad, $overtureEnabled, $canBreak);

        $this->retrieveResults($engines);
    }

545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
    # Spezielle Suchen und Sumas

    public function sumaIsSelected($suma, $request)
    {
        if ($this->fokus === "angepasst") {
            if ($request->has($suma["name"])) {
                return true;
            }
        } else {
            $types = explode(",", $suma["type"]);
            if (in_array($this->fokus, $types)) {
                return true;
            }
        }
        return false;
    }

    public function actuallyCreateSearchEngines($enabledSearchengines, $siteSearchFailed)
    {
        $engines = [];
        foreach ($enabledSearchengines as $engine) {

            # Wenn diese Suchmaschine gar nicht eingeschaltet sein soll
            if (!$siteSearchFailed
                && strlen($this->site) > 0
                && (!isset($engine['hasSiteSearch'])
                    || $engine['hasSiteSearch']->__toString() === "0")) {
                continue;
            }

            # Setze Pfad zu Parser
            $path = "App\Models\parserSkripte\\" . ucfirst($engine["package"]->__toString());

            # Prüfe ob Parser vorhanden
            if (!file_exists(app_path() . "/Models/parserSkripte/" . ucfirst($engine["package"]->__toString()) . ".php")) {
580
                Log::error("Konnte " . $engine["name"] . " nicht abfragen, da kein Parser existiert");
581
                $this->errors[] = trans('metaGer.engines.noParser', ['engine' => $engine["name"]]);
582
583
584
585
586
587
588
589
                continue;
            }

            # Es wird versucht die Suchengine zu erstellen
            $time = microtime();
            try {
                $tmp = new $path($engine, $this);
            } catch (\ErrorException $e) {
590
                Log::error("Konnte " . $engine["name"] . " nicht abfragen. " . var_dump($e));
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
                continue;
            }

            # Ausgabe bei Debug-Modus
            if ($tmp->enabled && isset($this->debug)) {
                $this->warnings[] = $tmp->service . "   Connection_Time: " . $tmp->connection_time . "    Write_Time: " . $tmp->write_time . " Insgesamt:" . ((microtime() - $time) / 1000);
            }

            # Wenn die neu erstellte Engine eingeschaltet ist, wird sie der Liste hinzugefügt
            if ($tmp->isEnabled()) {
                $engines[] = $tmp;
            }
        }
        return $engines;
    }

    public function isBildersuche()
    {
609
        return $this->fokus === "bilder";
610
611
612
613
    }

    public function sumaIsAdsuche($suma, $overtureEnabled)
    {
614
        $sumaName = $suma["name"]->__toString();
615
        return
616
617
618
619
            $sumaName === "qualigo"
            || $sumaName === "similar_product_ads"
            || (!$overtureEnabled && $sumaName === "overtureAds")
            || $sumaName == "rlvproduct";
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
    }

    public function sumaIsDisabled($suma)
    {
        return
        isset($suma['disabled'])
        && $suma['disabled']->__toString() === "1";
    }

    public function sumaIsOverture($suma)
    {
        return
        $suma["name"]->__toString() === "overture"
        || $suma["name"]->__toString() === "overtureAds";
    }

    public function sumaIsNotAdsuche($suma)
    {
        return
        $suma["name"]->__toString() !== "qualigo"
        && $suma["name"]->__toString() !== "similar_product_ads"
        && $suma["name"]->__toString() !== "overtureAds";
    }

    public function requestIsCached($request)
    {
        return
        $request->has('next')
        && Cache::has($request->input('next'))
        && unserialize(Cache::get($request->input('next')))['page'] > 1;
    }

    public function getCachedEngines($request)
    {
        $next       = unserialize(Cache::get($request->input('next')));
        $this->page = $next['page'];
        $engines    = $next['engines'];
        if (isset($next['startForwards'])) {
            $this->startForwards = $next['startForwards'];
        }
        if (isset($next['startBackwards'])) {
            $this->startBackwards = $next['startBackwards'];
        }
        return $engines;
    }

    public function loadMiniSucher($xml, $subcollections)
    {
        $minisucherEngine             = $xml->xpath('suma[@name="minism"]')[0];
        $subcollections               = urlencode("(" . implode(" OR ", $subcollections) . ")");
        $minisucherEngine["formData"] = str_replace("<<SUBCOLLECTIONS>>", $subcollections, $minisucherEngine["formData"]);
        $minisucherEngine["formData"] = str_replace("<<COUNT>>", sizeof($subcollections) * 10, $minisucherEngine["formData"]);
        return $minisucherEngine;
    }

675
    # Passt den Suchfokus an, falls für einen Fokus genau alle vorhandenen Sumas eingeschaltet sind
676
677
    public function adjustFocus($sumas, $enabledSearchengines)
    {
678
679
        # Findet für alle Foki die enthaltenen Sumas
        $foki = []; # [fokus][suma] => [suma]
680
        foreach ($sumas as $suma) {
681
            if ((!$this->sumaIsDisabled($suma)) && (!isset($suma['userSelectable']) || $suma['userSelectable']->__toString() === "1")) {
682
                if (isset($suma['type'])) {
683
684
685
                    # Wenn foki für diese Suchmaschine angegeben sind
                    $focuses = explode(",", $suma['type']->__toString());
                    foreach ($focuses as $foc) {
686
687
688
689
690
                        if (isset($suma['minismCollection'])) {
                            $foki[$foc][] = "minism";
                        } else {
                            $foki[$foc][] = $suma['name']->__toString();
                        }
691
                    }
692
                } else {
693
                    # Wenn keine foki für diese Suchmaschine angegeben sind
694
695
696
697
698
                    if (isset($suma['minismCollection'])) {
                        $foki["andere"][] = "minism";
                    } else {
                        $foki["andere"][] = $suma['name']->__toString();
                    }
699
700
701
702
                }
            }
        }

703
        # Findet die Namen der aktuell eingeschalteten Sumas
704
        $realEngNames = [];
705
        foreach ($enabledSearchengines as $realEng) {
706
            $nam = $realEng["name"]->__toString();
707
            if ($nam !== "qualigo" && $nam !== "overtureAds" && $nam !== "rlvproduct") {
708
709
710
                $realEngNames[] = $nam;
            }
        }
711

712
        # Anschließend werden diese beiden Listen verglichen (jeweils eine der Fokuslisten für jeden Fokus), um herauszufinden ob sie vielleicht identisch sind. Ist dies der Fall, so hat der Nutzer anscheinend Suchmaschinen eines kompletten Fokus eingestellt. Der Fokus wird dementsprechend angepasst.
713
        foreach ($foki as $fok => $engines) {
714
            $isFokus      = true;
715
            $fokiEngNames = [];
716
            foreach ($engines as $eng) {
717
718
                $fokiEngNames[] = $eng;
            }
719
            # Jede eingeschaltete Engine ist für diesen Fokus geeignet
720
            foreach ($fokiEngNames as $fen) {
721
722
                # Bei Bildersuchen ist uns egal, ob alle Suchmaschinen aus dem Suchfokus eingeschaltet sind, da wir sie eh als Bildersuche anzeigen müssen
                if (!in_array($fen, $realEngNames) && $fok !== "bilder") {
723
724
725
                    $isFokus = false;
                }
            }
726
            # Jede im Fokus erwartete Engine ist auch eingeschaltet
727
728
            foreach ($realEngNames as $ren) {
                if (!in_array($ren, $fokiEngNames)) {
729
730
731
                    $isFokus = false;
                }
            }
732
            # Wenn die Listen identisch sind, setze den Fokus um
733
            if ($isFokus) {
734
735
736
                $this->fokus = $fok;
            }
        }
737
    }
738

739
740
741
742
743
744
745
    public function checkCanNotSitesearch($enabledSearchengines)
    {
        if (strlen($this->site) > 0) {
            $enginesWithSite = 0;
            foreach ($enabledSearchengines as $engine) {
                if (isset($engine['hasSiteSearch']) && $engine['hasSiteSearch']->__toString() === "1") {
                    $enginesWithSite++;
746
                }
747
748
            }
            if ($enginesWithSite === 0) {
749
                $this->errors[] = trans('metaGer.sitesearch.failed', ['site' => $this->site, 'searchLink' => $this->generateSearchLink("web", false)]);
750
751
                return true;
            } else {
752
                $this->warnings[] = trans('metaGer.sitesearch.success', ['site' => $this->site]);
753
                return false;
754
755
            }
        }
756
        return false;
757
758
759
760
    }

    public function waitForResults($enginesToLoad, $overtureEnabled, $canBreak)
    {
761
        $loadedEngines = 0;
762
        $timeStart     = microtime(true);
763
764
765
766

        # Auf wie viele Suchmaschinen warten wir?
        $engineCount = count($enginesToLoad);

767
768
        while (true) {
            $time          = (microtime(true) - $timeStart) * 1000;
769
            $loadedEngines = intval(Redis::hlen('search.' . $this->getHashCode()));
770
            if ($overtureEnabled && (Redis::hexists('search.' . $this->getHashCode(), 'overture') || Redis::hexists('search.' . $this->getHashCode(), 'overtureAds'))) {
771
                $canBreak = true;
772
            }
773
774

            # Abbruchbedingung
775
            if ($time < 500) {
776
                if (($engineCount === 0 || $loadedEngines >= $engineCount) && $canBreak) {
777
                    break;
778
779
780
                }

            } elseif ($time >= 500 && $time < $this->time) {
781
                if (($engineCount === 0 || ($loadedEngines / ($engineCount * 1.0)) >= 0.8) && $canBreak) {
782
                    break;
783
784
785
                }

            } else {
786
787
788
789
                break;
            }
            usleep(50000);
        }
790
791
792
793
794
795
796

        # Wir haben nun so lange wie möglich gewartet. Wir registrieren nun noch die Suchmaschinen, die geanwortet haben.
        $answered = Redis::hgetall('search.' . $this->getHashCode());
        foreach ($answered as $key => $value) {
            $enginesToLoad[$key] = true;
        }
        $this->enginesToLoad = $enginesToLoad;
797
    }
798

799
800
801
    public function retrieveResults($engines)
    {
        # Von geladenen Engines die Ergebnisse holen
802
803
804
        foreach ($engines as $engine) {
            if (!$engine->loaded) {
                try {
805
                    $engine->retrieveResults($this);
806
                } catch (\ErrorException $e) {
807
808
809
810
                    Log::error($e);
                }
            }
        }
811

812
        # Nicht fertige Engines verwefen
813
814
        foreach ($engines as $engine) {
            if (!$engine->loaded) {
815
                $engine->shutdown();
816
            }
817
818
819
        }

        $this->engines = $engines;
820
821
    }

822
823
824
/*
 * Ende Suchmaschinenerstellung und Ergebniserhalt
 */
Karl's avatar
Karl committed
825

826
827
    public function parseFormData(Request $request)
    {
828
        # Sichert, dass der request in UTF-8 formatiert ist
829
830
831
832
833
834
835
836
        if ($request->input('encoding', '') !== "utf8") {
            # In früheren Versionen, als es den Encoding Parameter noch nicht gab, wurden die Daten in ISO-8859-1 übertragen
            $input = $request->all();
            foreach ($input as $key => $value) {
                $input[$key] = mb_convert_encoding("$value", "UTF-8", "ISO-8859-1");
            }
            $request->replace($input);
        }
837
        $this->url = $request->url();
838
        # Zunächst überprüfen wir die eingegebenen Einstellungen:
839
        # Fokus
840
        $this->fokus = $request->input('focus', 'web');
841
        # Suma-File
842
        if (App::isLocale("en")) {
843
            $this->sumaFile = config_path() . "/sumas.xml";
844
        } else {
845
846
            $this->sumaFile = config_path() . "/sumas.xml";
        }
847
        if (!file_exists($this->sumaFile)) {
848
            die(trans('metaGer.formdata.cantLoad'));
849
        }
850
        # Sucheingabe
851
        $this->eingabe = trim($request->input('eingabe', ''));
852
        if (strlen($this->eingabe) === 0) {
853
            $this->warnings[] = trans('metaGer.formdata.noSearch');
854
855
        }
        $this->q = $this->eingabe;
856
        # IP
857
        $this->ip = $request->ip();
858
        # Language
859
        if (isset($_SERVER['HTTP_LANGUAGE'])) {
860
            $this->language = $_SERVER['HTTP_LANGUAGE'];
861
        } else {
862
863
864
865
            $this->language = "";
        }
        # Category
        $this->category = $request->input('category', '');
866
        # Request Times
867
868
        $this->time = $request->input('time', 1000);
        # Page
869
        $this->page = 1;
870
871
        # Lang
        $this->lang = $request->input('lang', 'all');
872
873
        if ($this->lang !== "de" && $this->lang !== "en" && $this->lang !== "all") {
            $this->lang = "all";
874
        }
875
        $this->agent  = new Agent();
876
        $this->mobile = $this->agent->isMobile();
877
        # Sprüche
878
        $this->sprueche = $request->input('sprueche', 'off');
879
        if ($this->sprueche === "off") {
880
            $this->sprueche = true;
881
        } else {
882
            $this->sprueche = false;
883
        }
884
885
886
887
888
889
        $this->maps = $request->input('maps', 'off');
        if ($this->maps === "off") {
            $this->maps = true;
        } else {
            $this->maps = false;
        }
890
        # Theme
Phil Höfer's avatar
Phil Höfer committed
891
        $this->theme = preg_replace("/[^[:alnum:][:space:]]/u", '', $request->input('theme', 'default'));
892
893
894
        # Ergebnisse pro Seite:
        $this->resultCount = $request->input('resultCount', '20');
        # Manchmal müssen wir Parameter anpassen um den Sucheinstellungen gerecht zu werden:
895
896
        if ($request->has('dart')) {
            $this->time       = 10000;
897
            $this->warnings[] = trans('metaGer.formdata.dartEurope');
898
        }
899
900
        if ($this->time <= 500 || $this->time > 20000) {
            $this->time = 1000;
901
        }
902
903
904
905
906
907
908
909
910
        if ($request->has('minism') && ($request->has('fportal') || $request->has('harvest'))) {
            $input    = $request->all();
            $newInput = [];
            foreach ($input as $key => $value) {
                if ($key !== "fportal" && $key !== "harvest") {
                    $newInput[$key] = $value;
                }
            }
            $request->replace($newInput);
911
        }
912
913
        if (App::isLocale("en")) {
            $this->sprueche = "off";
914
        }
915
916
        if ($this->resultCount <= 0 || $this->resultCount > 200) {
            $this->resultCount = 1000;
917
        }
918
919
920
921
922
923
        if ($request->has('onenewspageAll') || $request->has('onenewspageGermanyAll')) {
            $this->time  = 5000;
            $this->cache = "cache";
        }
        if ($request->has('tab')) {
            if ($request->input('tab') === "off") {
924
                $this->tab = "_blank";
925
            } else {
926
927
                $this->tab = "_self";
            }
928
        } else {
929
930
            $this->tab = "_blank";
        }
931
        if ($request->has('password')) {
932
            $this->password = $request->input('password');
933
934
        }
        if ($request->has('quicktips')) {
935
            $this->quicktips = false;
936
        } else {
937
            $this->quicktips = true;
938
        }
939
        $this->out = $request->input('out', "html");
940
        # Standard output format html
941
        if ($this->out !== "html" && $this->out !== "json" && $this->out !== "results" && $this->out !== "results-with-style") {
942
            $this->out = "html";
943
        }
944
        $this->request = $request;
945
946
947
948
    }

    public function checkSpecialSearches(Request $request)
    {
949
950
951
952
953
        if ($request->has('site')) {
            $site = $request->input('site');
        } else {
            $site = "";
        }
954

955
956
957
958
        $this->searchCheckSitesearch($site);
        $this->searchCheckHostBlacklist();
        $this->searchCheckDomainBlacklist();
        $this->searchCheckPhrase();
Karl's avatar
Karl committed
959
        $this->searchCheckStopwords();
960
961
962
963

        if ($this->q === "") {
            $this->warnings[] = trans('metaGer.formdata.noSearch');
        }
964
965
    }

966
    public function searchCheckSitesearch($site)
967
    {
968
        if (preg_match("/(.*)\bsite:(\S+)(.*)/si", $this->q, $match)) {
969
970
971
            $this->site = $match[2];
            $this->q    = $match[1] . $match[3];
        }
972
973
        if ($site !== "") {
            $this->site = $site;
974
        }
975
    }
976

977
    public function searchCheckHostBlacklist()
978
    {
979
        while (preg_match("/(.*)(^|\s)-host:(\S+)(.*)/si", $this->q, $match)) {
980
            $this->hostBlacklist[] = $match[3];
981
            $this->q               = $match[1] . $match[4];
982
983
984
985
986
987
988
        }
        if (sizeof($this->hostBlacklist) > 0) {
            $hostString = "";
            foreach ($this->hostBlacklist as $host) {
                $hostString .= $host . ", ";
            }
            $hostString       = rtrim($hostString, ", ");
989
            $this->warnings[] = trans('metaGer.formdata.hostBlacklist', ['host' => $hostString]);
990
        }
991
    }
992

993
    public function searchCheckDomainBlacklist()
994
    {
995
        while (preg_match("/(.*)(^|\s)-domain:(\S+)(.*)/si", $this->q, $match)) {
996
            $this->domainBlacklist[] = $match[3];
997
            $this->q                 = $match[1] . $match[4];
998
999
1000
1001
1002
1003
1004
        }
        if (sizeof($this->domainBlacklist) > 0) {
            $domainString = "";
            foreach ($this->domainBlacklist as $domain) {
                $domainString .= $domain . ", ";
            }
            $domainString     = rtrim($domainString, ", ");
1005
            $this->warnings[] = trans('metaGer.formdata.domainBlacklist', ['domain' => $domainString]);
1006
        }
1007
    }
1008

1009
    public function searchCheckStopwords()
1010
    {
1011
        while (preg_match("/(.*)(^|\s)-(\S+)(.*)/si", $this->q, $match)) {
1012
            $this->stopWords[] = $match[3];
1013
            $this->q           = $match[1] . $match[4];