Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.16% covered (danger)
0.16%
1 / 611
4.35% covered (danger)
4.35%
1 / 23
CRAP
0.00% covered (danger)
0.00%
0 / 1
DevDataUtils
0.16% covered (danger)
0.16%
1 / 611
4.35% covered (danger)
4.35%
1 / 23
6289.41
0.00% covered (danger)
0.00%
0 / 1
 fullResetDb
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 resetDbStructure
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 resetDbContent
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 dropDbTables
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
20
 truncateDbTables
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
20
 addDbStructure
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
42
 addDbContent
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
 getCurrentMigration
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 generateMigration
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 migrateTo
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 getDbBackup
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
72
 dumpDb
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getDbStructureSql
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
2
 getDbContentSql
0.00% covered (danger)
0.00%
0 / 48
0.00% covered (danger)
0.00%
0 / 1
110
 clearFiles
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
 addFiles
0.00% covered (danger)
0.00%
0 / 289
0.00% covered (danger)
0.00%
0 / 1
42
 mkdir
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 copy
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 mkimg
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 1
90
 mklog
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
20
 enqueueForTouch
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 touchEnqueued
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 fromEnv
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Olz\Utils;
4
5use Ifsnop\Mysqldump\Mysqldump;
6use Symfony\Component\Console\Input\ArrayInput;
7use Symfony\Component\Console\Output\BufferedOutput;
8
9class DevDataUtils {
10    use WithUtilsTrait;
11
12    /** @var array<string> */
13    private array $enqueuedForTouch = [];
14
15    /** DO NOT CALL THIS FUNCTION ON PROD! */
16    public function fullResetDb(): void {
17        if ($this->envUtils()->getAppEnv() === 'prod') {
18            throw new \Exception("Are you fucking insane? You're on prod!");
19        }
20        // Overwrite database with dev content.
21        $this->dropDbTables();
22        $this->addDbStructure();
23        $this->addDbContent();
24        $this->migrateTo('latest');
25
26        // Initialize the non-code data file system at $data_path
27        $this->clearFiles();
28        $this->addFiles();
29    }
30
31    /** DO NOT CALL THIS FUNCTION ON PROD! */
32    public function resetDbStructure(): void {
33        if ($this->envUtils()->getAppEnv() === 'prod') {
34            throw new \Exception("Are you fucking insane? You're on prod!");
35        }
36        // Overwrite database with dev content.
37        $this->dropDbTables();
38        $this->addDbStructure();
39        $this->addDbContent();
40        $this->migrateTo('latest');
41
42        // Initialize the non-code data file system at $data_path
43        $this->addFiles();
44    }
45
46    /** DO NOT CALL THIS FUNCTION ON PROD! */
47    public function resetDbContent(): void {
48        if ($this->envUtils()->getAppEnv() === 'prod') {
49            throw new \Exception("Are you fucking insane? You're on prod!");
50        }
51        $this->truncateDbTables();
52        $this->addDbContent();
53    }
54
55    /** DO NOT CALL THIS FUNCTION ON PROD! */
56    public function dropDbTables(): void {
57        if ($this->envUtils()->getAppEnv() === 'prod') {
58            throw new \Exception("Are you fucking insane? You're on prod!");
59        }
60        $db = $this->dbUtils()->getDb();
61
62        // Remove all database tables.
63        $beg = microtime(true);
64        $result = $db->query("SHOW TABLES");
65        assert(!is_bool($result));
66        $table_names = [];
67        while ($row = $result->fetch_array()) {
68            $table_name = $row[0];
69            $table_names[] = $table_name;
70        }
71        $db->query('SET foreign_key_checks = 0');
72        foreach ($table_names as $table_name) {
73            $sql = "DROP TABLE `{$table_name}`";
74            $db->query($sql);
75        }
76        $db->query('SET foreign_key_checks = 1');
77        $duration = round(microtime(true) - $beg, 3);
78        $this->log()->debug("Dropping took {$duration}s");
79    }
80
81    /** DO NOT CALL THIS FUNCTION ON PROD! */
82    public function truncateDbTables(): void {
83        if ($this->envUtils()->getAppEnv() === 'prod') {
84            throw new \Exception("Are you fucking insane? You're on prod!");
85        }
86        $db = $this->dbUtils()->getDb();
87
88        // Remove all database tables.
89        $beg = microtime(true);
90        $result = $db->query("SHOW TABLES");
91        assert(!is_bool($result));
92        $table_names = [];
93        while ($row = $result->fetch_array()) {
94            $table_name = $row[0];
95            $table_names[] = $table_name;
96        }
97        $db->query('SET foreign_key_checks = 0');
98        foreach ($table_names as $table_name) {
99            $sql = "TRUNCATE TABLE `{$table_name}`";
100            $db->query($sql);
101        }
102        $db->query('SET foreign_key_checks = 1');
103        $duration = round(microtime(true) - $beg, 3);
104        $this->log()->debug("Truncating took {$duration}s");
105    }
106
107    /** DO NOT CALL THIS FUNCTION ON PROD! */
108    public function addDbStructure(): void {
109        if ($this->envUtils()->getAppEnv() === 'prod') {
110            throw new \Exception("Are you fucking insane? You're on prod!");
111        }
112        $db = $this->dbUtils()->getDb();
113        $dev_data_dir = __DIR__.'/data/';
114
115        // Overwrite database structure with dev content.
116        $beg = microtime(true);
117        $sql_content = file_get_contents("{$dev_data_dir}db_structure.sql") ?: '';
118        if ($db->multi_query($sql_content)) {
119            while ($db->next_result()) {
120                $result = $db->store_result();
121                if ($result) {
122                    $result->free();
123                }
124            }
125        }
126        $duration = round(microtime(true) - $beg, 3);
127        $this->log()->debug("Adding structure took {$duration}s");
128    }
129
130    /** DO NOT CALL THIS FUNCTION ON PROD! */
131    public function addDbContent(): void {
132        if ($this->envUtils()->getAppEnv() === 'prod') {
133            throw new \Exception("Are you fucking insane? You're on prod!");
134        }
135        $db = $this->dbUtils()->getDb();
136        $dev_data_dir = __DIR__.'/data/';
137
138        // Insert dev content into database.
139        $beg = microtime(true);
140        $db->query('SET foreign_key_checks = 0');
141        $sql_content = file_get_contents("{$dev_data_dir}db_content.sql") ?: '';
142        if ($db->multi_query($sql_content)) {
143            while ($db->next_result()) {
144                $result = $db->store_result();
145                if ($result) {
146                    $result->free();
147                }
148            }
149        }
150        $db->query('SET foreign_key_checks = 1');
151        $duration = round(microtime(true) - $beg, 3);
152        $this->log()->debug("Adding content took {$duration}s");
153    }
154
155    public function getCurrentMigration(): ?string {
156        $input = new ArrayInput(['--no-interaction' => true]);
157        $input->setInteractive(false);
158        $output = new BufferedOutput();
159        $this->symfonyUtils()->callCommand(
160            'doctrine:migrations:current',
161            $input,
162            $output
163        );
164        $is_match = preg_match('/^\s*([a-zA-Z0-9\\\]+)(\s|$)/', $output->fetch(), $matches);
165        return $is_match ? $matches[1] : null;
166    }
167
168    public function generateMigration(): string {
169        $input = new ArrayInput([
170            '--no-interaction' => true,
171        ]);
172        $input->setInteractive(false);
173        $output = new BufferedOutput();
174        $this->symfonyUtils()->callCommand(
175            'doctrine:migrations:diff',
176            $input,
177            $output
178        );
179        return $output->fetch();
180    }
181
182    public function migrateTo(string $version = 'latest'): string {
183        $input = new ArrayInput([
184            'version' => $version,
185            '--no-interaction' => true,
186        ]);
187        $input->setInteractive(false);
188        $output = new BufferedOutput();
189        $this->symfonyUtils()->callCommand(
190            'doctrine:migrations:migrate',
191            $input,
192            $output
193        );
194        return $output->fetch();
195    }
196
197    public function getDbBackup(string $key): void {
198        if (!$key || strlen($key) < 10) {
199            throw new \Exception("No valid key");
200        }
201
202        $tmp_dir = __DIR__.'/data/tmp/';
203        if (!is_dir($tmp_dir)) {
204            mkdir($tmp_dir);
205        }
206
207        $plain_path = "{$tmp_dir}backup.plain.sql";
208        $plain_fp = fopen($plain_path, 'w+');
209        assert((bool) $plain_fp);
210        fwrite($plain_fp, $this->getDbStructureSql());
211        fwrite($plain_fp, "\n\n----------\n\n\n");
212        fwrite($plain_fp, $this->getDbContentSql());
213        fclose($plain_fp);
214
215        $cipher_path = "{$tmp_dir}backup.cipher.sql";
216        $cipher_fp = fopen($cipher_path, 'w+');
217        assert((bool) $cipher_fp);
218        $algo = 'aes-256-gcm';
219        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($algo) ?: 0);
220        fwrite($cipher_fp, openssl_encrypt(file_get_contents($plain_path) ?: '', $algo, $key, OPENSSL_RAW_DATA, $iv, $tag) ?: '');
221        fclose($cipher_fp);
222
223        unlink($plain_path);
224
225        echo json_encode([
226            'algo' => $algo,
227            'iv' => base64_encode($iv),
228            'tag' => base64_encode($tag),
229            'ciphertext' => base64_encode(file_get_contents($cipher_path) ?: ''),
230        ]);
231        echo "\n";
232
233        unlink($cipher_path);
234    }
235
236    public function dumpDb(): void {
237        $dev_data_dir = __DIR__.'/data/';
238
239        $sql_structure = $this->getDbStructureSql();
240        $sql_content = $this->getDbContentSql();
241        $sql_structure_path = "{$dev_data_dir}db_structure.sql";
242        $sql_content_path = "{$dev_data_dir}db_content.sql";
243        file_put_contents($sql_structure_path, $sql_structure);
244        file_put_contents($sql_content_path, $sql_content);
245    }
246
247    public function getDbStructureSql(): string {
248        $env_utils = $this->envUtils();
249
250        $current_migration = $this->getCurrentMigration();
251        $sql_content = (
252            "-- Die Struktur der Datenbank der Webseite der OL Zimmerberg\n"
253            ."-- MIGRATION: {$current_migration}\n"
254            ."\n"
255        );
256        $dump_filename = tempnam(__DIR__.'/tmp', 'OLZ');
257        $mysql_server = $env_utils->getMysqlServer();
258        $mysql_schema = $env_utils->getMysqlSchema();
259        $dump = new Mysqldump(
260            "mysql:host={$mysql_server};dbname={$mysql_schema}",
261            $env_utils->getMysqlUsername(),
262            $env_utils->getMysqlPassword(),
263            [
264                'skip-comments' => true,
265                'no-data' => true,
266                // This is the only way to exclude all views:
267                'include-views' => [''], // include only a view which does not exist.
268            ],
269        );
270        $dump->start($dump_filename);
271        $sql_content .= file_get_contents($dump_filename);
272        unlink($dump_filename);
273
274        return $sql_content;
275    }
276
277    public function getDbContentSql(): string {
278        $db = $this->dbUtils()->getDb();
279        $current_migration = $this->getCurrentMigration();
280        $sql_content = (
281            "-- Der Test-Inhalt der Datenbank der Webseite der OL Zimmerberg\n"
282            ."-- MIGRATION: {$current_migration}\n"
283            ."\n"
284            ."SET SQL_MODE = \"NO_AUTO_VALUE_ON_ZERO\";\n"
285            ."SET AUTOCOMMIT = 0;\n"
286            ."START TRANSACTION;\n"
287            ."SET time_zone = \"+00:00\";\n"
288        );
289
290        $res_tables = $db->query('SHOW TABLES');
291        assert(!is_bool($res_tables));
292        while ($row_tables = $res_tables->fetch_row()) {
293            $table_name = $row_tables[0];
294            $sql_content .= "\n";
295            $sql_content .= "-- Table {$table_name}\n";
296            $res_contents = $db->query("SELECT * FROM `{$table_name}`");
297            assert(!is_bool($res_contents));
298            if ($table_name === 'counter') {
299                $sql_content .= "-- (counter omitted)\n";
300            } elseif ($table_name === 'auth_requests') {
301                $sql_content .= "-- (auth_requests omitted)\n";
302            } elseif ($res_contents->num_rows > 0) {
303                $sql_content .= "INSERT INTO {$table_name}\n";
304                $content_fields = $res_contents->fetch_fields();
305                $field_names = [];
306                foreach ($content_fields as $field) {
307                    $field_names[] = $field->name;
308                }
309                $field_names_sql = implode('`, `', $field_names);
310                $sql_content .= "    (`{$field_names_sql}`)\n";
311                $sql_content .= "VALUES\n";
312                $first = true;
313                while ($row_contents = $res_contents->fetch_assoc()) {
314                    if ($first) {
315                        $first = false;
316                    } else {
317                        $sql_content .= ",\n";
318                    }
319                    $field_values = [];
320                    foreach ($field_names as $name) {
321                        $content = $row_contents[$name] ?? null;
322                        if ($content === null) {
323                            $field_values[] = 'NULL';
324                        } else {
325                            $sane_content = $db->escape_string("{$content}");
326                            $field_values[] = "'{$sane_content}'";
327                        }
328                    }
329                    $field_values_sql = implode(', ', $field_values);
330                    $sql_content .= "    ({$field_values_sql})";
331                }
332                $sql_content .= ";\n";
333            }
334        }
335        $sql_content .= "\n";
336        $sql_content .= "COMMIT;\n";
337        return $sql_content;
338    }
339
340    /** DO NOT CALL THIS FUNCTION ON PROD! */
341    public function clearFiles(): void {
342        if ($this->envUtils()->getAppEnv() === 'prod') {
343            throw new \Exception("Are you fucking insane? You're on prod!");
344        }
345        $data_path = $this->envUtils()->getDataPath();
346        $general_utils = $this->generalUtils();
347
348        // Remove existing data.
349        $general_utils->removeRecursive("{$data_path}downloads");
350        $general_utils->removeRecursive("{$data_path}files");
351        $general_utils->removeRecursive("{$data_path}img");
352        $general_utils->removeRecursive("{$data_path}movies");
353        $general_utils->removeRecursive("{$data_path}olz_mitglieder");
354        $general_utils->removeRecursive("{$data_path}OLZimmerbergAblage");
355        $general_utils->removeRecursive("{$data_path}panini_data");
356        $general_utils->removeRecursive("{$data_path}pdf");
357        $general_utils->removeRecursive("{$data_path}results");
358        $general_utils->removeRecursive("{$data_path}temp");
359    }
360
361    /** DO NOT CALL THIS FUNCTION ON PROD! */
362    public function addFiles(): void {
363        if ($this->envUtils()->getAppEnv() === 'prod') {
364            throw new \Exception("Are you fucking insane? You're on prod!");
365        }
366        $private_path = $this->envUtils()->getPrivatePath();
367        $data_path = $this->envUtils()->getDataPath();
368
369        $sample_path = __DIR__.'/data/sample-data/';
370
371        $this->enqueuedForTouch = [];
372
373        // Build downloads/
374        $this->mkdir("{$data_path}downloads");
375
376        // Build files/
377        $this->mkdir("{$data_path}files");
378        $this->mkdir("{$data_path}files/downloads");
379        $this->mkdir("{$data_path}files/downloads/1");
380        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/downloads/1/MIGRATED0000000000010001.pdf");
381        $this->mkdir("{$data_path}files/downloads/3");
382        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/downloads/3/XV4x94BJaf2JCPWvB8DDqTyt.pdf");
383
384        $this->mkdir("{$data_path}files/news");
385        $this->mkdir("{$data_path}files/news/3");
386        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/news/3/MIGRATED0000000000030001.pdf");
387        $this->mkdir("{$data_path}files/news/4");
388        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/news/4/xMpu3ExjfBKa8Cp35bcmsDgq.pdf");
389        $this->mkdir("{$data_path}files/news/10");
390        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/news/10/gAQa_kYXqXTP1_DKKU1s1pGr.csv");
391        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/news/10/8kCalo9sQtu2mrgrmMjoGLUW.pdf");
392        $this->mkdir("{$data_path}files/news/6403");
393        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/news/6403/MIGRATED0000000064030001.pdf");
394
395        $this->mkdir("{$data_path}files/questions");
396        $this->mkdir("{$data_path}files/questions/1");
397        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/questions/1/4a7J72vVQFrqkboyD358S4cf.pdf");
398
399        $this->mkdir("{$data_path}files/roles");
400        $this->mkdir("{$data_path}files/roles/5");
401        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/roles/5/c44s3s8QjwZd2WYTEVg3iW9k.pdf");
402
403        $this->mkdir("{$data_path}files/snippets");
404        $this->mkdir("{$data_path}files/snippets/24");
405        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/snippets/24/AXfZYP3eyLKTWJmfBRGTua7H.pdf");
406
407        $this->mkdir("{$data_path}files/termine");
408        $this->mkdir("{$data_path}files/termine/2");
409        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/termine/2/MIGRATED0000000000020001.pdf");
410        $this->mkdir("{$data_path}files/termine/5");
411        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/termine/5/MIGRATED0000000000050001.pdf");
412        $this->mkdir("{$data_path}files/termine/7");
413        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/termine/7/Kzt5p5g6cjM5k9CXdVaSsGFx.pdf");
414
415        $this->mkdir("{$data_path}files/termin_labels");
416        $this->mkdir("{$data_path}files/termin_labels/3");
417        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/termin_labels/3/6f6novQPv2fjHGzzguXE6nzi.pdf");
418        $this->mkdir("{$data_path}files/termin_labels/4");
419        $this->copy("{$sample_path}sample-icon_20.svg", "{$data_path}files/termin_labels/4/EM8hA6vye74doeon2RWzZyRf.svg");
420
421        $this->mkdir("{$data_path}files/termin_templates");
422        $this->mkdir("{$data_path}files/termin_templates/2");
423        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}files/termin_templates/2/qjhUey6Lc6svXsmUcSaguWkJ.pdf");
424
425        // Build img/
426        $this->mkdir("{$data_path}img");
427        $this->mkdir("{$data_path}img/weekly_picture");
428        $this->mkdir("{$data_path}img/weekly_picture/1");
429        $this->mkdir("{$data_path}img/weekly_picture/1/img");
430        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/weekly_picture/1/img/ed48ksmyjVgRsaKXUXmmcbRN.jpg", 800, 600);
431        $this->mkdir("{$data_path}img/weekly_picture/2");
432        $this->mkdir("{$data_path}img/weekly_picture/2/img");
433        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/weekly_picture/2/img/C8k84ncvWyVptk6kjtMJxTUu.jpg", 800, 600);
434        $this->mkdir("{$data_path}img/fuer_einsteiger");
435        $this->mkdir("{$data_path}img/fuer_einsteiger/img");
436        $this->mkdir("{$data_path}img/fuer_einsteiger/thumb");
437        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/orientierungslauf_001.jpg", 800, 600);
438        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/orientierungslauf_002.jpg", 800, 600);
439        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/orientierungslauf_003.jpg", 800, 600);
440        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/orientierungslauf_004.jpg", 800, 600);
441        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/was_ist_ol_001.jpg", 800, 600);
442        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_001.jpg", 800, 600);
443        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_002.jpg", 800, 600);
444        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_003.jpg", 800, 600);
445        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_004.jpg", 800, 600);
446        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_005.jpg", 800, 600);
447        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_006.jpg", 800, 600);
448        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_007.jpg", 800, 600);
449        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_008.jpg", 800, 600);
450        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_009.jpg", 800, 600);
451        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_010.jpg", 800, 600);
452        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_011.jpg", 800, 600);
453        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_012.jpg", 800, 600);
454        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_013.jpg", 800, 600);
455        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_014.jpg", 800, 600);
456        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_015.jpg", 800, 600);
457        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ol_zimmerberg_016.jpg", 800, 600);
458        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/wie_anfangen_001.jpg", 800, 600);
459        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/wie_anfangen_002.jpg", 800, 600);
460        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/wie_anfangen_003.jpg", 800, 600);
461        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/wie_anfangen_004.jpg", 800, 600);
462        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_001.jpg", 800, 600);
463        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_002.jpg", 800, 600);
464        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_003.jpg", 800, 600);
465        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_004.jpg", 800, 600);
466        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_005.jpg", 800, 600);
467        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_006.jpg", 800, 600);
468        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_007.jpg", 800, 600);
469        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_008.jpg", 800, 600);
470        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_009.jpg", 800, 600);
471        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_010.jpg", 800, 600);
472        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_011.jpg", 800, 600);
473        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_012.jpg", 800, 600);
474        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_013.jpg", 800, 600);
475        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_014.jpg", 800, 600);
476        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_015.jpg", 800, 600);
477        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/trainings_016.jpg", 800, 600);
478        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/pack_die_chance_001.jpg", 800, 600);
479        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ansprechperson_001.jpg", 800, 600);
480        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ansprechperson_002.jpg", 800, 600);
481        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ansprechperson_003.jpg", 800, 600);
482        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/fuer_einsteiger/img/ansprechperson_004.jpg", 800, 600);
483
484        // Generate thumbs
485        if (function_exists('shell_exec')) {
486            $thumbize_src = __DIR__."/../../tools/fuer_einsteiger/thumbize.sh";
487            $thumbize_dest = "{$data_path}img/fuer_einsteiger/thumbize.sh";
488            if (is_file($thumbize_dest)) {
489                unlink($thumbize_dest);
490            }
491            $this->copy($thumbize_src, $thumbize_dest);
492            $pwd = getcwd();
493            chdir("{$data_path}img/fuer_einsteiger");
494            shell_exec("sh ./thumbize.sh");
495            if ($pwd) {
496                chdir($pwd);
497            }
498        }
499
500        $this->mkdir("{$data_path}img/karten");
501        $this->mkdir("{$data_path}img/karten/1");
502        $this->mkdir("{$data_path}img/karten/1/img");
503        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/karten/1/img/MIGRATED0000000000010001.jpg", 800, 600);
504        $this->mkdir("{$data_path}img/karten/3");
505        $this->mkdir("{$data_path}img/karten/3/img");
506        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/karten/3/img/6R3bpgwcCU3SfUF8vCpepzRJ.jpg", 800, 600);
507
508        $this->mkdir("{$data_path}img/news");
509        $this->mkdir("{$data_path}img/news/3");
510        $this->mkdir("{$data_path}img/news/3/img");
511        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/3/img/MIGRATED0000000000030001.jpg", 800, 600);
512        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/3/img/MIGRATED0000000000030002.jpg", 800, 600);
513        $this->mkdir("{$data_path}img/news/4");
514        $this->mkdir("{$data_path}img/news/4/img");
515        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/4/img/xkbGJQgO5LFXpTSz2dCnvJzu.jpg", 800, 600);
516        $this->mkdir("{$data_path}img/news/6");
517        $this->mkdir("{$data_path}img/news/6/img");
518        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/6/img/eGbiJQgOyLF5p6S92kC3vTzE.jpg", 600, 800);
519        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/6/img/Frw83uTOyLF5p6S92kC7zpEW.jpg", 800, 600);
520        $this->mkdir("{$data_path}img/news/7");
521        $this->mkdir("{$data_path}img/news/7/img");
522        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/7/img/aRJIflbxtkF5p6S92k470912.jpg", 800, 600);
523        $this->mkdir("{$data_path}img/news/8");
524        $this->mkdir("{$data_path}img/news/8/img");
525        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/8/img/9GjbtlsSu96AWZ-oH0rHjxup.jpg", 800, 600);
526        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/8/img/zUXE3aKfbK3edmqS35FhaF8g.jpg", 800, 600);
527        $this->mkdir("{$data_path}img/news/10");
528        $this->mkdir("{$data_path}img/news/10/img");
529        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/10/img/DvDB8QkHcGuxQ4lAFwyvHnVd.jpg", 800, 600);
530        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/10/img/OOVJIqrWlitR_iTZuIIhztKC.jpg", 800, 600);
531        $this->mkdir("{$data_path}img/news/1201");
532        $this->mkdir("{$data_path}img/news/1201/img");
533        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1201/img/MIGRATED0000000012010001.jpg", 800, 600);
534        $this->mkdir("{$data_path}img/news/1202");
535        $this->mkdir("{$data_path}img/news/1202/img");
536        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1202/img/MIGRATED0000000012020001.jpg", 800, 600);
537        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1202/img/MIGRATED0000000012020002.jpg", 800, 600);
538        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1202/img/MIGRATED0000000012020003.jpg", 600, 800);
539        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1202/img/MIGRATED0000000012020004.jpg", 800, 600);
540        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1202/img/MIGRATED0000000012020005.jpg", 800, 300);
541        $this->mkdir("{$data_path}img/news/1203");
542        $this->mkdir("{$data_path}img/news/1203/img");
543        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1203/img/MIGRATED0000000012030001.jpg", 800, 600);
544        $this->mkdir("{$data_path}img/news/1203/thumb");
545        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1203/thumb/MIGRATED0000000012030001.jpg\$128.jpg", 128, 96);
546        $this->mkdir("{$data_path}img/news/1206");
547        $this->mkdir("{$data_path}img/news/1206/img");
548        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1206/img/MIGRATED0000000012060001.jpg", 800, 600);
549        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1206/img/MIGRATED0000000012060002.jpg", 800, 600);
550        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1206/img/MIGRATED0000000012060003.jpg", 800, 600);
551        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1206/img/MIGRATED0000000012060004.jpg", 800, 600);
552        $this->mkdir("{$data_path}img/news/1206/thumb");
553        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1206/thumb/MIGRATED0000000012060001.jpg\$128.jpg", 128, 96);
554        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1206/thumb/MIGRATED0000000012060002.jpg\$128.jpg", 128, 96);
555        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1206/thumb/MIGRATED0000000012060003.jpg\$128.jpg", 128, 96);
556        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/1206/thumb/MIGRATED0000000012060004.jpg\$128.jpg", 128, 96);
557        $this->mkdir("{$data_path}img/news/6401");
558        $this->mkdir("{$data_path}img/news/6401/img");
559        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/6401/img/MIGRATED0000000064010001.jpg", 800, 600);
560        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/6401/img/MIGRATED0000000064010002.jpg", 800, 600);
561        $this->mkdir("{$data_path}img/news/6403");
562        $this->mkdir("{$data_path}img/news/6403/img");
563        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/news/6403/img/MIGRATED0000000064030001.jpg", 800, 600);
564
565        $this->mkdir("{$data_path}img/questions");
566        $this->mkdir("{$data_path}img/questions/1");
567        $this->mkdir("{$data_path}img/questions/1/img");
568        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/questions/1/img/bKfDuE4hocQhbw9FGMD7WCW3.jpg", 800, 600);
569
570        $this->mkdir("{$data_path}img/roles");
571        $this->mkdir("{$data_path}img/roles/5");
572        $this->mkdir("{$data_path}img/roles/5/img");
573        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/roles/5/img/ZntVatFCHj3h8KZh7LyiB9x5.jpg", 800, 600);
574
575        $this->mkdir("{$data_path}img/snippets");
576        $this->mkdir("{$data_path}img/snippets/24");
577        $this->mkdir("{$data_path}img/snippets/24/img");
578        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/snippets/24/img/oCGvpb96V6bZNLoQNe8djJgw.jpg", 800, 600);
579
580        $this->mkdir("{$data_path}img/termine");
581        $this->mkdir("{$data_path}img/termine/5");
582        $this->mkdir("{$data_path}img/termine/5/img");
583        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/termine/5/img/Ffpi3PK5wBjKfN4etpvGK3ti.jpg", 800, 600);
584        $this->mkdir("{$data_path}img/termine/10");
585        $this->mkdir("{$data_path}img/termine/10/img");
586        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/termine/10/img/659gCbqzigX8D37XgWMbedB3.jpg", 800, 600);
587        $this->mkdir("{$data_path}img/termine/13");
588        $this->mkdir("{$data_path}img/termine/13/img");
589        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/termine/13/img/8EKYh2n8DZWShYMWo9ZRnor5.jpg", 800, 600);
590
591        $this->mkdir("{$data_path}img/termin_labels");
592        $this->mkdir("{$data_path}img/termin_labels/3");
593        $this->mkdir("{$data_path}img/termin_labels/3/img");
594        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/termin_labels/3/img/QQ8ZApZjsNSBM2wKrkRQxXZG.jpg", 800, 600);
595
596        $this->mkdir("{$data_path}img/termin_locations");
597        $this->mkdir("{$data_path}img/termin_locations/1");
598        $this->mkdir("{$data_path}img/termin_locations/1/img");
599        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/termin_locations/1/img/2ZiW6T9biPNjEERzj5xjLRDz.jpg", 800, 600);
600
601        $this->mkdir("{$data_path}img/termin_templates");
602        $this->mkdir("{$data_path}img/termin_templates/2");
603        $this->mkdir("{$data_path}img/termin_templates/2/img");
604        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/termin_templates/2/img/bv3KeYVKDJNg3MTyjhSQsDRx.jpg", 800, 600);
605
606        $this->mkdir("{$data_path}img/users");
607        $this->mkdir("{$data_path}img/users/1");
608        $this->mkdir("{$data_path}img/users/1/img");
609        $this->mkdir("{$data_path}img/users/1/thumb");
610        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/users/1/img/8sVwnV3aAEtQUUxmQYFmojMs.jpg", 300, 300);
611        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/users/1/thumb/8sVwnV3aAEtQUUxmQYFmojMs.jpg\$256.jpg", 256, 256);
612        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/users/1/thumb/8sVwnV3aAEtQUUxmQYFmojMs.jpg\$128.jpg", 128, 128);
613        $this->mkdir("{$data_path}img/users/3");
614        $this->mkdir("{$data_path}img/users/3/img");
615        $this->mkdir("{$data_path}img/users/3/thumb");
616        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/users/3/img/oyLeyPTaCfmadcm5ShEJ236e.jpg", 150, 150);
617        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "img/users/3/thumb/oyLeyPTaCfmadcm5ShEJ236e.jpg\$128.jpg", 128, 128);
618
619        // Build movies/
620        $this->mkdir("{$data_path}movies");
621
622        // Build olz_mitglieder/
623        $this->mkdir("{$data_path}olz_mitglieder");
624        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "olz_mitglieder/max_muster.jpg", 84, 120);
625
626        // Build OLZimmerbergAblage/
627        $this->mkdir("{$data_path}OLZimmerbergAblage");
628        $dokumente_path = "{$data_path}OLZimmerbergAblage/OLZ Dokumente";
629        $this->mkdir("{$dokumente_path}");
630        $this->mkdir("{$dokumente_path}/vorstand");
631        $this->copy("{$sample_path}sample-document.pdf", "{$dokumente_path}/vorstand/mitgliederliste.pdf");
632        $this->mkdir("{$dokumente_path}/vorstand/protokolle");
633        $this->copy("{$sample_path}sample-document.pdf", "{$dokumente_path}/vorstand/protokolle/protokoll.pdf");
634        $this->mkdir("{$dokumente_path}/karten");
635        $this->copy("{$sample_path}sample-document.pdf", "{$dokumente_path}/karten/uebersicht.pdf");
636        $this->mkdir("{$dokumente_path}/karten/wald");
637        $this->copy("{$sample_path}sample-document.pdf", "{$dokumente_path}/karten/wald/buchstabenwald.pdf");
638
639        // Build panini_data/
640        $this->mkdir("{$data_path}panini_data");
641        $this->mkdir("{$data_path}panini_data/cache");
642        $this->mkdir("{$data_path}panini_data/fonts");
643        $this->mkdir("{$data_path}panini_data/fonts/OpenSans");
644        $this->copy("{$sample_path}sample-font.ttf", "{$data_path}panini_data/fonts/OpenSans/OpenSans-SemiBold.ttf");
645        $this->copy("{$sample_path}sample-font.php", "{$data_path}panini_data/fonts/OpenSans/OpenSans-SemiBold.php");
646        $this->copy("{$sample_path}sample-font.z", "{$data_path}panini_data/fonts/OpenSans/OpenSans-SemiBold.z");
647        $this->mkdir("{$data_path}panini_data/masks");
648        $this->mkimg("{$sample_path}sample-mask.png", $data_path, "panini_data/masks/topP_1517x2091.png", 1517, 2091);
649        $this->mkimg("{$sample_path}sample-mask.png", $data_path, "panini_data/masks/bottomP_1517x2091.png", 1517, 2091);
650        $this->mkimg("{$sample_path}sample-mask.png", $data_path, "panini_data/masks/associationP_1517x2091.png", 1517, 2091);
651        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "panini_data/masks/associationStencilP_1517x2091.png", 1517, 2091);
652        $this->mkimg("{$sample_path}sample-mask.png", $data_path, "panini_data/masks/topP_1594x2303.png", 1594, 2303);
653        $this->mkimg("{$sample_path}sample-mask.png", $data_path, "panini_data/masks/topL_2303x1594.png", 2303, 1594);
654        $this->mkimg("{$sample_path}sample-mask.png", $data_path, "panini_data/masks/bottomP_1594x2303.png", 1594, 2303);
655        $this->mkimg("{$sample_path}sample-mask.png", $data_path, "panini_data/masks/bottomL_2303x1594.png", 2303, 1594);
656        $this->mkimg("{$sample_path}sample-mask.png", $data_path, "panini_data/masks/associationP_1594x2303.png", 1594, 2303);
657        $this->mkimg("{$sample_path}sample-mask.png", $data_path, "panini_data/masks/associationL_2303x1594.png", 2303, 1594);
658        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "panini_data/masks/associationStencilP_1594x2303.png", 1594, 2303);
659        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "panini_data/masks/associationStencilL_2303x1594.png", 2303, 1594);
660        $this->mkdir("{$data_path}panini_data/wappen");
661        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "panini_data/wappen/thalwil.jpg", 100, 100);
662        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "panini_data/wappen/other.jpg", 100, 100);
663        $this->mkdir("{$data_path}panini_data/portraits");
664        $this->mkdir("{$data_path}panini_data/portraits/1001");
665        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "panini_data/portraits/1001/vptD8fzvXIhv_6X32Zkw2s5s.jpg", 800, 600);
666        $this->mkdir("{$data_path}panini_data/portraits/1002");
667        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "panini_data/portraits/1002/LkGdXukqgYEdnWpuFHfrJkr7.jpg", 800, 600);
668        $this->mkdir("{$data_path}panini_data/other");
669        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "panini_data/other/portrait.jpg", 600, 800);
670        $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "panini_data/other/landscape.jpg", 800, 600);
671        for ($i = 1003; $i <= 1012; $i++) {
672            $this->mkdir("{$data_path}panini_data/portraits/{$i}");
673            $this->mkimg("{$sample_path}sample-picture.jpg", $data_path, "panini_data/portraits/{$i}/LkGdXukqgYEdnWpuFHfrJkr7.jpg", 800, 600);
674        }
675
676        // Build pdf/
677        $this->mkdir("{$data_path}pdf");
678        $this->copy("{$sample_path}sample-document.pdf", "{$data_path}pdf/trainingsprogramm.pdf");
679
680        // Build results/
681        $this->mkdir("{$data_path}results");
682        $this->copy("{$sample_path}sample-results.xml", "{$data_path}results/results.xml");
683        $this->copy("{$sample_path}sample-results.xml", "{$data_path}results/2020-termine-7.xml");
684
685        // Build temp/
686        $this->mkdir("{$data_path}temp");
687
688        // Build logs/
689        $this->mkdir("{$private_path}logs");
690        $this->mklog("{$private_path}logs/merged-2020-08-13.log", "2020-08-13");
691        $this->mklog("{$private_path}logs/merged-2020-08-14.log", "2020-08-14");
692        $this->mklog("{$private_path}logs/merged-2020-08-15.log", "2020-08-15");
693        $this->mkdir("{$private_path}logs/server");
694        $this->mklog("{$private_path}logs/server/access_ssl_log", "2020-08-15");
695        $this->mklog("{$private_path}logs/server/access_ssl_log.processed", "2020-08-14");
696        $this->mklog("{$private_path}logs/server/access_ssl_log.processed.1", "2020-08-13");
697        $this->mklog("{$private_path}logs/server/access_ssl_log.processed.2", "2020-08-12");
698
699        // Build sessions
700        $this->mkdir("{$private_path}sessions");
701
702        $this->touchEnqueued(1584118800);
703    }
704
705    protected function mkdir(string $path, int $mode = 0o777, bool $recursive = false): void {
706        if (!is_dir($path)) {
707            mkdir($path, $mode, $recursive);
708        }
709        $this->enqueueForTouch($path);
710    }
711
712    protected function copy(string $source, string $dest): void {
713        if (!is_file($dest)) {
714            copy($source, $dest);
715        }
716        $this->enqueueForTouch($dest);
717    }
718
719    /**
720     * @param int<1, max> $width
721     * @param int<1, max> $height
722     */
723    protected function mkimg(
724        string $source_path,
725        string $data_path,
726        string $destination_relative_path,
727        int $width,
728        int $height,
729    ): void {
730        $destination_path = "{$data_path}{$destination_relative_path}";
731        if (is_file($destination_path)) {
732            return;
733        }
734        $tmp_dir = __DIR__.'/data/tmp/';
735        if (!is_dir($tmp_dir)) {
736            mkdir($tmp_dir);
737        }
738        $flat_destination_relative_path = str_replace('/', '___', $destination_relative_path);
739        $extension_pos = strrpos($flat_destination_relative_path, '.') ?: 0;
740        $ident = substr($flat_destination_relative_path, 0, $extension_pos);
741        $extension = substr($flat_destination_relative_path, $extension_pos);
742        $tmp_basename = "{$ident}___{$width}x{$height}{$extension}";
743        $tmp_path = "{$tmp_dir}{$tmp_basename}";
744        if (!is_file($tmp_path)) {
745            $info = getimagesize($source_path);
746            $source_width = $info[0] ?? 0;
747            $source_height = $info[1] ?? 0;
748            $image_type = $info[2] ?? 0;
749            $source = null;
750            if ($image_type === 2) {
751                $source = imagecreatefromjpeg($source_path);
752            } elseif ($image_type === 3) {
753                $source = imagecreatefrompng($source_path);
754            }
755            if (!$source) {
756                throw new \Exception("mkimg: Image must be JPEG or PNG, was: {$image_type}");
757            }
758            $destination = imagecreatetruecolor($width, $height);
759            imagealphablending($destination, false);
760            imagesavealpha($destination, true);
761            imagecopyresampled(
762                $destination,
763                $source,
764                0,
765                0,
766                0,
767                0,
768                $width,
769                $height,
770                $source_width,
771                $source_height,
772            );
773            $red = imagecolorallocate($destination, 255, 0, 0);
774            assert($red !== false);
775            $hash = intval(substr(md5($destination_relative_path), 0, 1), 16);
776            $x = floor($hash / 4) * $width / 4;
777            $y = floor($hash % 4) * $height / 4;
778            imagefilledrectangle(
779                $destination,
780                intval(round($x)),
781                intval(round($y)),
782                intval(round($x + $width / 4)),
783                intval(round($y + $height / 4)),
784                $red
785            );
786            if (preg_match('/\.jpg$/', $destination_relative_path)) {
787                imagejpeg($destination, $tmp_path, 90);
788            } else {
789                imagepng($destination, $tmp_path);
790            }
791            imagedestroy($destination);
792        }
793        $this->copy($tmp_path, $destination_path);
794    }
795
796    protected function mklog(string $file_path, string $iso_date): void {
797        $log_levels = [
798            'DEBUG',
799            'INFO',
800            'NOTICE',
801            'WARNING',
802            'ERROR',
803            'CRITICAL',
804            'ALERT',
805            'EMERGENCY',
806        ];
807        $num_log_levels = count($log_levels);
808        $fp = fopen($file_path, 'w+');
809        assert($fp !== false);
810        $long_line = 'Wow,';
811        for ($i = 0; $i < 1000; $i++) {
812            $long_line .= ' so much content';
813        }
814        for ($i = 0; $i < 1440; $i++) {
815            $time = str_pad(strval(floor($i / 60)), 2, '0', STR_PAD_LEFT).':'.
816                str_pad(strval(floor($i % 60)), 2, '0', STR_PAD_LEFT).':'.
817                str_pad(strval(random_int(0, 59)), 2, '0', STR_PAD_LEFT).'.'.
818                str_pad(strval(random_int(0, 999999)), 6, '0', STR_PAD_LEFT);
819            $level = $log_levels[$i % $num_log_levels];
820            $fill_up = ($i % ($num_log_levels + 1)) === 0 ? $long_line : '';
821            $line = "[{$iso_date}T{$time}+01:00] Command:ProcessEmail.{$level}: Something happened... {$fill_up} [] []\n";
822            fwrite($fp, $line);
823        }
824        fclose($fp);
825    }
826
827    protected function enqueueForTouch(string $path): void {
828        $this->enqueuedForTouch[] = $path;
829    }
830
831    protected function touchEnqueued(?int $timestamp): void {
832        foreach ($this->enqueuedForTouch as $path) {
833            touch($path, $timestamp, $timestamp);
834        }
835    }
836
837    public static function fromEnv(): self {
838        return new self();
839    }
840}