LanguageController.php 18.4 KB
Newer Older
1
2
3
4
5
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
6
use App\Models\LanguageObject;
7
8
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
9
use ZipArchive;
10
11
12

class LanguageController extends Controller
{
Aria Givi's avatar
Aria Givi committed
13
14
15
16

    public function __construct() 
    {
       $this->languageFilePath = resource_path()."/lang/";
17
       $this->languages = array('de','en','fr','es','nd');
Aria Givi's avatar
Aria Givi committed
18
19
    }

20
21
    public function createOverview(Request $request)
    {
22
        $languageFolders  = scandir($this->languageFilePath);
23
        $dirs             = [];
24

Aria Givi's avatar
Aria Givi committed
25
26
27
        foreach ($languageFolders as $folder) {
            if (is_dir($this->languageFilePath . $folder) && $folder !== "." && $folder !== "..") {
                $dirs[] = $folder;
28
29
30
31
32
33
34
35
            }
        }
        # Im Array "$dirs" haben wir nun alle Verzeichnisse mit dem entsprechenden Sprachkürzel
        # Alle von uns bislang unterstützen Sprachen sind hier eingetragen.
        $langTexts = [];
        $sum       = [];
        foreach ($dirs as $dir) {
            # Wir überprüfen nun für jede Datei die Anzahl der vorhandenen Übersetzungen
Aria Givi's avatar
Aria Givi committed
36
            $di                           = new RecursiveDirectoryIterator($this->languageFilePath . $dir);
37
38
39
40
41
42
43
            $langTexts[$dir]["textCount"] = 0;
            $langTexts[$dir]["fileCount"] = 0;
            foreach (new RecursiveIteratorIterator($di) as $filename => $file) {
                if (!$this->endsWith($filename, ".")) {
                    $langTexts[$dir]["fileCount"] += 1;
                    $tmp = include $filename;
                    foreach ($tmp as $key => $value) {
44
                        $sum = array_merge($sum, $this->getValues([$key => $value], basename($filename)));
45
                        $langTexts[$dir]["textCount"] += count($this->getValues([$key => $value]));
46
47
48
49
50
51
52
53
54
55
56
57
                    }
                }
            }
        }
        $deComplete = $langTexts["de"]["textCount"] === count($sum) ? true : false;
        return view('languages.overview')
            ->with('title', trans('titles.languages'))
            ->with('langTexts', $langTexts)
            ->with('sum', $sum)
            ->with('deComplete', $deComplete);
    }

58
    public function createEditPage($from, $to, $exclude = "", $email = "")
59
    {
Aria Givi's avatar
Aria Givi committed
60
        $languageFolders  = scandir($this->languageFilePath);
61
        $dirs             = [];
62

Aria Givi's avatar
Aria Givi committed
63
64
65
        foreach ($languageFolders as $folder) {
            if (is_dir($this->languageFilePath . $folder) && $folder !== "." && $folder !== "..") {
                $dirs[$folder] = $folder;
66
67
            }
        }
Aria Givi's avatar
Aria Givi committed
68

69
        # Abbruchbedingungen:
70
        if (!in_array($to, $this->languages) || $from === "" || $to === "" || ($from !== "de" && $from !== "all") || ($from === "all" && $to !== "de") && !array_has($dirs, $to)) {
71
72
73
74
75
            return redirect(url('languages'));
        }

        $texts = [];

76
77
78
        $langTexts = [];
        $sum       = [];
        $filePath  = [];
79
80
81
82
83
84
        foreach ($dirs as $dir) {
            if ($from !== "all" && $dir !== $to && $dir !== $from) {
                continue;
            }

            # Wir überprüfen nun für jede Datei die Anzahl der vorhandenen Übersetzungen
Aria Givi's avatar
Aria Givi committed
85
            $di              = new RecursiveDirectoryIterator($this->languageFilePath . $dir);
86
            $langTexts[$dir] = 0;
87
88
89
90
            foreach (new RecursiveIteratorIterator($di) as $filename => $file) {
                if (!$this->endsWith($filename, ".")) {
                    $tmp = include $filename;
                    foreach ($tmp as $key => $value) {
91
                        $sum                                    = array_merge($sum, $this->getValues([$key => $value], basename($filename)));
92
                        $texts[basename($filename)][$key][$dir] = $value;
93
                        $langTexts[$dir] += count($this->getValues([$key => $value]));
94
                    }
95
                    $filePath[basename($filename)] = preg_replace("/lang\/.*?\//si", "lang/$to/", substr($filename, strpos($filename, "lang")));
96
97
98
                }
            }
        }
99

100
101
        $langs = [];
        $fn    = "";
102
        $t     = [];
Aria Givi's avatar
Aria Givi committed
103
        $ex = $this->decodeExcludedFiles($exclude);
104

105
106
107
        foreach ($texts as $filename => $text) {
            foreach ($ex['files'] as $file) {
                if ($file === $filename) {
Aria Givi's avatar
Aria Givi committed
108
                    continue 2;
109
110
                }
            }
111
112
113
            while ($this->hasToMuchDimensions($text)) {
                $text = $this->deMultidimensionalizeArray($text);
            }
114
            # Hier können wir später die bereits bearbeiteten Dateien ausschließen.
115
            foreach ($text as $textname => $languages) {
116
117
118
119
                if ($languages === "") {
                    continue;
                }

120
121
122
123
124
125
                foreach ($languages as $lang => $value) {
                    if ($lang !== $to) {
                        $langs = array_add($langs, $lang, $lang);
                    }
                }
                if (!isset($languages[$to])) {
126
127
                    $fn = $filePath[$filename];
                    $t  = $text;
128
                    break 2;
129
130
131
                }
            }
        }
132
        $t = $this->htmlEscape($t, $to);
133
        $t = $this->createHints($t, $to);
134
        return view('languages.edit')
135
136
137
138
139
140
141
142
143
            ->with('texts', $t)             //Array mit vorhandenen Übersetzungen der Datei $fn in beiden Sprachen
            ->with('filename', $fn)         //Pfad zur angezeigten Datei
            ->with('title', trans('titles.languages.edit')) 
            ->with('langs', $langs)         //Ausgangssprache (1 Element)
            ->with('to', $to)               //zu bearbeitende Sprache
            ->with('langTexts', $langTexts) //Anzahl der vorhandenen Übersetzungen
            ->with('sum', $sum)             //Alle vorhandenen Texte (in allen Dateien) in beiden Sprachen in einem Array
            ->with('new', $ex["new"])       //
            ->with('email', $email);        //Email-Adresse des Benutzers
144
145
    }

146
    public function createSynopticEditPage(Request $request, $exclude = "") 
147
    {
Aria Givi's avatar
Aria Givi committed
148
        $languageFolders  = scandir($this->languageFilePath); 
149
        # Enthält zu jeder Sprache ein Objekt mit allen Daten
150
        $languageObjects  = [];
151
        $to = []; # Alle vorhandenen Sprachen
152

153
        # Dekodieren ausgeschlossener Dateien anhand des URL-Parameters
Aria Givi's avatar
Aria Givi committed
154
        $ex = $this->decodeExcludedFiles($exclude);
155

156
        # Instanziiere LanguageObject
157
        foreach ($languageFolders as $folder) {
Aria Givi's avatar
Aria Givi committed
158
159
            if (is_dir($this->languageFilePath . $folder) && $folder !== "." && $folder !== "..") {
                $languageObjects[$folder] = new LanguageObject($folder, $this->languageFilePath.$folder);
160
            }
161
162
        }

163
        # Speichere Daten in LanguageObject, überspringe ausgeschlossene Dateien
164
165
166
        foreach ($languageObjects as $folder => $languageObject) {
            $to[] = $folder;
            $di = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($languageObject->filePath));
167
            foreach($di as $filename => $file) {
168
169
170
171
172
                foreach($ex['files'] as $file) {
                    if($file === basename($filename)) {
                        continue 2;
                    }
                }
173
                if(!$this->endsWith($filename, ".")) {
174
175
                    $tmp = include $filename;
                    foreach ($tmp as $key => $value) {
176
                        $languageObject->saveData(basename($filename), $key, $value);
177
                    }
178
                }
179
180
            }
        }
181

182
        $fn = "";
183

184
        # Wähle die erste, unbearbeitete Datei aus
185
186
        foreach($languageObjects as $folder => $languageObject) {
            foreach($languageObject->stringMap as $languageFileName => $languageFile) {
187
188
                $fn = $languageFileName;
                break 2;            
189
190
            }
        }
Aria Givi's avatar
Aria Givi committed
191
192
193

