Passed
Push — master ( f5fb2d...a64750 )
by
unknown
16:33 queued 07:23
created

Version20251022005000::resolvePublicBaseDir()   B

Complexity

Conditions 9
Paths 10

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 12
c 1
b 0
f 0
nc 10
nop 0
dl 0
loc 21
rs 8.0555
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
declare(strict_types=1);
5
6
namespace Chamilo\CoreBundle\Migrations\Schema\V200;
7
8
use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo;
9
use Doctrine\DBAL\Schema\Schema;
10
11
final class Version20251022005000 extends AbstractMigrationChamilo
12
{
13
    private const DEBUG = true;
14
15
    /** @var array<string,true> */
16
    private array $globalNeeded = [];
17
18
    public function getDescription(): string
19
    {
20
        return 'Rewrite legacy image paths in system templates to public /img paths.';
21
    }
22
23
    public function up(Schema $schema): void
24
    {
25
        $conn = $this->connection;
26
27
        $rows = $conn->fetchAllAssociative(
28
            "SELECT id, title, content
29
               FROM system_template
30
              WHERE content LIKE '%/main/img/%'
31
                 OR content LIKE '%{REL_PATH}main/img/%'
32
                 OR content LIKE '%{IMG_DIR}%'
33
                 OR content LIKE '%{COURSE_DIR}images/%'
34
                 OR content LIKE '%/img/certificates/%'"
35
        );
36
37
        if (empty($rows)) {
38
            $this->dbg('[MIG][templates] No legacy paths found. Nothing to do.');
39
            return;
40
        }
41
42
        $updated = 0;
43
44
        foreach ($rows as $r) {
45
            $id      = (int)$r['id'];
46
            $title   = (string)$r['title'];
47
            $content = (string)$r['content'];
48
49
            $newContent = $this->rewriteLegacyPathsToPublic($content, $title, $id);
50
51
            $needed = $this->extractPublicImagePaths($newContent);
52
            $this->logNeededImages($id, $title, $needed);
53
54
            if ($newContent !== $content) {
55
                $conn->update('system_template', ['content' => $newContent], ['id' => $id]);
56
                $updated++;
57
                $this->dbg(sprintf('[MIG][templates] Updated template id=%d title="%s".', $id, $title));
58
            }
59
        }
60
61
        $this->dbg('--- [MIG][templates:images] GLOBAL REQUIRED IMAGES ---');
62
        $publicBase = $this->resolvePublicBaseDir();
63
        foreach (array_keys($this->globalNeeded) as $relPath) {
64
            $status = $this->fileStatus($publicBase, $relPath);
65
            $this->dbg(sprintf('[MIG][templates:images] %s  %s', $status, $relPath));
66
        }
67
        $this->dbg(sprintf('[MIG][templates] DONE. Updated %d template(s).', $updated));
68
    }
69
70
    /**
71
     * Rewrite legacy patterns to /img/...
72
     */
73
    private function rewriteLegacyPathsToPublic(string $html, string $title, int $id): string
74
    {
75
        $countAll = 0;
76
77
        // {IMG_DIR}filename.ext -> /img/filename.ext
78
        $html = preg_replace(
79
            '#(\burl\(\s*[\'"]?)\{IMG_DIR\}#i',
80
            '$1/img/',
81
            $html,
82
            -1,
83
            $c1
84
        ); $countAll += (int)$c1;
85
86
        $html = preg_replace(
87
            '#(\bsrc=\s*[\'"])\{IMG_DIR\}#i',
88
            '$1/img/',
89
            $html,
90
            -1,
91
            $c1b
92
        ); $countAll += (int)$c1b;
93
94
        // {REL_PATH}main/img/... -> /img/...
95
        $html = preg_replace(
96
            '#(\burl\(\s*[\'"]?)\{REL_PATH\}main/img/#i',
97
            '$1/img/',
98
            $html,
99
            -1,
100
            $c2
101
        ); $countAll += (int)$c2;
102
103
        $html = preg_replace(
104
            '#(\bsrc=\s*[\'"])\{REL_PATH\}main/img/#i',
105
            '$1/img/',
106
            $html,
107
            -1,
108
            $c2b
109
        ); $countAll += (int)$c2b;
110
111
        // Raw /main/img/... -> /img/...
112
        $html = preg_replace(
113
            '#(\burl\(\s*[\'"]?)/main/img/#i',
114
            '$1/img/',
115
            $html,
116
            -1,
117
            $c3
118
        ); $countAll += (int)$c3;
119
120
        $html = preg_replace(
121
            '#(\bsrc=\s*[\'"])/main/img/#i',
122
            '$1/img/',
123
            $html,
124
            -1,
125
            $c3b
126
        ); $countAll += (int)$c3b;
127
128
        // {COURSE_DIR}images/... -> /img/...
129
        $html = preg_replace(
130
            '#(\burl\(\s*[\'"]?)\{COURSE_DIR\}images/#i',
131
            '$1/img/',
132
            $html,
133
            -1,
134
            $c4
135
        ); $countAll += (int)$c4;
136
137
        $html = preg_replace(
138
            '#(\bsrc=\s*[\'"])\{COURSE_DIR\}images/#i',
139
            '$1/img/',
140
            $html,
141
            -1,
142
            $c4b
143
        ); $countAll += (int)$c4b;
144
145
        // /img/certificates/... -> /img/...
146
        $html = preg_replace(
147
            '#(\burl\(\s*[\'"]?)/img/certificates/#i',
148
            '$1/img/',
149
            $html,
150
            -1,
151
            $c5
152
        ); $countAll += (int)$c5;
153
154
        $html = preg_replace(
155
            '#(\bsrc=\s*[\'"])/img/certificates/#i',
156
            '$1/img/',
157
            $html,
158
            -1,
159
            $c5b
160
        ); $countAll += (int)$c5b;
161
162
        $html = preg_replace('#/img//+#', '/img/', $html, -1, $c6); $countAll += (int)$c6;
163
164
        if ($countAll > 0) {
165
            $this->dbg(sprintf(
166
                '[MIG][templates] id=%d title="%s" rewrites=%d',
167
                $id,
168
                $title,
169
                $countAll
170
            ));
171
        }
172
173
        return $html;
174
    }
175
176
    /**
177
     * Extracts /img/... references from <img src> and CSS url(...).
178
     *
179
     * @return string[] relative paths (starting with /img)
180
     */
181
    private function extractPublicImagePaths(string $html): array
182
    {
183
        $found = [];
184
185
        // <img src="/img/...">
186
        if (preg_match_all('#<img\b[^>]*\bsrc\s*=\s*["\'](/img/[^"\']+)#i', $html, $m1)) {
187
            foreach ($m1[1] as $p) {
188
                $found[$p] = true;
189
            }
190
        }
191
192
        // url('/img/...') o url(/img/...)
193
        if (preg_match_all('#url\(\s*[\'"]?(/img/[^)\'"]+)#i', $html, $m2)) {
194
            foreach ($m2[1] as $p) {
195
                $found[$p] = true;
196
            }
197
        }
198
199
        return array_keys($found);
200
    }
201
202
    /**
203
     * Records (and accumulates) the list of images and checks for existence under public/.
204
     *
205
     * @param int      $id
206
     * @param string   $title
207
     * @param string[] $paths
208
     */
209
    private function logNeededImages(int $id, string $title, array $paths): void
210
    {
211
        if (empty($paths)) {
212
            $this->dbg(sprintf('[MIG][templates:images] id=%d title="%s" no image refs.', $id, $title));
213
            return;
214
        }
215
216
        $publicBase = $this->resolvePublicBaseDir();
217
        $this->dbg(sprintf('[MIG][templates:images] id=%d title="%s" refs=%d', $id, $title, count($paths)));
218
219
        foreach ($paths as $rel) {
220
            $this->globalNeeded[$rel] = true;
221
            $status = $this->fileStatus($publicBase, $rel);
222
            $this->dbg(sprintf('  %s  %s', $status, $rel));
223
        }
224
    }
225
226
    /**
227
     * Attempts to resolve absolute path to public/ when run via CLI.
228
     */
229
    private function resolvePublicBaseDir(): ?string
230
    {
231
        $cwdPublic = getcwd() ? rtrim((string)getcwd(), '/').'/public' : null;
232
        if ($cwdPublic && is_dir($cwdPublic)) {
233
            return $cwdPublic;
234
        }
235
236
        $guess = realpath(__DIR__.'/../../../../../../public');
237
        if ($guess && is_dir($guess)) {
238
            return $guess;
239
        }
240
241
        for ($i = 1; $i <= 7; $i++) {
242
            $up = realpath(__DIR__.str_repeat('/..', $i).'/public');
243
            if ($up && is_dir($up)) {
244
                return $up;
245
            }
246
        }
247
248
        $this->dbg('[MIG][templates:images] WARN: could not resolve public/ base dir.');
249
        return null;
250
    }
251
252
    /**
253
     * Mark whether the file exists or not.
254
     */
255
    private function fileStatus(?string $publicBase, string $relPath): string
256
    {
257
        if (!$publicBase) {
258
            return '[?   ]';
259
        }
260
        $abs = $publicBase.$relPath;
261
        if (is_file($abs)) {
262
            return '[OK  ]';
263
        }
264
        return '[MISS]';
265
    }
266
267
    private function dbg(string $msg): void
268
    {
269
        if (self::DEBUG) {
270
            error_log($msg);
271
        }
272
    }
273
}
274