diff --git a/Dockerfile b/Dockerfile index 3ce57225bed2bbc5d56de5e7f8223a4aaa4ff994..e6b4514e5741feb787aab13d4ab3123fb67ef58a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,12 +33,11 @@ EXPOSE 80 COPY config/nginx.conf /etc/nginx/nginx.conf COPY config/nginx-default.conf /etc/nginx/conf.d/default.conf -COPY . /html +COPY --chown=root:nginx . /html +RUN chmod -R g+w storage bootstrap/cache CMD /etc/init.d/cron start && \ /etc/init.d/php7.3-fpm start && \ /etc/init.d/nginx start && \ /etc/init.d/redis-server start && \ - chmod -R 0777 /html/storage && \ - chmod -R 0777 /html/bootstrap/cache && \ - php artisan worker:spawner + su -s /bin/bash -c 'php artisan requests:fetcher' nginx diff --git a/app/CacheHelper.php b/app/CacheHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..81b7ee56a42b2fe11de752cddb1da5ff01baa780 --- /dev/null +++ b/app/CacheHelper.php @@ -0,0 +1,24 @@ +<?php + +namespace App; + +use Illuminate\Support\Facades\Redis; + +class CacheHelper +{ + + /** + * MetaGer uses a pretty slow harddrive for the configured cache + * That's why we have some processes running to write cache to disk in parallel + */ + public static function put($key, $value, $timeSeconds) + { + $cacherItem = [ + 'timeSeconds' => $timeSeconds, + 'key' => $key, + 'value' => $value, + ]; + Redis::rpush(\App\Console\Commands\RequestCacher::CACHER_QUEUE, json_encode($cacherItem)); + + } +} diff --git a/app/Console/Commands/CacheGC.php b/app/Console/Commands/CacheGC.php new file mode 100644 index 0000000000000000000000000000000000000000..fbe8d1f97517264a706b70effb3349a17ba1bf82 --- /dev/null +++ b/app/Console/Commands/CacheGC.php @@ -0,0 +1,85 @@ +<?php + +namespace App\Console\Commands; + +use Illuminate\Console\Command; + +class CacheGC extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'cache:gc'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Cleans up every expired cache File'; + + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $cachedir = storage_path('framework/cache'); + + $lockfile = $cachedir . "/cache.gc"; + + if (file_exists($lockfile)) { + return; + } else { + touch($lockfile); + } + + try { + foreach (new \DirectoryIterator($cachedir) as $fileInfo) { + if ($fileInfo->isDot()) { + continue; + } + $file = $fileInfo->getPathname(); + $basename = basename($file); + if (!is_dir($file) && $basename !== "cache.gc" && $basename !== ".gitignore") { + $fp = fopen($file, 'r'); + $delete = false; + try { + $time = intval(fread($fp, 10)); + if ($time < time()) { + $delete = true; + } + } finally { + fclose($fp); + } + if ($delete) { + unlink($file); + } + } else if (is_dir($file)) { + // Delete Directory if empty + try { + rmdir($file); + } catch (\ErrorException $e) { + + } + } + } + } finally { + unlink($lockfile); + } + + } +} diff --git a/app/Console/Commands/RequestCacher.php b/app/Console/Commands/RequestCacher.php new file mode 100644 index 0000000000000000000000000000000000000000..39a88827d6d3894cffc16b660e94945ecc502f81 --- /dev/null +++ b/app/Console/Commands/RequestCacher.php @@ -0,0 +1,67 @@ +<?php + +namespace App\Console\Commands; + +use Cache; +use Illuminate\Console\Command; +use Illuminate\Support\Facades\Redis; + +class RequestCacher extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'requests:cacher'; + + const CACHER_QUEUE = 'cacher.queue'; + protected $shouldRun = true; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Listens to a buffer of fetched search results and writes them to the filesystem cache.'; + + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + pcntl_async_signals(true); + pcntl_signal(SIGINT, [$this, "sig_handler"]); + pcntl_signal(SIGTERM, [$this, "sig_handler"]); + pcntl_signal(SIGHUP, [$this, "sig_handler"]); + + while ($this->shouldRun) { + $cacheItem = Redis::blpop(self::CACHER_QUEUE, 1); + if (!empty($cacheItem)) { + $cacheItem = json_decode($cacheItem[1], true); + if (empty($cacheItem["body"])) { + $cacheItem["body"] = "no-result"; + } + Cache::put($cacheItem["hash"], $cacheItem["body"], now()->addMinutes($cacheItem["cacheDuration"])); + } + } + } + + public function sig_handler($sig) + { + $this->shouldRun = false; + echo ("Terminating Cacher Process\n"); + } +} diff --git a/app/Console/Commands/RequestFetcher.php b/app/Console/Commands/RequestFetcher.php new file mode 100644 index 0000000000000000000000000000000000000000..c96a6d9bbd460debfe632e809dd62eb594f2dc6b --- /dev/null +++ b/app/Console/Commands/RequestFetcher.php @@ -0,0 +1,186 @@ +<?php + +namespace App\Console\Commands; + +use Artisan; +use Illuminate\Console\Command; +use Illuminate\Support\Facades\Redis; +use Log; + +class RequestFetcher extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'requests:fetcher'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'This commands fetches requests to the installed search engines'; + + protected $shouldRun = true; + protected $multicurl = null; + protected $proxyhost, $proxyuser, $proxypassword; + + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->multicurl = curl_multi_init(); + $this->proxyhost = env("PROXY_HOST", ""); + $this->proxyport = env("PROXY_PORT", ""); + $this->proxyuser = env("PROXY_USER", ""); + $this->proxypassword = env("PROXY_PASSWORD", ""); + + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $pids = []; + $pid = null; + for ($i = 0; $i < 5; $i++) { + $pid = \pcntl_fork(); + $pids[] = $pid; + if ($pid === 0) { + break; + } + } + if ($pid === 0) { + Artisan::call('requests:cacher'); + exit; + } else { + pcntl_async_signals(true); + pcntl_signal(SIGINT, [$this, "sig_handler"]); + pcntl_signal(SIGTERM, [$this, "sig_handler"]); + pcntl_signal(SIGHUP, [$this, "sig_handler"]); + } + + try { + $blocking = false; + while ($this->shouldRun) { + $status = curl_multi_exec($this->multicurl, $active); + $currentJob = null; + if (!$blocking) { + $currentJob = Redis::lpop(\App\MetaGer::FETCHQUEUE_KEY); + } else { + $currentJob = Redis::blpop(\App\MetaGer::FETCHQUEUE_KEY, 1); + if (!empty($currentJob)) { + $currentJob = $currentJob[1]; + } + } + + if (!empty($currentJob)) { + $currentJob = json_decode($currentJob, true); + $ch = $this->getCurlHandle($currentJob); + curl_multi_add_handle($this->multicurl, $ch); + $blocking = false; + $active = true; + } + + $answerRead = false; + while (($info = curl_multi_info_read($this->multicurl)) !== false) { + $answerRead = true; + $infos = curl_getinfo($info["handle"], CURLINFO_PRIVATE); + $infos = explode(";", $infos); + $resulthash = $infos[0]; + $cacheDurationMinutes = intval($infos[1]); + $responseCode = curl_getinfo($info["handle"], CURLINFO_HTTP_CODE); + $body = ""; + + $error = curl_error($info["handle"]); + if (!empty($error)) { + Log::error($error); + } + + if ($responseCode !== 200) { + Log::debug("Got responsecode " . $responseCode . " fetching \"" . curl_getinfo($info["handle"], CURLINFO_EFFECTIVE_URL) . "\n"); + } else { + $body = \curl_multi_getcontent($info["handle"]); + } + + Redis::pipeline(function ($pipe) use ($resulthash, $body, $cacheDurationMinutes) { + $pipe->set($resulthash, $body); + $pipe->expire($resulthash, 60); + $cacherItem = [ + 'timeSeconds' => $cacheDurationMinutes * 60, + 'key' => $resulthash, + 'value' => $body, + ]; + $pipe->rpush(\App\Console\Commands\RequestCacher::CACHER_QUEUE, json_encode($cacherItem)); + }); + \curl_multi_remove_handle($this->multicurl, $info["handle"]); + } + if (!$active && !$answerRead) { + $blocking = true; + } + } + } finally { + curl_multi_close($this->multicurl); + } + foreach ($pids as $tmppid) { + \pcntl_waitpid($tmppid, $status, WNOHANG); + } + } + + private function getCurlHandle($job) + { + $ch = curl_init(); + + curl_setopt_array($ch, array( + CURLOPT_URL => $job["url"], + CURLOPT_PRIVATE => $job["resulthash"] . ";" . $job["cacheDuration"], + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_USERAGENT => "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1", + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_CONNECTTIMEOUT => 10, + CURLOPT_MAXCONNECTS => 500, + CURLOPT_LOW_SPEED_LIMIT => 500, + CURLOPT_LOW_SPEED_TIME => 5, + CURLOPT_TIMEOUT => 10, + )); + + if (!empty($this->proxyhost) && !empty($this->proxyport) && !empty($this->proxyuser) && !empty($this->proxypassword)) { + curl_setopt($ch, CURLOPT_PROXY, $this->proxyhost); + curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->proxyuser . ":" . $this->proxypassword); + curl_setopt($ch, CURLOPT_PROXYPORT, $this->proxyport); + curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); + } + + if (!empty($job["username"]) && !empty($job["password"])) { + curl_setopt($ch, CURLOPT_USERPWD, $job["username"] . ":" . $job["password"]); + } + + if (!empty($job["headers"])) { + $headers = []; + foreach ($job["headers"] as $key => $value) { + $headers[] = $key . ":" . $value; + } + # Headers are in the Form: + # <key>:<value>;<key>:<value> + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } + + return $ch; + } + + public function sig_handler($sig) + { + $this->shouldRun = false; + echo ("Terminating Process\n"); + } + +} diff --git a/app/Console/Commands/WorkerSpawner.php b/app/Console/Commands/WorkerSpawner.php deleted file mode 100644 index ea55d11c770ec13a11dfe7bbeb414fdae8a5e3ca..0000000000000000000000000000000000000000 --- a/app/Console/Commands/WorkerSpawner.php +++ /dev/null @@ -1,146 +0,0 @@ -<?php - -namespace App\Console\Commands; - -use Illuminate\Console\Command; -use Illuminate\Support\Facades\Redis; - -class WorkerSpawner extends Command -{ - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'worker:spawner'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'This command makes sure that enough worker processes are spawned'; - - protected $shouldRun = true; - protected $processes = []; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Execute the console command. - * - * @return mixed - */ - public function handle() - { - pcntl_async_signals(true); - pcntl_signal(SIGINT, [$this, "sig_handler"]); - pcntl_signal(SIGTERM, [$this, "sig_handler"]); - pcntl_signal(SIGHUP, [$this, "sig_handler"]); - - try { - $counter = 0; - while ($this->shouldRun) { - $counter++; - $counter = $counter % 10; - $length = Redis::llen("queues:default"); - if ($length > 0) { - while (true) { - usleep(50 * 1000); - if (Redis::llen("queues:default") !== $length) { - $length = Redis::llen("queues:default"); - } else { - break; - } - } - $jobs = Redis::lrange("queues:default", 0, -1); - $length = sizeof($jobs) + 5; - $ids = $this->getJobIds($jobs); - for ($i = 0; $i <= $length; $i++) { - $this->processes[] = $this->spawnWorker(); - } - while (sizeof($ids) > 0) { - $jobs = Redis::lrange("queues:default", 0, -1); - $newIds = $this->getJobIds($jobs); - foreach ($ids as $index => $id) { - foreach ($newIds as $newId) { - if ($id === $newId) { - continue 2; - } - } - unset($ids[$index]); - break; - } - } - } else { - usleep(100 * 1000); // Sleep for 100ms - } - if ($counter === 0) { - $newProcs = []; - foreach ($this->processes as $process) { - $infos = proc_get_status($process["process"]); - if (!$infos["running"]) { - fclose($process["pipes"][1]); - proc_close($process["process"]); - } else { - $newProcs[] = $process; - } - } - $this->processes = $newProcs; - } - } - } finally { - foreach ($this->processes as $process) { - fclose($process["pipes"][1]); - proc_close($process["process"]); - } - } - } - - private function getJobIds($jobs) - { - $result = []; - foreach ($jobs as $job) { - $result[] = json_decode($job, true)["id"]; - } - return $result; - } - - private function sig_handler($sig) - { - $this->shouldRun = false; - echo ("Terminating Process\n"); - } - - private function spawnWorker() - { - $descriptorspec = array( - 0 => array("pipe", "r"), // STDIN ist eine Pipe, von der das Child liest - 1 => array("pipe", "w"), // STDOUT ist eine Pipe, in die das Child schreibt - 2 => array("file", "/tmp/worker-error.txt", "a"), // STDERR ist eine Datei, - // in die geschrieben wird - ); - $cwd = getcwd(); - $env = array(); - - $process = proc_open('php artisan queue:work --stop-when-empty --sleep=1', $descriptorspec, $pipes, $cwd, $env); - if (is_resource($process)) { - fclose($pipes[0]); - \stream_set_blocking($pipes[1], 0); - return [ - "process" => $process, - "pipes" => $pipes, - "working" => false, - ]; - } - - } -} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index decf0ba0c2c94d37c3a61365b1236a7c5e7ec965..97ce1d64f4353c535360221484de3219966fcd36 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -27,6 +27,7 @@ class Kernel extends ConsoleKernel { $schedule->command('requests:gather')->everyFifteenMinutes(); $schedule->command('requests:useragents')->everyFiveMinutes(); + $schedule->command('cache:gc')->hourly(); $schedule->call(function () { DB::table('monthlyrequests')->truncate(); diff --git a/app/Http/Controllers/MetaGerSearch.php b/app/Http/Controllers/MetaGerSearch.php index e45a4a0cb9019e886b8966bb8721ac73116a953d..4eaba9979831d0f885f5aa09af29e40a1e64ab0a 100644 --- a/app/Http/Controllers/MetaGerSearch.php +++ b/app/Http/Controllers/MetaGerSearch.php @@ -14,6 +14,7 @@ class MetaGerSearch extends Controller { public function search(Request $request, MetaGer $metager) { + $time = microtime(true); $spamEntries = []; if (file_exists(config_path('spam.txt'))) { $spamEntries = file(config_path('spam.txt')); diff --git a/app/Http/Middleware/HumanVerification.php b/app/Http/Middleware/HumanVerification.php index f9cad046f7d66a9323acbf4230d37b36bd842d0d..f7c61dc15b4736cec50de395a483a38ebbcdeb16 100644 --- a/app/Http/Middleware/HumanVerification.php +++ b/app/Http/Middleware/HumanVerification.php @@ -50,7 +50,7 @@ class HumanVerification # Get all Users of this IP $users = Cache::get($prefix . "." . $id, []); - $users = $this->removeOldUsers($users); + $users = $this->removeOldUsers($prefix, $users); $user = []; if (empty($users[$uid])) { @@ -148,10 +148,10 @@ class HumanVerification // Lock must be acquired within 2 seconds $userList = Cache::get($prefix . "." . $user["id"], []); $userList[$user["uid"]] = $user; - Cache::put($prefix . "." . $user["id"], $userList, now()->addWeeks(2)); + \App\CacheHelper::put($prefix . "." . $user["id"], $userList, 2 * 7 * 24 * 60 * 60); } - public function removeOldUsers($userList) + public function removeOldUsers($prefix, $userList) { $newUserlist = []; $now = now(); @@ -168,10 +168,7 @@ class HumanVerification } if ($changed) { - // Lock must be acquired within 2 seconds - Cache::lock($prefix . "." . $user["id"])->block(2, function () { - Cache::put($prefix . "." . $user["id"], $newUserlist, now()->addWeeks(2)); - }); + \App\CacheHelper::put($prefix . "." . $user["id"], $newUserlist, 2 * 7 * 24 * 60 * 60); } return $newUserlist; diff --git a/app/Jobs/Searcher.php b/app/Jobs/Searcher.php deleted file mode 100644 index df12c992bb422c5db00b665cf2605bbf8b67f6dc..0000000000000000000000000000000000000000 --- a/app/Jobs/Searcher.php +++ /dev/null @@ -1,245 +0,0 @@ -<?php - -namespace App\Jobs; - -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Redis; - -class Searcher implements ShouldQueue -{ - use InteractsWithQueue, Queueable, SerializesModels; - - public $tries = 1; - /** - * The number of seconds the job can run before timing out. - * - * @var int - */ - public $timeout = 300; - - protected $name, $ch, $pid, $counter, $lastTime, $connectionInfo, $user, $password, $headers; - protected $proxyhost, $proxyuser, $proxypassword; - # Each Searcher will shutdown after a specified time(s) or number of requests - protected $MAX_REQUESTS = 100; - # This value should always be below the retry_after value in config/queue.php - protected $MAX_TIME = 240; - protected $startTime = null; - protected $importantEngines = array("Fastbot", "overture", "overtureAds"); - protected $recheck; - - /** - * Create a new job instance. - * This is our new Worker/Searcher Class - * It will take it's name from the sumas.xml as constructor argument - * Each Searcher is dedicated to one remote server from our supported Searchengines - * It will listen to a queue in the Redis Database within the handle() method and - * answer requests to this specific search engine. - * The curl handle will be left initialized and opened so that we can make full use of - * keep-alive requests. - * @return void - */ - public function __construct($name, $user = null, $password = null, $headers = null) - { - $this->name = $name; - $this->pid = getmypid(); - $this->recheck = false; - $this->startTime = microtime(true); - $this->user = $user; - $this->password = $password; - $this->headers = $headers; - $this->proxyhost = env("PROXY_HOST", ""); - $this->proxyport = env("PROXY_PORT", ""); - $this->proxyuser = env("PROXY_USER", ""); - $this->proxypassword = env("PROXY_PASSWORD", ""); - // Submit this worker to the Redis System - Redis::expire($this->name, 5); - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - // This Searches is freshly called so we need to initialize the curl handle $ch - $this->ch = $this->initCurlHandle(); - try { - $this->counter = 0; // Counts the number of answered jobs - $time = microtime(true); - while (true) { - // Update the expire - Redis::expire($this->name, 5); - Redis::expire($this->name . ".stats", 5); - // One Searcher can handle a ton of requests to the same server - // Each search to the server of this Searcher will be submitted to a queue - // stored in redis which has the same name as this searchengine appended by a ".queue" - // We will perform a blocking pop on this queue so the queue can remain empty for a while - // without killing this searcher directly. - $mission = Redis::blpop($this->name . ".queue", 4); - $this->counter++; - $this->updateStats(microtime(true) - $time); - $this->switchToRunning(); - // The mission can be empty when blpop hit the timeout - if (!empty($mission)) { - $mission = $mission[1]; - $poptime = microtime(true) - $time; - - // The mission is a String which can be divided to retrieve three informations: - // 1. The Hash Value where the result should be stored - // 2. The Url to Retrieve - // 3. The maximum time to take - // These three informations are divided by a ";" in the mission string - $mission = explode(";", $mission); - $hashValue = $mission[0]; // The hash value for redis to store the results under - $url = base64_decode($mission[1]); // The url to fetch - $timeout = $mission[2]; // Timeout from the MetaGer process in ms - $medianFetchTime = $this->getFetchTime(); // The median Fetch time of the search engine in ms - Redis::hset('search.' . $hashValue . ".results." . $this->name, "status", "connected"); - $result = $this->retrieveUrl($url); - - $this->storeResult($result, $poptime, $hashValue); - - // Reset the time of the last Job so we can calculate - // the time we have spend waiting for a new job - // We submit that calculation to the Redis systemin the method - $time = microtime(true); - } - - // In sync mode every Searcher may only retrieve one result because it would block - // the execution of the remaining code otherwise: - if (getenv("QUEUE_CONNECTION") === "sync" - || $this->counter > $this->MAX_REQUESTS - || (microtime(true) - $this->startTime) > $this->MAX_TIME) { - break; - } - } - } finally { - // When we reach this point, time has come for this Searcher to retire - $this->shutdown(); - } - } - - private function switchToRunning() - { - /** - * When a Searcher is initially started the redis value for $this->name is set to "locked" - * which effectively will prevent new Searchers of this type to be started. (Value is checked by the MetaGer process which starts the Searchers) - * This is done so the MetaGer processes won't start hundreds of Searchers parallely when under high work load. - * It will force that Searchers can only be started one after the other. - * When a new Searcher has served a minimum of three requests we have enough data to decide whether we need even more Searchers. - * To do so we will then set the redis value for $this->name to "running". - * There is a case where we don't want new Searchers to be started even if we would need to do so to serve every Request: - * When a search engine needs more time to produce search results than the timeout of the MetaGer process, we won't even bother of spawning - * more and more Searchers because they would just block free worker processes from serving the important engines which will give results in time. - **/ - if ($this->counter === 3 || getenv("QUEUE_CONNECTION") === "sync") { - # If the MetaGer process waits longer for the results than this Fetcher will probably need to fetch - # Or if this engine is in the array of important engines which we will always try to serve - Redis::set($this->name, "running"); - $this->recheck = false; - } - } - private function updateStats($poptime) - { - if ($this->connectionInfo !== null) { - $connectionInfo = base64_encode(json_encode($this->connectionInfo)); - Redis::hset($this->name . ".stats", $this->pid, $connectionInfo . ";" . $poptime); - } - } - - private function getFetchTime() - { - $vals = Redis::hgetall($this->name . ".stats"); - if (sizeof($vals) === 0) { - return 0; - } else { - $totalTime = 0; - foreach ($vals as $pid => $value) { - $time = floatval(json_decode(base64_decode(explode(";", $value)[0]), true)["total_time"]); - $time *= 1000; // Transform from seconds to milliseconds - $totalTime += $time; - } - $totalTime /= sizeof($vals); - return $totalTime; - } - } - - private function retrieveUrl($url) - { - // Set this URL to the Curl handle - curl_setopt($this->ch, CURLOPT_URL, $url); - $result = curl_exec($this->ch); - $this->connectionInfo = curl_getinfo($this->ch); - return $result; - } - - private function storeResult($result, $poptime, $hashValue) - { - $redis = Redis::connection(env('REDIS_RESULT_CONNECTION')); - $pipeline = $redis->pipeline(); - $pipeline->hset('search.' . $hashValue . ".results." . $this->name, "response", $result); - $pipeline->hset('search.' . $hashValue . ".results." . $this->name, "delivered", "0"); - $pipeline->hincrby('search.' . $hashValue . ".results.status", "engineAnswered", 1); - // After 60 seconds the results should be read by the MetaGer Process and stored in the Cache instead - $pipeline->expire('search.' . $hashValue . ".results." . $this->name, env('REDIS_RESULT_CACHE_DURATION')); - $pipeline->rpush('search.' . $hashValue . ".ready", $this->name); - $pipeline->expire('search.' . $hashValue . ".ready", env('REDIS_RESULT_CACHE_DURATION')); - $pipeline->sadd('search.' . $hashValue . ".engines", $this->name); - $pipeline->expire('search.' . $hashValue . ".engines", env('REDIS_RESULT_CACHE_DURATION')); - $pipeline->execute(); - $this->lastTime = microtime(true); - } - - private function shutdown() - { - Redis::hdel($this->name . ".stats", $this->pid); - if (sizeof(Redis::hgetall($this->name . ".stats")) === 0) { - Redis::del($this->name); - } - // We should close our curl handle before we do so - curl_close($this->ch); - } - - private function initCurlHandle() - { - $ch = curl_init(); - - curl_setopt_array($ch, array( - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_USERAGENT => "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1", - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_CONNECTTIMEOUT => 10, - CURLOPT_MAXCONNECTS => 500, - CURLOPT_LOW_SPEED_LIMIT => 500, - CURLOPT_LOW_SPEED_TIME => 5, - CURLOPT_TIMEOUT => 10, - )); - - if (!empty($this->proxyhost) && !empty($this->proxyport) && !empty($this->proxyuser) && !empty($this->proxypassword)) { - curl_setopt(CURLOPT_PROXY, $this->proxyhost); - curl_setopt(CURLOPT_PROXYUSERPWD, $this->proxyuser . ":" . $this->proxypassword); - curl_setopt(CURLOPT_PROXYPORT, $this->proxyport); - curl_setopt(CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); - } - - if ($this->user !== null && $this->password !== null) { - curl_setopt($ch, CURLOPT_USERPWD, $this->user . ":" . $this->password); - } - - if ($this->headers !== null) { - $headers = []; - foreach ($this->headers as $key => $value) { - $headers[] = $key . ":" . $value; - } - # Headers are in the Form: - # <key>:<value>;<key>:<value> - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - } - - return $ch; - } -} diff --git a/app/MetaGer.php b/app/MetaGer.php index 5c47ad70f2a1a01f81250035151532366129c043..63c9f5e6871b88075feb406d6509c9cffa315b6f 100644 --- a/app/MetaGer.php +++ b/app/MetaGer.php @@ -14,6 +14,8 @@ use Predis\Connection\ConnectionException; class MetaGer { + const FETCHQUEUE_KEY = "fetcher.queue"; + # Einstellungen für die Suche public $alteredQuery = ""; public $alterationOverrideQuery = ""; @@ -321,7 +323,7 @@ class MetaGer 'page' => $page, 'engines' => $this->next, ]; - Cache::put($this->getSearchUid(), serialize($this->next), 60); + \App\CacheHelper::put($this->getSearchUid(), serialize($this->next), 60 * 60); } else { $this->next = []; } @@ -780,13 +782,12 @@ class MetaGer public function waitForMainResults() { - $redis = Redis::connection(env('REDIS_RESULT_CONNECTION')); $engines = $this->engines; $enginesToWaitFor = []; $mainEngines = $this->sumaFile->foki->{$this->fokus}->main; foreach ($mainEngines as $mainEngine) { foreach ($engines as $engine) { - if (!$engine->cached && $engine->name === $mainEngine) { + if ($engine->name === $mainEngine) { $enginesToWaitFor[] = $engine; } } @@ -803,41 +804,38 @@ class MetaGer } while (sizeof($enginesToWaitFor) > 0 || ($forceTimeout !== null && (microtime(true) - $timeStart) < $forceTimeout)) { - $newEngine = $redis->blpop($this->redisResultWaitingKey, 1); - if ($newEngine === null || sizeof($newEngine) !== 2) { - continue; - } else { - $newEngine = $newEngine[1]; - foreach ($enginesToWaitFor as $index => $engine) { - if ($engine->name === $newEngine) { - unset($enginesToWaitFor[$index]); - break; - } + Log::info(sizeof($enginesToWaitFor) . " " . sizeof($answered) . " " . $enginesToWaitFor[0]->hash); + foreach ($enginesToWaitFor as $index => $engine) { + if (Redis::get($engine->hash) !== null) { + $answered[] = $engine; + unset($enginesToWaitFor[$index]); + break; } - $answered[] = $newEngine; } if ((microtime(true) - $timeStart) >= 2) { break; + } else { + usleep(50 * 1000); } } # Now we can add an entry to Redis which defines the starting time and how many engines should answer this request - - $pipeline = $redis->pipeline(); - $pipeline->hset($this->getRedisEngineResult() . "status", "startTime", $timeStart); - $pipeline->hset($this->getRedisEngineResult() . "status", "engineCount", sizeof($engines)); - $pipeline->hset($this->getRedisEngineResult() . "status", "engineDelivered", sizeof($answered)); - # Add the cached engines as answered - foreach ($engines as $engine) { - if ($engine->cached) { - $pipeline->hincrby($this->getRedisEngineResult() . "status", "engineDelivered", 1); - $pipeline->hincrby($this->getRedisEngineResult() . "status", "engineAnswered", 1); - } - } - foreach ($answered as $engine) { - $pipeline->hset($this->getRedisEngineResult() . $engine, "delivered", "1"); - } - $pipeline->execute(); + /* + $pipeline = $redis->pipeline(); + $pipeline->hset($this->getRedisEngineResult() . "status", "startTime", $timeStart); + $pipeline->hset($this->getRedisEngineResult() . "status", "engineCount", sizeof($engines)); + $pipeline->hset($this->getRedisEngineResult() . "status", "engineDelivered", sizeof($answered)); + # Add the cached engines as answered + foreach ($engines as $engine) { + if ($engine->cached) { + $pipeline->hincrby($this->getRedisEngineResult() . "status", "engineDelivered", 1); + $pipeline->hincrby($this->getRedisEngineResult() . "status", "engineAnswered", 1); + } + } + foreach ($answered as $engine) { + $pipeline->hset($this->getRedisEngineResult() . $engine, "delivered", "1"); + } + $pipeline->execute();*/ } public function retrieveResults() diff --git a/app/Models/Quicktips/Quicktips.php b/app/Models/Quicktips/Quicktips.php index 0480bf36ef8e73a16500d675d8dbd89735ec7bb4..2022b26b2d6976f6046979d28b04af4d4a3c3914 100644 --- a/app/Models/Quicktips/Quicktips.php +++ b/app/Models/Quicktips/Quicktips.php @@ -2,7 +2,7 @@ namespace App\Models\Quicktips; -use App\Jobs\Searcher; +use Cache; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Support\Facades\Redis; use Log; @@ -30,41 +30,23 @@ class Quicktips public function startSearch($search, $locale, $max_time) { $url = $this->quicktipUrl . "?search=" . $this->normalize_search($search) . "&locale=" . $locale; - # TODO anders weitergeben $this->hash = md5($url); - # TODO cache wieder einbauen (eventuell) - if ( /*!Cache::has($hash)*/true) { - $redis = Redis::connection(env('REDIS_RESULT_CONNECTION')); - - $redis->hset("search." . $this->hash . ".results." . self::QUICKTIP_NAME, "status", "waiting"); + if (!Cache::has($this->hash)) { // Queue this search - $mission = $this->hash . ";" . base64_encode($url) . ";" . $max_time; - Redis::rpush(self::QUICKTIP_NAME . ".queue", $mission); - - // Check the current status of Searchers for QUICKTIP_NAME - $needSearcher = false; - $searcherData = Redis::hgetall(self::QUICKTIP_NAME . ".stats"); - - // Create additional Searchers for QUICKTIP_NAME if necessary - if (sizeof($searcherData) === 0) { - $needSearcher = true; - } else { - $median = 0; - foreach ($searcherData as $pid => $data) { - $data = explode(";", $data); - $median += floatval($data[1]); - } - $median /= sizeof($searcherData); - if ($median < .1) { - $needSearcher = true; - } - } - if ($needSearcher && Redis::get(self::QUICKTIP_NAME) !== "locked") { - Redis::set(self::QUICKTIP_NAME, "locked"); - $this->dispatch(new Searcher(self::QUICKTIP_NAME)); - } + $mission = [ + "resulthash" => $this->hash, + "url" => $url, + "username" => null, + "password" => null, + "headers" => [], + "cacheDuration" => self::CACHE_DURATION, + ]; + + $mission = json_encode($mission); + + Redis::rpush(\App\MetaGer::FETCHQUEUE_KEY, $mission); } } @@ -87,13 +69,13 @@ class Quicktips public function retrieveResults($hash) { - $body = ""; - $redis = Redis::connection(env('REDIS_RESULT_CONNECTION')); - $body = $redis->hget('search.' . $hash . ".results." . self::QUICKTIP_NAME, "response"); + $body = null; + + if (Cache::has($this->hash)) { + $body = Cache::get($this->hash); + } - $redis->del('search.' . $hash . ".results." . self::QUICKTIP_NAME); - $redis->del('search.' . $hash . ".ready"); - if ($body !== "") { + if ($body !== null) { return $body; } else { return false; diff --git a/app/Models/Searchengine.php b/app/Models/Searchengine.php index 204bec805d89089740019a03f3ad6ea67a3c6a37..0604f5eac0d824904a2c8fbb1dbedd9c93e0dbe1 100644 --- a/app/Models/Searchengine.php +++ b/app/Models/Searchengine.php @@ -2,16 +2,12 @@ namespace App\Models; -use App\Jobs\Searcher; use App\MetaGer; use Cache; -use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Support\Facades\Redis; abstract class Searchengine { - use DispatchesJobs; - public $getString = ""; # Der String für die Get-Anfrage public $engine; # Die ursprüngliche Engine XML public $totalResults = 0; # How many Results the Searchengine has found @@ -40,17 +36,17 @@ abstract class Searchengine public $counter = 0; # Wird eventuell für Artefakte benötigt public $write_time = 0; # Wird eventuell für Artefakte benötigt public $connection_time = 0; # Wird eventuell für Artefakte benötigt + public $cacheDuration = 60; # Wie lange soll das Ergebnis im Cache bleiben (Minuten) public function __construct($name, \stdClass $engine, MetaGer $metager) { $this->engine = $engine; $this->name = $name; - # Cache Standarddauer 60 - $this->cacheDuration = 60; if (isset($engine->{"cache-duration"}) && $engine->{"cache-duration"} !== -1) { $this->cacheDuration = $engine->{"cache-duration"}; } + $this->cacheDuration = max($this->cacheDuration, 5); $this->useragent = $metager->getUserAgent(); $this->ip = $metager->getIp(); @@ -61,7 +57,12 @@ abstract class Searchengine $this->password = $this->engine->{"http-auth-credentials"}->password; } - $this->headers = $this->engine->{"request-header"}; + if ($this->engine->{"request-header"}) { + $this->headers = []; + foreach ($this->headers as $key => $value) { + $this->headers[$key] = $value; + } + } # Suchstring generieren $q = $metager->getQ(); @@ -87,7 +88,6 @@ abstract class Searchengine $this->getString = $this->generateGetString($q); $this->updateHash(); - $this->resultHash = $metager->getSearchUid(); $this->canCache = $metager->canCache(); } @@ -95,20 +95,15 @@ abstract class Searchengine # Standardimplementierung der getNext Funktion, damit diese immer verwendet werden kann public function getNext(MetaGer $metager, $result) - { } + {} # Prüft, ob die Suche bereits gecached ist, ansonsted wird sie als Job dispatched public function startSearch(\App\MetaGer $metager) { if ($this->canCache && Cache::has($this->hash)) { $this->cached = true; - $this->retrieveResults($metager); + $this->retrieveResults($metager, true); } else { - $redis = Redis::connection(env('REDIS_RESULT_CONNECTION')); - // We will push the confirmation of the submission to the Result Hash - $redis->hset($metager->getRedisEngineResult() . $this->name, "status", "waiting"); - $redis->expire($metager->getRedisEngineResult() . $this->name, env('REDIS_RESULT_CACHE_DURATION')); - // We need to submit a action that one of our workers can understand // The missions are submitted to a redis queue in the following string format // <ResultHash>;<URL to fetch> @@ -126,62 +121,27 @@ abstract class Searchengine $url .= ":" . $this->engine->port; } $url .= $this->getString; - $url = base64_encode($url); - $mission = $this->resultHash . ";" . $url . ";" . $metager->getTime(); + $mission = [ + "resulthash" => $this->hash, + "url" => $url, + "username" => $this->username, + "password" => $this->password, + "headers" => $this->headers, + "cacheDuration" => $this->cacheDuration, + ]; + + $mission = json_encode($mission); + // Submit this mission to the corresponding Redis Queue // Since each Searcher is dedicated to one specific search engine // each Searcher has it's own queue lying under the redis key <name>.queue - Redis::rpush($this->name . ".queue", $mission); - + Redis::rpush(\App\MetaGer::FETCHQUEUE_KEY, $mission); // The request is not cached and will be submitted to the searchengine // We need to check if the number of requests to this engine are limited if (!empty($this->engine->{"monthly-requests"})) { Redis::incr("monthlyRequests:" . $this->name); } - - /** - * We have Searcher processes running for MetaGer - * Each Searcher is dedicated to one specific Searchengine and fetches it's results. - * We can have multiple Searchers for each engine, if needed. - * At this point we need to decide, whether we need to start a new Searcher process or - * if we have enough of them running. - * The information for that is provided through the redis system. Each running searcher - * gives information how long it has waited to be given the last fetcher job. - * The longer this time value is, the less frequent the search engine is used and the less - * searcher of that type we need. - * But if it's too low, i.e. 100ms, then the searcher is near to it's full workload and needs assistence. - **/ - $needSearcher = false; - $searcherData = Redis::hgetall($this->name . ".stats"); - - // We now have an array of statistical data from the searchers - // Each searcher has one entry in it. - // So if it's empty, then we have currently no searcher running and - // of course need to spawn a new one. - if (sizeof($searcherData) === 0) { - $needSearcher = true; - } else { - // There we go: - // There's at least one Fetcher running for this search engine. - // Now we have to check if the current count is enough to fetch all the - // searches or if it needs help. - // Let's hardcode a minimum of 100ms between every search job. - // First calculate the median of all Times - $median = 0; - foreach ($searcherData as $pid => $data) { - $data = explode(";", $data); - $median += floatval($data[1]); - } - $median /= sizeof($searcherData); - if ($median < .1) { - $needSearcher = true; - } - } - if ($needSearcher && Redis::get($this->name) !== "locked") { - Redis::set($this->name, "locked"); - $this->dispatch(new Searcher($this->name, $this->username, $this->password, $this->headers)); - } } } @@ -210,18 +170,17 @@ abstract class Searchengine return true; } - $body = ""; - $redis = Redis::connection(env('REDIS_RESULT_CONNECTION')); - - if ($this->canCache && $this->cacheDuration > 0 && Cache::has($this->hash)) { + $body = null; + if ($this->cached) { $body = Cache::get($this->hash); - } elseif ($redis->hexists($metager->getRedisEngineResult() . $this->name, "response")) { - $body = $redis->hget($metager->getRedisEngineResult() . $this->name, "response"); - if ($this->canCache && $this->cacheDuration > 0) { - Cache::put($this->hash, $body, $this->cacheDuration); + if ($body === "no-result") { + $body = ""; } + } else { + $body = Redis::get($this->hash); } - if ($body !== "" && $body !== "connected" && $body !== "waiting") { + + if ($body !== null) { $this->loadResults($body); $this->getNext($metager, $body); $this->loaded = true; diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index f2634b44cdea21680ac09defb576a1847f6e29a6..892d2979f6f8dade3e9d86e4aef865d0bcc4da86 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -32,6 +32,8 @@ spec: spec: imagePullSecrets: {{ toYaml .Values.image.secrets | indent 10 }} + securityContext: + fsGroup: 2000 volumes: - name: mglogs-persistent-storage persistentVolumeClaim: diff --git a/chart/values.yaml b/chart/values.yaml index 50fbd556249f0e41c58c47f2cc32a27ab52903d9..c3951fa789bf253bdf86061d680c6a3588a3d643 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -68,12 +68,12 @@ postgresql: # stack: gitlab (This is an example. The labels should match the labels on the CloudSQLInstanceClass) resources: -# limits: -# cpu: 100m -# memory: 128Mi + limits: + cpu: 1 + memory: 1Gi requests: - cpu: 500m - memory: 128Mi + cpu: 1 + memory: 1Gi ## Configure PodDisruptionBudget ## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ diff --git a/index.html b/index.html new file mode 100644 index 0000000000000000000000000000000000000000..d4168c9ffcf9c46a1d567938d29804d914f7c612 --- /dev/null +++ b/index.html @@ -0,0 +1,240 @@ +<!DOCTYPE html> +<html lang="de"> + <head> + <meta charset="utf-8" /> + <title>MetaGer - Mehr als eine Suchmaschine</title> + <meta name="description" content="Sicher suchen und finden unter Wahrung der Privatsphäre. Das digitale Wissen der Welt muss ohne Bevormundung durch Staaten oder Konzerne frei zugänglich sein und bleiben." /> + <meta name="keywords" content="Internetsuche, privatsphäre, privacy, Suchmaschine, Datenschutz, Anonproxy, anonym suchen, Bildersuche, Suchmaschine, anonym, MetaGer, metager, metager.de" /> + <meta name="page-topic" content="Dienstleistung" /> + <meta name="robots" content="index,follow" /> + <meta name="revisit-after" content="7 days" /> + <meta name="audience" content="all" /> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> + <link href="/favicon.ico" rel="icon" type="image/x-icon" /> + <link href="/favicon.ico" rel="shortcut icon" type="image/x-icon" /> + <link rel="search" type="application/opensearchdescription+xml" title="MetaGer: Sicher suchen & finden, Privatsphäre schützen" href="https://metager.de/plugins/opensearch.xml"> + <link type="text/css" rel="stylesheet" href="/css/bootstrap.css?id=b803963ec1e03b9de08e" /> + <link type="text/css" rel="stylesheet" href="/css/themes/metager.css?id=35b998573f409cb5260f" /> + <link type="text/css" rel="stylesheet" href="/css/utility.css?id=119a7732fcac8ee992c0" /> + <link href="/fonts/liberationsans/stylesheet.css" rel="stylesheet"> + <link type="text/css" rel="stylesheet" href="/css/fontawesome.css?id=b9dfacd52a93d4406a21" /> + <link type="text/css" rel="stylesheet" href="/css/fontawesome-solid.css?id=ef93547e15423c41f724" /> + <script src="/js/lib.js?id=8794dbf6d3b10d784319"></script> + <script src="/js/utility.js?id=7fab2dc6a328a13d19a0"></script> + </head> + <body> + <header> + </header> + <div class="wrapper startpage"> + <main id="main-content"> + <h1 id="startpage-logo"> + <a href="https://metager.de/"> + <img src="/img/metager.svg" alt="MetaGer" /> + </a> + </h1> + <fieldset> + <form id="searchForm" method=GET action="https://metager.de/meta/meta.ger3 " accept-charset="UTF-8"> + <div class="searchbar startpage-searchbar"> + <div class="search-input-submit"> + <div id="search-key"> + <a id="key-link" class="unauthorized" href="https://metager.de/meta/key?redirUrl=https%3A%2F%2Fmetager.de" data-tooltip="Mitglieder-Key eingeben" tabindex="0"> + <i class="fa fa-key" aria-hidden="true"></i> + </a> + </div> + <div class="search-input"> + <input type="search" name="eingabe" value="" required="" autofocus autocomplete="off" class="form-control" placeholder="MetaGer: Sicher suchen & finden, Privatsphäre schützen" tabindex="0"> + <button id="search-delete-btn" name="delete-search-input" type="button" tabindex="-1"> + × + </button> + </div> + <div class="search-submit" id="submit-inputgroup"> + <button type="submit" tabindex="-1" name="submit-query" title="MetaGer-Suche" aria-label="MetaGer-Suche"> + <i class="fa fa-search" aria-hidden="true"></i> + </button> + </div> + </div> + <div class="search-hidden"> + <input type="hidden" name="focus" value=web> + </div> + <div class="search-custom-hidden"></div> + </div> + </form> +</fieldset> + <div id="plugin-btn-div"> + <a id="plugin-btn" href="https://metager.de/plugin" title="MetaGer zu Ihrem Browser hinzufügen"><i class="fa fa-plug" aria-hidden="true"></i> MetaGer-Plugin hinzufügen</a> + </div> + <div id="about-us"> + <div class="m-row"> + <a href="https://metager.de/about"> + <img alt="lock" src="/img/metager-schloss.svg"> + <span>Garantierte Privatsphäre</span> + <div class="teaser">Mit uns behalten Sie die volle Kontrolle über Ihre Daten. Wir speichern nicht und der Quellcode ist frei.</div> + </a> + <a href="https://suma-ev.de" target="_blank"> + <img alt="rainbow" src="/img/rainbow.svg"> + <span>Vielfältig & Frei</span> + <div class="teaser">MetaGer schützt gegen Zensur, indem es Ergebnisse vieler Suchmaschinen kombiniert.</div> + </a> + </div> + <div class="m-row"> + <a href="https://www.hetzner.de/unternehmen/umweltschutz/" target="_blank"> + <i class="fas fa-leaf" id="green-leaf"></i> + <span>100% Ökostrom</span> + <div class="teaser">Alle unsere Dienste sind mit Strom aus regenerativen Energiequellen betrieben. Nachhaltig und sauber.</div> + </a> + <a href="https://metager.de/spende"> + <i class="fas fa-heart" id="gradient"></i> + <span>Gemeinnütziger Verein</span> + <div class="teaser">Unterstützen Sie MetaGer, indem Sie spenden oder Mitglied im gemeinnützigen Trägerverein werden.</div> + </a> + </div> + </div> + <a id="scroll-helper" href="#about-us"> + <i class="fas fa-angle-double-down"></i> + </a> + </main> + </div> + <input id="sidebarToggle" class="hidden" type="checkbox"> +<div class="sidebar"> + <a class="sidebar-logo" href="https://metager.de/"> + <span> + <img src="/img/metager.svg" alt="MetaGer"></img> + </span> + </a> + <ul class="sidebar-list"> + <li> + <a href="https://metager.de/" id="navigationSuche"> + <i class="fa fa-search" aria-hidden="true"></i> + <span>Suche</span> + </a> + </li> + <hr> + <li> + <a href="https://metager.de/datenschutz" id="navigationPrivacy" > + <i class="fa fa-user-secret" aria-hidden="true"></i> + <span>Datenschutz</span> + </a> + </li> + <li> + <a href="https://metager.de/hilfe" > + <i class="fa fa-info" aria-hidden="true"></i> + <span>Hilfe</span> + </a> + </li> + <hr> + <li> + <a href="https://metager.de/spende" > + <i class="fa fa-donate" aria-hidden="true"></i> + <span>Spenden</span> + </a> + </li> + <li> + <a href="https://metager.de/beitritt" > + <i class="fa fa-users" aria-hidden="true"></i> + <span>Mitglied werden</span> + </a> + </li> + <hr> + <li> + <a href="https://metager.de/app" > + <i class="fa fa-mobile-alt" aria-hidden="true"></i> + <span>MetaGer App</span> + </a> + </li> + <li> + <a class="inlink" href="https://maps.metager.de" target="_blank" > + <i class="fa fa-map" aria-hidden="true"></i> + <span>Maps.MetaGer.de</span> + </a> + </li> + <hr> + <li class="metager-dropdown"> + <input id="contactToggle" class="sidebarCheckbox" type="checkbox"> + <label for="contactToggle" class="metager-dropdown-toggle navigation-element" aria-haspopup="true" id="navigationKontakt" tabindex=0> + <i class="fa fa-comments" aria-hidden="true"></i> + <span>Kontakt</span> + <span class="caret"></span> + </label> + <ul class="metager-dropdown-content"> + <li> + <a href="https://metager.de/kontakt" >Kontakt</a> + </li> + <li> + <a href="https://metager.de/team" >Team</a> + </li> + <li> + <a href="https://metager.de/about" >Über uns</a> + </li> + <li> + <a href="https://metager.de/impressum" >Impressum</a> + </li> + </ul> + </li> + <li class="metager-dropdown"> + <input id="servicesToggle" class="sidebarCheckbox" type="checkbox"> + <label for="servicesToggle" class="metager-dropdown-toggle navigation-element" aria-haspopup="true" tabindex=0> + <i class="fa fa-wrench" aria-hidden="true"></i> + <span>Dienste</span> + <span class="caret"></span> + </label> + <ul class="metager-dropdown-content"> + <li> + <a href="https://metager.de/plugin" >MetaGer Plugin</a> + </li> + <li> + <a href="https://metager.de/widget" >Widget</a> + </li> + <li> + <a href="https://metager.de/zitat-suche" >Zitatsuche</a> + </li> + <li> + <a href="https://metager.de/asso" >Assoziator</a> + </li> + <li> + <a href="https://metager.de/tips" >Tips</a> + </li> + <li> + <a class="outlink" href="https://gitlab.metager3.de/open-source/MetaGer" >MetaGer Quellcode</a> + </li> + <li> + <a class="outlink" href="https://metager.de/tor" >TOR-Hidden-Service</a> + </li> + <li> + <a class="outlink" href="https://shop.spreadshirt.de/metager/" rel="noopener" target="_blank">MetaGer-Fanshop</a> + </li> + <li> + <a class="outlink" href="https://www.wecanhelp.de/430159004" >MetaGer-Fördershops</a> + </li> + </ul> + </li> + <li class="metager-dropdown"> + <input id="languagesToggle" class="sidebarCheckbox" type="checkbox"> + <label for="languagesToggle" class="metager-dropdown-toggle navigation-element" aria-haspopup="true" id="navigationSprache" tabindex=0> + <i class="fa fa-globe" aria-hidden="true"></i> + <span>Sprache (Deutsch)</span> + <span class="caret"></span> + </label> + <ul class="metager-dropdown-content"> + <li> + <a rel="alternate" hreflang="de" href="https://metager.de/" >Deutsch</a> + </li> + <li> + <a rel="alternate" hreflang="en" href="https://metager.de/en" >English</a> + </li> + <li> + <a rel="alternate" hreflang="es" href="https://metager.de/es" >Español</a> + </li> + </ul> + </li> + </ul> +</div> + <label class="sidebar-opener navigation-element fixed" for="sidebarToggle"></label> + <footer class="startPageFooter noprint"> + <div> + <a href="https://metager.de/kontakt">Kontakt</a> + <a href="https://metager.de/impressum">Impressum</a> + <a href="https://metager.de/datenschutz">Datenschutz</a> + </div> + </footer> + </body> +</html> diff --git a/public/img/apple/touch-icon-114.png b/public/img/apple/touch-icon-114.png new file mode 100644 index 0000000000000000000000000000000000000000..d06b3767b78b61ab2375c30d9ced6bc4b143e19e Binary files /dev/null and b/public/img/apple/touch-icon-114.png differ diff --git a/public/img/apple/touch-icon-120.png b/public/img/apple/touch-icon-120.png new file mode 100644 index 0000000000000000000000000000000000000000..076227786eb7681383f2bab7eb0bbd23163d16b5 Binary files /dev/null and b/public/img/apple/touch-icon-120.png differ diff --git a/public/img/apple/touch-icon-144.png b/public/img/apple/touch-icon-144.png new file mode 100644 index 0000000000000000000000000000000000000000..d9a3ac529394f27d0241b8be494e7e6c777db0cc Binary files /dev/null and b/public/img/apple/touch-icon-144.png differ diff --git a/public/img/apple/touch-icon-152.png b/public/img/apple/touch-icon-152.png new file mode 100644 index 0000000000000000000000000000000000000000..9ccbd4c119638bb8cebd76186974f756d7db9b7e Binary files /dev/null and b/public/img/apple/touch-icon-152.png differ diff --git a/public/img/apple/touch-icon-180.png b/public/img/apple/touch-icon-180.png new file mode 100644 index 0000000000000000000000000000000000000000..a50b70798ecbe7dd0b4686d5b55739a7ab24b58f Binary files /dev/null and b/public/img/apple/touch-icon-180.png differ diff --git a/public/img/apple/touch-icon-57.png b/public/img/apple/touch-icon-57.png new file mode 100644 index 0000000000000000000000000000000000000000..d3b6eddf3e35075d4077d8015f31230797c3e551 Binary files /dev/null and b/public/img/apple/touch-icon-57.png differ diff --git a/public/img/apple/touch-icon-72.png b/public/img/apple/touch-icon-72.png new file mode 100644 index 0000000000000000000000000000000000000000..cbd1b3ab57639e109c57f864334dcd6c0032299d Binary files /dev/null and b/public/img/apple/touch-icon-72.png differ diff --git a/public/img/apple/touch-icon-76.png b/public/img/apple/touch-icon-76.png new file mode 100644 index 0000000000000000000000000000000000000000..21e7d1271479a2d1926c2669d7d68072230e02c8 Binary files /dev/null and b/public/img/apple/touch-icon-76.png differ diff --git a/public/img/apple/touch-icon.png b/public/img/apple/touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..28ff01ec02a3e3e82d94b708acd05d03501d102c Binary files /dev/null and b/public/img/apple/touch-icon.png differ diff --git a/resources/lang/de/about.php b/resources/lang/de/about.php index f8aecd231b36597bc7d5773e0a29ac84b927efc4..f9431aecb6c355dd35dec1864006783b4140c847 100644 --- a/resources/lang/de/about.php +++ b/resources/lang/de/about.php @@ -18,7 +18,7 @@ return [ 'list.2' => 'Wir arbeiten nicht gewinnorientiert, wir sind ein <a href="/spende/">gemeinnütziger Verein</a>: Wir haben nicht das Ziel, uns durch Ihre Klicks und schon gar nicht durch Ihre Daten zu bereichern.', 'list.3' => '<a href="https://de.wikipedia.org/wiki/MetaGer" target="_blank" rel="noopener">MetaGer</a> ist primär eine <a href="https://de.wikipedia.org/wiki/Metasuchmaschine" target="_blank" rel="noopener"><em>Meta</em>-Suchmaschine:</a> Wir fragen bis zu 50 Suchmaschinen ab. Damit können wir echte Vielfalt in den Ergebnissen liefern.', 'list.4' => 'Wir bevorzugen in unseren Suchergebnissen nicht das, <a href="https://de.wikipedia.org/wiki/Filterblase" target="_blank" rel="noopener">was viel angeklickt wird</a>: Auch dadurch erhalten Sie nicht nur den Mainstream, sondern Vielfältigkeit.', - 'list.5' => '<a href="https://blog.suma-ev.de/node/207" target="_blank" rel="noopener">MetaGer ist seit mehr als 20 Jahren am Netz</a>, entstanden in der Uni-Hannover am <a href="http://noack-grasdorf.de" target="_blank" rel="noopener">RRZN</a>: Unsere Erfahrung ist Ihr Vorteil - wir wissen was wir tun.', + 'list.5' => 'MetaGer ist seit mehr als 20 Jahren am Netz, entstanden in der Uni-Hannover am <a href="http://noack-grasdorf.de" target="_blank" rel="noopener">RRZN</a>: Unsere Erfahrung ist Ihr Vorteil - wir wissen was wir tun.', 'list.6' => 'Unsere Server werden ausschließlich mit nachhaltig produzierter Energie (Wasserkraft) betrieben.', 'list.7' => 'Aber auch wir sind nicht fehlerfrei: Wenn Ihnen bei uns Merkwürdiges begegnet: Bitte <a href="/kontakt/" target="_blank" rel="noopener">kontaktieren Sie uns</a>! Wir nehmen Ihre Hinweise ernst: <em>Sie</em> sind uns das Wichtigste.', 'head.4' => 'Wie kann ich MetaGer bzw. den SUMA-EV unterstützen?', @@ -28,8 +28,8 @@ return [ Helfen Sie mit, dass freie Suchmaschinen im Internet frei bleiben und stetig weiterentwickelt werden! Das können Sie mit einer Spende auf dieser Seite tun. Oder, wenn Sie freie Suchmaschinen auch langfristig sichern wollen: <a href="https://metager.de/beitritt" target="_blank" rel="noopener">Werden Sie Mitglied im Trägerverein von MetaGer, dem SUMA-EV.</a>', - 'about.3' => 'Für Spenden können Sie unser <a href="/spende" rel="noopener" target="_blank">Spendenformular</a> nutzen. Oder werden Sie Mitglied (<a href="/beitritt" target="_blank" rel="noopener">zum Aufnahmeantrag</a>); Sie haben dann die Möglichkeit, auf den Seiten des SUMA-EV gegebenfalls Ihr Logo mit einem kurzen Text unterzubringen. Wie das aussieht, sehen Sie hier: <a href="https://suma-ev.de/suma-links/index.html#sponsors" target="_blank" rel="noopener">Sponsorenseite</a> und auch auf dieser Seite: <a href="https://suma-ev.de/mitglieder/index.html" target="_blank" rel="noopener">SUMA-EV Mitglieder</a>. Oder <a href="/spende" target="_blank" rel="noopener">werden Sie SUMA-EV Förderer!</a>', - 'about.4' => '<a href="https://suma-ev.de/unterstuetzung/index.html" target="_blank" rel="noopener">JEDE Form Ihrer Unterstützung</a> hilft mit, dass freie Suchmaschinen und freier Wissenszugang im Internet eine Chance haben. Zum freien Wissenszugang gehört es auch, dass Ihre Daten weder überwacht, noch Ihre Internet-Adressen und Verbindungsdaten gespeichert werden. Bei uns wird Ihre Internet-Adresse bereits während der Suche anonymisiert, sie wird nicht gespeichert und nicht an Dritte weitergegeben. Freie Internet-Suche ohne Überwachung: <a href="/" target="_blank" rel="noopener">MetaGer.de!</a>', + 'about.3' => 'Für Spenden können Sie unser <a href="/spende" rel="noopener" target="_blank">Spendenformular</a> nutzen. Oder werden Sie Mitglied (<a href="/beitritt" target="_blank" rel="noopener">zum Aufnahmeantrag</a>); Sie haben dann die Möglichkeit, auf den Seiten des SUMA-EV gegebenfalls Ihr Logo mit einem kurzen Text unterzubringen. Wie das aussieht, sehen Sie hier: <a href="https://suma-ev.de/suma-links/index.html#sponsors" target="_blank" rel="noopener">Sponsorenseite</a> und auch auf dieser Seite: <a href="https://suma-ev.de/mitglieder/liste-unserer-mitglieder/" target="_blank" rel="noopener">SUMA-EV Mitglieder</a>. Oder <a href="/spende" target="_blank" rel="noopener">werden Sie SUMA-EV Förderer!</a>', + 'about.4' => 'JEDE Form Ihrer Unterstützung hilft mit, dass freie Suchmaschinen und freier Wissenszugang im Internet eine Chance haben. Zum freien Wissenszugang gehört es auch, dass Ihre Daten weder überwacht, noch Ihre Internet-Adressen und Verbindungsdaten gespeichert werden. Bei uns wird Ihre Internet-Adresse bereits während der Suche anonymisiert, sie wird nicht gespeichert und nicht an Dritte weitergegeben. Freie Internet-Suche ohne Überwachung: <a href="/" target="_blank" rel="noopener">MetaGer.de!</a>', 'about.5' => 'Eine weitere Möglichkeit, MetaGer zu fördern, besteht darin, dass Sie Ihren nächsten Online-Einkauf bei MetaGer-Fördershops machen. Damit wir auf diesem Weg unterstützt werden können, haben wir uns in das Netzwerk zur Förderung gemeinnützig anerkannter Organisationen eingebracht, das Projekt <a href="https://www.wecanhelp.de/430159004" target="_blank" rel="noopener">www.wecanhelp.de</a> Unter dem Dach dieses Projektes sind ca. 400 Online-Shops (von 1und1 bis Zooplus) vereint, die sich bereit erklärt haben, von allen Verkäufen etwa 6% an das Projekt zu spenden. Statt wie bisher direkt zum Online-Shop zu surfen, gehen Sie zunächst auf <a href="/" target="_blank" rel="noopener">MetaGer.de!</a> und klicken dort oben in der Navigationsleiste auf "Fördern", dann auf <a href="https://www.wecanhelp.de/430159004" target="_blank" rel="noopener">"Einkaufen bei MetaGer Fördershops"</a>. Dieser Klick führt Sie in die Shop-Auswahl des Boost-Projektes. Dort suchen Sie sich Ihren Shop aus und machen wie gewohnt Ihren Einkauf. Oder klicken Sie für Einkäufe aus dem Bereich Schreibwaren, Büro- und Geschenkartikel beispielsweise direkt auf <a href="https://www.gladizon.com/" target="_blank" rel="noopener">www.gladizon.com</a>. Das ist alles. Wenn genug Menschen dies tun, dann brauchen wir keine Werbung mehr. Nur zwei Mausklicks für Sie - für alle eine Chance für den freien Wissenszugang in der digitalen Welt.', ]; diff --git a/resources/lang/de/hilfe.php b/resources/lang/de/hilfe.php index 48affc42981bc8cd4425763c6c24d3f3b20ffb69..ae99a3b509c60d9257793be52fa486044f65d0de 100644 --- a/resources/lang/de/hilfe.php +++ b/resources/lang/de/hilfe.php @@ -95,7 +95,7 @@ return [ "maps.title" => "MetaGer Maps", "maps.1" => 'Die Sicherung der Privatsphäre im Zeitalter der globalen Datenkraken hat uns auch bewogen, <a href="https://maps.metager.de" target="_blank">https://maps.metager.de</a> zu entwickeln: Der (unseres Wissens) einzige Routenplaner, der die vollen Funktionalitäten via Browser und App bietet - ohne dass Nutzer-Standorte gespeichert werden. All dies ist nachprüfbar, denn unsere Softwaren sind Open-Source. Für die Nutzung von maps.metager.de empfehlen wir unsere schnelle App-Version. Unsere Apps können Sie unter <a href="/app" target="_blank">App beziehen</a> downloaden (oder natürlich auch über den Play-Store).', - "maps.2" => "Diese Kartenfunktion kann auch von der MetaGer-Suche aufgerufen werden (und umgekehrt). Sobald Sie bei MetaGer nach einem Begriff gesucht haben, sehen Sie oben rechts einen neuen Suchfokus \"Maps.metager.de\". Beim Klick darauf gelangen Sie zu einer dazugehörigen Karte. Sie können die Funktion unter Einstellungen (Zahnradsymbol) auch dauerhaft einschalten. Wenn Sie sie speichern, so erhalten Sie ab diesem Moment einen Kartenausschnitt in der MetaGer-Ergebnisliste.", + "maps.2" => "Diese Kartenfunktion kann auch von der MetaGer-Suche aufgerufen werden (und umgekehrt). Sobald Sie bei MetaGer nach einem Begriff gesucht haben, sehen Sie oben rechts einen neuen Suchfokus \"Maps\". Beim Klick darauf gelangen Sie zu einer dazugehörigen Karte. .", "maps.3" => "Die Karte zeigt nach dem Aufrufen die auch in der Spalte rechts wiedergegebenen von MetaGer gefundenen Punkte (POIs = Points of Interest). Beim Zoomen passt sich diese Liste an den Kartenausschnitt an. Wenn Sie die Maus über eine Markierung in der Karte oder in der Liste halten, wird das jeweilige Gegenstück hervorgehoben. Klicken Sie auf \"Details\", um genauere Informationen zu diesem Punkt aus der darunter liegenden Datenbank zu erhalten.", 'faq.title' => 'FAQ', diff --git a/resources/lang/de/spende.php b/resources/lang/de/spende.php index eee51d92c5f18af7b7306f7f769971337605d60f..5fbba0d7c06faa73fea1a094a2b3f4824f7b0195 100644 --- a/resources/lang/de/spende.php +++ b/resources/lang/de/spende.php @@ -6,9 +6,9 @@ return [ 'bankinfo.1' => 'Spenden mittels einer Überweisung', 'bankinfo.2' => 'SUMA-EV', - 'bankinfo.2.1' => 'DE64 4306 0967 4075 0332 01', - 'bankinfo.2.2' => 'GENODEM1GLS', - 'bankinfo.2.3' => 'GLS Gemeinschaftsbank, Bochum', + 'bankinfo.2.1' => 'IBAN: DE64 4306 0967 4075 0332 01', + 'bankinfo.2.2' => 'BIC: GENODEM1GLS', + 'bankinfo.2.3' => 'Bank: GLS Gemeinschaftsbank, Bochum', 'bankinfo.2.4' => '(Konto-Nr.: 4075 0332 01, BLZ: 43060967)', 'bankinfo.3' => 'Falls Sie eine Spendenbescheinigung wünschen, teilen Sie uns bitte Ihre vollständige Adresse mit. Bei Spenden bis 200,-€ genügt der Kontoauszug für die Absetzbarkeit beim Finanzamt.', diff --git a/resources/lang/en/about.php b/resources/lang/en/about.php index 2b89ec70fbcbad5e7d844a9b2de38006fe60eef9..f4f6226017ba1924c426a93189a0f4c7348f66c9 100644 --- a/resources/lang/en/about.php +++ b/resources/lang/en/about.php @@ -25,7 +25,7 @@ return [ "about.3" => 'You can use our <a href="/en/spende" rel="noopener" target="_blank">donation form</a> for single donations. If you donate more than 100,-EUR or become a member of <a href="https://suma-ev.de/en/index.html" target="_blank" rel="noopener">SUMA-EV</a> , you or your company can obtain one or more sponsoring links on our <a href="http://suma-ev.de/suma-links/index.html#sponsors" target="_blank" rel="noopener">members and sponsors pages</a> linked to your homepage (if available). If you want that, please tell us within your donation message. <a href="/en/spende" target="_blank" rel="noopener">Or become a SUMA-EV sponsor!</a>', - "about.4" => "<a href=\"https://suma-ev.de/en/support/index.html\" target=\"_blank\" rel=\"noopener\">Any type of support</a> helps open search engines and open knowledge access to have a chance on the Internet. Open Knowledge access also includes that neither your information is monitored, nor your Internet adresses and connection data is collected. We anonymise your Internet adress during search, it is not stored and not passed on. Open Internet search without surveillance: <a href=\"/en/\" target=\"_blank\" rel=\"noopener\">MetaGer.de!</a>", + "about.4" => "Any type of support helps open search engines and open knowledge access to have a chance on the Internet. Open Knowledge access also includes that neither your information is monitored, nor your Internet adresses and connection data is collected. We anonymise your Internet adress during search, it is not stored and not passed on. Open Internet search without surveillance: <a href=\"/en/\" target=\"_blank\" rel=\"noopener\">MetaGer.de!</a>", "about.5" => "Another possibility to help MetaGer is to make your next online purchase via a MetaGer support shop. We became part of the <a href=\"https://www.wecanhelp.de/430159004\" target=\"_blank\" rel=\"noopener\">www.wecanhelp.de</a> to be able to be supported this way. The project unites around 700 online shops (from Amazon to Zooplus), that all declared to donate around 6% of every sale. Instead of directly browsing the online shop, use this link next time: <a href=\"/en/\" target=\"_blank\" rel=\"noopener\">MetaGer.de!</a> and click below the search term box on <a href=\"https://www.wecanhelp.de/430159004\" target=\"_blank\" rel=\"noopener\">\"Purchase at affiliate shop\" - click here!</a> This click brings you to the boost project shop selection. There you choose your shop and keep on shopping as usual. That is all. If enough people do this, we do not need ads anymore. Just two clicks for you - a chance for open knowledge access for the digital world.", ]; diff --git a/resources/lang/en/hilfe.php b/resources/lang/en/hilfe.php index 4ce411d9a0e36ebba75feca59309b2da9db355b0..b9132e535daf3c65615cf0445918335bf52f1991 100644 --- a/resources/lang/en/hilfe.php +++ b/resources/lang/en/hilfe.php @@ -45,7 +45,7 @@ return [ "result.info.more" => '"MORE": you will get more options, the result changes its appearance to:<p><div class="image-container"><img src="/img/hilfe-php-resultpic-en-02.png"></div></p>', "result.info.2" => 'The new options are:', "result.info.saveresult" => '"Save result in TAB" (Only desktop): The result will be stored in a new TAB. It´ s used for collecting results out of several searches. This TAB appears on the right side of your screen. (info: <a href="#searchinsearch"> Search in search</a>)', - "result.info.domainnewsearch" => '"start a new search on this domain": search only on this domain.', + "result.info.domainnewsearch" => '"Start a new search on this domain": search only on this domain.', "result.info.hideresult" => '"Hide": hide results from this domain. You can use this filter directly after your search words (e.g. my search words -site:*.wikipedia.org), filters can be concatenated and the wildcard "*" is allowed. Do one search with a filter and store it as a bookmark. Next time -using the bookmark- you have your settings active immediately.', "urls.title" => "Exclude URLs", @@ -92,10 +92,10 @@ return [ "tor.2" => "MetaGer Tor address: http://b7cxf4dkdsko6ah2.onion/tor/", "proxy.title" => "MetaGer proxy server", - "proxy.1" => "Looking at the MetaGer result page, you will find a link \"open anonymously\" marked by a small lock at the right of every single result. Use this link to hide behind the MetaGer proxy server. The provided protection is limited to the website you reached from our result page. Protection persists while you see https://proxy.suma-ev.de/?url=...in your webbrowser‘s address field.", + "proxy.1" => "Click or touch \"open anonymously\" to use the MetaGer proxy server. The provided protection is limited to the website you reached from our result page. Protection persists while you see https://proxy.suma-ev.de/?url=...in your webbrowser‘s address field.", "maps.title" => "MetaGer maps", - "maps.1" => "MetaGer provides a map function: On a result page you see a new focus on the upper right, called \"maps.MetaGer.de\". You receive a map according to your search by click. Use the \"customize\" page for toggling maps function \"show/hide\", you will get a durably embedded small map on the result page, then.", + "maps.1" => "MetaGer provides a map function (not on metager.org, please use <a href=\"https://www.metager.de/\" target=\"_blank\" rel=\"noopener\">MetaGer.de</a>) : On a result page you see a new focus on the upper right, called \"Maps\". You receive a map according to your search by click.", "maps.2" => "After loading the map shows POIs according to the MetaGer results. You see them in the right column too. Mouseover a POI highlights its counterpart. Click \"Details\" to get further information (Nominatim data base) to this POI.", "maps.3" => "The maps are rendered before (except for the last three ones) and fast available. Affect the zoom level by mouse-wheel or the \"+ / -\" buttons in the upper left corner of the map.", @@ -103,7 +103,7 @@ return [ 'metager.title' => 'MetaGer - General remarks', 'metager.explanation.1' => 'MetaGer is primarily a meta search engine (founded in 1996). Besides that MetaGer maintains a number of specialized crawlers and indexers of its own.', - 'metager.explanation.2' => 'Additionally: meta search engines provide a wider coverage and a better overview, because none of the searchengines knows the whole internet (Read more: <a href="https://en.wikipedia.org/wiki/Metasearch_engine" target="_blank" rel="noopener">Wikipedia</a>. Every result shows its origin in the right top corner. You may try this specific search engine to look for further results. We have grouped all available search services to several search focuses.', + 'metager.explanation.2' => 'Additionally: meta search engines provide a wider coverage and a better overview, because none of the search engines knows the whole internet (Read more: <a href="https://en.wikipedia.org/wiki/Metasearch_engine" target="_blank" rel="noopener">Wikipedia</a>. Every result shows its origin in the right top corner. You may try this specific search engine to look for further results. We have grouped all available search services to several search focuses.', 'searchengine.title' => 'How does MetaGer query other search engines while preserving user anonymity?', 'searchengine.explanation' => 'Since MetaGer is a meta-search engine, every search request you send to us will be stripped of information which could lead to your identification before being sent to e.g. Yahoo. Yahoo will send their response to us which we will then forward to you. This is how we handle every search request sent to us, no matter which one of the offered search engines you use. By doing this we commit ourselves to guarantee your privacy and do not save any personal data. It is possible to verify this, since the source code of MetaGer has been released under a free license <a href=\"https://gitlab.metager3.de/open-source/MetaGer\">(https://gitlab.metager3.de/open-source/MetaGer)</a>. If you have your own website you could try and find out how our <a href=":widget-link" target="_blank">MetaGer-Widget</a> works. You are also free to create links to our search engine.', @@ -119,7 +119,7 @@ return [ 'proposal.title' => 'How can I delete the search suggestions?', 'proposal.explanation' => 'This is provided by your webbrowser. Try to customize the history settings.', - 'assignment.title' => 'How can one match data to indviduals ?', + 'assignment.title' => 'How can one match data to individuals ?', 'assignment.explanation.1' => 'This could be done by cookies. For example a cookie can be set as a part of making an account. You give your data, a cookie is made out of it and is stored on your PC. Next time using the same service it will know you very well. This kind of data is managed by the webbrowser. It is easy to find and erase all cookies from unknown or not confidable origins. You should do this routinely.', 'assignment.explanation.2' => 'The webbrowser sends a heap of data to a website, like the user agent, exact version numbers, the work invironment, the operating system and so on. Maybe there is a matching of data to individuals possible, too.', -]; \ No newline at end of file +]; diff --git a/resources/lang/en/spende.php b/resources/lang/en/spende.php index 7d218f27224148f73173401393e5d2dd9e0c7ae7..54d28234fc63e147b9792626cd3dcb1ddbc573b6 100644 --- a/resources/lang/en/spende.php +++ b/resources/lang/en/spende.php @@ -7,10 +7,10 @@ return [ Please help to keep free and open search engines free and open on the Internet. The digital knowledge of the world must stay free from surveillance and control by governments or companies and must be publicly accessible to everyone. Please click <a href="https://metager.org" rel="noopener" target=_blank>here</a> for more information about us.', "bankinfo.1" => "By bank transfer", "bankinfo.2" => "SUMA-EV", - 'bankinfo.2.1' => 'DE64 4306 0967 4075 0332 01', - 'bankinfo.2.2' => 'GENODEM1GLS', - 'bankinfo.2.3' => 'GLS Gemeinschaftsbank, Bochum', - 'bankinfo.2.4' => '(Konto-Nr.: 4075 0332 01, BLZ: 43060967)', + 'bankinfo.2.1' => 'IBAN: DE64 4306 0967 4075 0332 01', + 'bankinfo.2.2' => 'BIC: GENODEM1GLS', + 'bankinfo.2.3' => 'Bank: GLS Gemeinschaftsbank, Bochum', + 'bankinfo.2.4' => '(AN: 4075 0332 01, BC: 43060967)', "bankinfo.3" => "If you wish to receive a donation receipt,\r\nplease specify your full adress and (if available)\r\nyour E-Mail adress on the money transfer form.", "paypal.1" => "Comfortably with Paypal<br>Via Paypal by credit card too (without PayPal registration),", "paypal.2" => "Donate via Paypal - it's fast, safe and free", diff --git a/resources/lang/es/spende.php b/resources/lang/es/spende.php index fb43cd8eeadcd17f511d400769840f2ea9830e6b..da6816d6d2c0c44c3fde069659d85fd725b97663 100644 --- a/resources/lang/es/spende.php +++ b/resources/lang/es/spende.php @@ -5,10 +5,10 @@ return [ "headline.2" => "Ayuda usted, que en el internet los buscadores libres quedan libre. El conocimiento digital del mundo tiene que ser accesible sin tutela de estados o empresas.", "bankinfo.1" => "Con una transferencia bancaria", "bankinfo.2" => "SUMA-EV", - 'bankinfo.2.1' => 'DE64 4306 0967 4075 0332 01', - 'bankinfo.2.2' => 'GENODEM1GLS', - 'bankinfo.2.3' => 'GLS Gemeinschaftsbank, Bochum', - 'bankinfo.2.4' => '(Konto-Nr.: 4075 0332 01, BLZ: 43060967)', + 'bankinfo.2.1' => 'IBAN: DE64 4306 0967 4075 0332 01', + 'bankinfo.2.2' => 'BIC: GENODEM1GLS', + 'bankinfo.2.3' => 'Banco: GLS Gemeinschaftsbank, Bochum', + 'bankinfo.2.4' => '(NDC: 4075 0332 01, Código: 43060967)', "bankinfo.3" => "En caso que quiere un recibo de donación, por favor ponga su correo electrónico y su dirección completa en el formulario de transferencia.", "paypal.1" => "Cómodo con Paypal ,<br> incluso con tarjeta de crédito y sin registrarse.", "lastschrift.1" => "Donaciones con procedimiento de nota de cargo:", diff --git a/resources/views/layouts/resultPage.blade.php b/resources/views/layouts/resultPage.blade.php index 1c77e9cd05ff7ac38932444a820f3d0f66dbd871..4f349e97d1d5001c8cc9454af2acd9b2df28ea56 100644 --- a/resources/views/layouts/resultPage.blade.php +++ b/resources/views/layouts/resultPage.blade.php @@ -5,6 +5,15 @@ <title>{{ $eingabe }} - MetaGer</title> <link href="/favicon.ico" rel="icon" type="image/x-icon" /> <link href="/favicon.ico" rel="shortcut icon" type="image/x-icon" /> + <link rel="apple-touch-icon" href="/img/apple/touch-icon.png"> + <link rel="apple-touch-icon" sizes="57x57" href="/img/apple/touch-icon-57.png"> + <link rel="apple-touch-icon" sizes="72x72" href="/img/apple/touch-icon-72.png"> + <link rel="apple-touch-icon" sizes="76x76" href="/img/apple/touch-icon-76.png"> + <link rel="apple-touch-icon" sizes="114x114" href="/img/apple/touch-icon-114.png"> + <link rel="apple-touch-icon" sizes="120x120" href="/img/apple/touch-icon-120.png"> + <link rel="apple-touch-icon" sizes="144x144" href="/img/apple/touch-icon-144.png"> + <link rel="apple-touch-icon" sizes="152x152" href="/img/apple/touch-icon-152.png"> + <link rel="apple-touch-icon" sizes="180x180" href="/img/apple/touch-icon-180.png"> <meta content="width=device-width, initial-scale=1.0, user-scalable=no" name="viewport" /> <meta name="p" content="{{ getmypid() }}" /> <meta name="q" content="{{ $eingabe }}" /> diff --git a/resources/views/layouts/staticPages.blade.php b/resources/views/layouts/staticPages.blade.php index f9beaa9db8968d2a79b1b1e79fa5170e6a91ebae..1f92b3cae9c40aca190a8f309924609dea455cb2 100644 --- a/resources/views/layouts/staticPages.blade.php +++ b/resources/views/layouts/staticPages.blade.php @@ -12,6 +12,15 @@ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> <link href="/favicon.ico" rel="icon" type="image/x-icon" /> <link href="/favicon.ico" rel="shortcut icon" type="image/x-icon" /> + <link rel="apple-touch-icon" href="/img/apple/touch-icon.png"> + <link rel="apple-touch-icon" sizes="57x57" href="/img/apple/touch-icon-57.png"> + <link rel="apple-touch-icon" sizes="72x72" href="/img/apple/touch-icon-72.png"> + <link rel="apple-touch-icon" sizes="76x76" href="/img/apple/touch-icon-76.png"> + <link rel="apple-touch-icon" sizes="114x114" href="/img/apple/touch-icon-114.png"> + <link rel="apple-touch-icon" sizes="120x120" href="/img/apple/touch-icon-120.png"> + <link rel="apple-touch-icon" sizes="144x144" href="/img/apple/touch-icon-144.png"> + <link rel="apple-touch-icon" sizes="152x152" href="/img/apple/touch-icon-152.png"> + <link rel="apple-touch-icon" sizes="180x180" href="/img/apple/touch-icon-180.png"> <link rel="search" type="application/opensearchdescription+xml" title="{{ trans('staticPages.opensearch') }}" href="{{ LaravelLocalization::getLocalizedURL(LaravelLocalization::getCurrentLocale(), action('StartpageController@loadPlugin')) }}"> <link type="text/css" rel="stylesheet" href="{{ mix('css/bootstrap.css') }}" /> <link type="text/css" rel="stylesheet" href="{{ mix('css/themes/metager.css') }}" /> diff --git a/storage/app/public/MetaGer-release.apk b/storage/app/public/MetaGer-release.apk old mode 100644 new mode 100755 diff --git a/storage/app/public/aufnahmeantrag-de.pdf b/storage/app/public/aufnahmeantrag-de.pdf old mode 100644 new mode 100755 diff --git a/storage/app/public/aufnahmeantrag-en.pdf b/storage/app/public/aufnahmeantrag-en.pdf old mode 100644 new mode 100755 diff --git a/storage/app/public/stopwords.txt b/storage/app/public/stopwords.txt old mode 100644 new mode 100755