        $snippets = [];

194
        # Speichere den Inhalt der ausgewählten Datei in allen Sprachen in $snippets ab
195
196
197
        foreach($languageObjects as $folder => $languageObject) {
            foreach($languageObject->stringMap as $languageFileName => $languageFile) {
                if($languageFileName === $fn) {
Aria Givi's avatar
Aria Givi committed
198
199
200
                    foreach($languageFile as $key => $value) {
                        $snippets[$key][$languageObject->language] = $value;      
                    }
201
                    continue 2;
202
203
204
205
                }
            }
        }

206
        # Fülle $snippets auf mit leeren Einträgen für übrige Sprachen
Aria Givi's avatar
Aria Givi committed
207
208
209
210
211
212
        foreach($to as $t) {
            foreach($snippets as $key => $langArray) {
                if(!isset($langArray[$t])) {
                    $snippets[$key][$t] = "";
                }
            }
213
        }
214

215
        return view('languages.synoptic')
216
217
218
            ->with('to', $to)           # Alle vorhandenen Sprachen
            ->with('texts', $snippets)         # Array mit Sprachsnippets
            ->with('filename', $fn)     # Name der Datei
219
            ->with('title', trans('titles.languages.edit'));
220
221
    }

222
223
224
225
226
227
228
229
230
231
232
233
234
235
    private function htmlEscape($t, $to)
    {
        foreach ($t as $key => $langTexts) {
            if ($langTexts !== "") {
                foreach ($langTexts as $lang => $text) {
                    if ($lang !== $to) {
                        $t[$key][$lang] = htmlspecialchars($text);
                    }
                }
            }
        }
        return $t;
    }

236
237
238
239
    public function processSynopticPageInput(Request $request, $exclude = "") {

        $filename = $request->input('filename');

240
        # Identifizieren des gedrückten Buttons
241
242
        if(isset($request['nextpage'])) {

243
244
            # Leite weiter zur nächsten Seite
            $ex = [];
Aria Givi's avatar
Aria Givi committed
245

246
247
248
249
250
251
            if ($exclude !== "") {
                try {
                    $ex = unserialize(base64_decode($exclude));
                } catch (\ErrorException $e) {
                    $ex = [];
                }
252

253
254
255
                if (!isset($ex["files"])) {
                    $ex["files"] = [];
                }
Aria Givi's avatar
Aria Givi committed
256
            }
257
258
259
260
261
            if (!isset($ex["new"])) {
                $ex["new"] = 0;
            }
            $ex['files'][] = basename($filename);
            $ex = base64_encode(serialize($ex));
262

263
            return redirect(url('synoptic', ['exclude' => $ex]));
Aria Givi's avatar
Aria Givi committed
264
265

        } elseif(isset($request['download'])) {
266
        # Andernfalls auslesen, zippen und herunterladen der veränderten Dateien 
Aria Givi's avatar
Aria Givi committed
267
268
269
270
         
            $data = [];
            $new  = 0;
            $editedFiles = [];
271

Aria Givi's avatar
Aria Givi committed
272
            foreach ($request->all() as $key => $value) {
273

Aria Givi's avatar
Aria Givi committed
274
275
276
                if ($key === "filename" || $value === "") {
                    continue;
                }
277

Aria Givi's avatar
Aria Givi committed
278
                $key = base64_decode($key);
279

280
                # Pfad zur Datei anhand des Schlüsselnamens rekonstruieren (Schlüssel enthält Sprachkürzel)
Aria Givi's avatar
Aria Givi committed
281
282
283
284
285
286
287
288
                $langdir = $this->extractLanguage($key);
                $filepath = "lang/".$langdir."/".$filename;
     
                if (strpos($key, "_new_") === 0 && $value !== "" || MailController::isEdited($this->processKey($key), $value, $filepath)) {
                    $new++;
                    $editedFiles[$langdir] = $filepath;
                } 
            }     
289

290
            # Erneute Iteration über Request, damit Dateien mitsamt vorherigen Einträgen abgespeichert werden 
Aria Givi's avatar
Aria Givi committed
291
            foreach($request->all() as $key => $value) {
292

Aria Givi's avatar
Aria Givi committed
293
294
295
                if ($key === "filename" || $value === "") {
                    continue;
                }
296

Aria Givi's avatar
Aria Givi committed
297
                $key = base64_decode($key);
298

299
                # Pfad zur Datei anhand des Schlüsselnamens rekonstruieren (Schlüssel enthält Sprachkürzel)
Aria Givi's avatar
Aria Givi committed
300
                $langdir = $this->extractLanguage($key);
301

302
                # Überspringe Datei, falls diese nicht bearbeitet worden ist
Aria Givi's avatar
Aria Givi committed
303
304
305
                if(!isset($editedFiles[$langdir])) {
                    continue;
                }
306

307
                # Key kuerzen, sodass er nur den eigentlichen Keynamen enthält
Aria Givi's avatar
Aria Givi committed
308
309
310
311
                $key = $this->processKey($key);
                
                if (!strpos($key, "#")) {
                    $data[$langdir][$key] = $value;
312
                # Aufdröseln von 2D-Arrays
Aria Givi's avatar
Aria Givi committed
313
314
315
316
317
318
319
320
321
                } else {
                    $ref = &$data;
                    do {
                        $ref = &$ref[$langdir][substr($key, 0, strpos($key, "#"))];
                        $key = substr($key, strpos($key, "#") + 1);
                    } while (strpos($key, "#"));
                    $ref = &$ref[$key];
                    $ref = $value;
                }
322
323
            }

324
325
            if(file_exists("/tmp/langfiles.zip"))
                unlink("/tmp/langfiles.zip");
326

Aria Givi's avatar
Aria Givi committed
327
            $zip = new ZipArchive();
328

329
            if (empty($data) || $zip->open("/tmp/langfiles.zip", ZipArchive::CREATE) !== TRUE) {
Aria Givi's avatar
Aria Givi committed
330
331
332
333
                return redirect(url('synoptic', ['exclude' => $exclude]));
            } 
                
            try{
334
            # Erstelle Ausgabedateien
Aria Givi's avatar
Aria Givi committed
335
336
337
338
339
340
341
342
343
                foreach($data as $lang => $entries) {
                    $output = json_encode($entries, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                    $output = preg_replace("/\{/si", "[", $output);
                    $output = preg_replace("/\}/si", "]", $output);
                    $output = preg_replace("/\": ([\"\[])/si", "\"\t=>\t$1", $output);
                    $output = "<?php\n\nreturn $output;\n";
                    $zip->addEmptyDir($lang);
                    $zip->addFromString($lang."/".$filename, $output);
                }
344

Aria Givi's avatar
Aria Givi committed
345
            $zip->close();
346

347
            return response()->download("/tmp/langfiles.zip", $filename.".zip");
Aria Givi's avatar
Aria Givi committed
348
349
350
                    } catch(ErrorException $e) {
                echo("Failed to write ".$filename);
                }
351
352
        }
    }
Aria Givi's avatar
Aria Givi committed
353

354
355
356
    private function createHints($t, $to)
    {
        foreach ($t as $key => $langTexts) {
357
358
359
            if ($langTexts !== "") {
                foreach ($langTexts as $lang => $text) {
                    if ($lang !== $to) {
360
361
                        if (preg_match("/:\w+/si", $text)) {
                            $t[$key][$lang] = preg_replace("/(:\w+)/si", "<a class=\"text-danger hint\" data-toggle=\"tooltip\" data-trigger=\"hover\" data-placement=\"auto\" title=\"Dies ist ein Variablenname. Er wird dort, wo der Text verwendet wird durch einen dynamischen Wert ersetzt. In der Übersetzung sollte dieser deshalb auch so wie er ist in den Satz integriert werden.\" data-container=\"body\" >$1</a>", $text);
362
                        }
363
364
365
                        if (preg_match("/&lt;.*?&gt;/si", $text)) {
                            $t[$key][$lang] = preg_replace("/(&lt;.*?&gt;)/si", "<a class=\"text-danger hint\" data-toggle=\"tooltip\" data-trigger=\"hover\" data-placement=\"auto\" title=\"Dies ist ein sogenanntes HTML-Tag. Wenn Sie sich das zutrauen, bauen Sie diese HTML Tags gerne so wie sie sind in Ihre Übersetzung ein. Achten Sie hierbei darauf, dass der Text zwischen den Tags auch bei der Übersetzung an der logisch gleichen Stelle von den Tags umfasst ist.\" data-container=\"body\" >$1</a>", $text);
                        }
366

367
                    }
368
369
370
371
372
373
                }
            }
        }
        return $t;
    }

Aria Givi's avatar
Aria Givi committed
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
    private function decodeExcludedFiles($exclude)
    {
        $ex = ['files' => [], 'new' => 0];

        if ($exclude !== "") {
            try {
                $ex = unserialize(base64_decode($exclude));
            } catch (ErrorException $e) {
                $ex = ['files' => [], 'new' => 0];
            }
        }

        return $ex;
    }

389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
    private function getValues($values, $prefix = "")
    {
        $return = [];
        if (!is_array($values)) {
            return $return;
        } else {
            foreach ($values as $key => $value) {
                if (is_array($value)) {
                    $return = array_merge($return, $this->getValues($value, $prefix . $key));
                } elseif (is_string($value)) {
                    $return[$prefix . $key] = $value;
                }
            }
        }

        return $return;
    }

    private function hasToMuchDimensions($t)
    {
        foreach ($t as $key => $value) {
            if (is_array($value)) {
                foreach ($value as $lang => $val) {
                    if (is_array($val)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    private function deMultidimensionalizeArray($t)
    {
        # Es gibt noch Besonderheiten in den Texten
        # Es kann sein, dass statt einem String ein Array aus Strings als Werte existieren.
        # Diese müssen aufgelöst werden:
        $tmp = [];
        foreach ($t as $key => $value) {
            $isArray = false;
            if (is_array($value)) {
                foreach ($value as $lang => $val) {
                    if (is_array($val)) {
                        $isArray = true;
                    }
                }
            } else {
                $tmp[$key] = $value;
                continue;
            }
            if (!$isArray) {
                $tmp[$key] = $value;
            } else {
                $tmp[$key] = "";
                foreach ($value as $lang => $val) {
                    if (is_array($val)) {
                        foreach ($val as $key2 => $val) {
                            $tmp["\t" . $key . "#" . $key2][$lang] = $val;
                        }

                    }
                }
            }

        }
        return $tmp;
456
457
    }

458
    public function startsWith($haystack, $needle)
459
460
461
462
463
    {
        // search backwards starting from haystack length characters from the end
        return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== false;
    }

464
    public function endsWith($haystack, $needle)
465
466
467
468
    {
        // search forward starting from end minus needle length characters
        return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0 && strpos($haystack, $needle, $temp) !== false);
    }
469

Aria Givi's avatar
Aria Givi committed
470
    private function extractLanguage($key) 
471
    {   
472
        # Kürzt bspw. "_new_de_redirect bzw. "de_redirect" zu "de"
473
474
475
476
477
478
479
480
481
482
        preg_match("/^(?:_new_)?([^_]*)/", $key, $matches);
        foreach($matches as $dir) {
            if(strlen($dir) == 2)
                return $dir;
            }
    }

    private function processKey($key) 
    {   
        $key = trim($key);
483
        # Kürzt bspw. "_new_de_redirect bzw. "de_redirect" zu "redirect"
484
485
486
487
488
489
490
491
        preg_match("/^(?:_new_)?(?:[^_]*)_(\w*.?\w*#?.?\w*)/", $key, $matches);
        foreach($matches as $processedKey) {
            if(strpos($processedKey, "_") === FALSE) {
                return $processedKey;
            }
        }
        return $key;
    }
492
}