Completed
Push — alpha ( 11d197 )
by Yannick
01:22 queued 44s
created

Version20230913162700   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 240
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 149
dl 0
loc 240
rs 8.96
c 4
b 0
f 0
wmc 43

8 Methods

Rating   Name   Duplication   Size   Complexity  
B updateContent() 0 23 7
A getDescription() 0 3 1
B updateHtmlContent() 0 26 9
B replaceOldURLsWithNew() 0 34 7
A replaceDocumentLinks() 0 11 4
A recursiveFileSearch() 0 10 4
B createNewDocument() 0 65 7
A up() 0 38 4

How to fix   Complexity   

Complex Class

Complex classes like Version20230913162700 often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Version20230913162700, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\Migrations\Schema\V200;
8
9
use Chamilo\CoreBundle\Entity\Course;
10
use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo;
11
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
12
use Chamilo\CourseBundle\Entity\CDocument;
13
use Chamilo\CourseBundle\Repository\CDocumentRepository;
14
use Chamilo\CoreBundle\Repository\ResourceNodeRepository;
15
use Doctrine\DBAL\Schema\Schema;
16
use Exception;
17
use RecursiveDirectoryIterator;
18
use RecursiveIteratorIterator;
19
20
use const PHP_URL_PATH;
21
22
final class Version20230913162700 extends AbstractMigrationChamilo
23
{
24
    public function getDescription(): string
25
    {
26
        return 'Replace old document path by resource file path';
27
    }
28
29
    public function up(Schema $schema): void
30
    {
31
        $documentRepo = $this->container->get(CDocumentRepository::class);
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

31
        /** @scrutinizer ignore-call */ 
32
        $documentRepo = $this->container->get(CDocumentRepository::class);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
32
        $resourceNodeRepo = $this->container->get(ResourceNodeRepository::class);
33
34
        $q = $this->entityManager->createQuery('SELECT c FROM Chamilo\CoreBundle\Entity\Course c');
0 ignored issues
show
Bug introduced by
The method createQuery() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

34
        /** @scrutinizer ignore-call */ 
35
        $q = $this->entityManager->createQuery('SELECT c FROM Chamilo\CoreBundle\Entity\Course c');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
35
        $updateConfigurations = [
36
            ['table' => 'c_tool_intro', 'field' => 'intro_text'],
37
            ['table' => 'c_course_description', 'field' => 'content'],
38
            ['table' => 'c_quiz', 'fields' => ['description', 'text_when_finished']],
39
            ['table' => 'c_quiz_question', 'fields' => ['description', 'question']],
40
            ['table' => 'c_quiz_answer', 'fields' => ['answer', 'comment']],
41
            ['table' => 'c_course_description', 'field' => 'content'],
42
            ['table' => 'c_student_publication', 'field' => 'description'],
43
            ['table' => 'c_student_publication_comment', 'field' => 'comment'],
44
            ['table' => 'c_forum_category', 'field' => 'cat_comment'],
45
            ['table' => 'c_forum_forum', 'field' => 'forum_comment'],
46
            ['table' => 'c_forum_post', 'field' => 'post_text'],
47
            ['table' => 'c_glossary', 'field' => 'description'],
48
            ['table' => 'c_survey', 'fields' => ['title', 'subtitle']],
49
            ['table' => 'c_survey_question', 'fields' => ['survey_question', 'survey_question_comment']],
50
            ['table' => 'c_survey_question_option', 'field' => 'option_text'],
51
        ];
52
53
        /** @var Course $course */
54
        foreach ($q->toIterable() as $course) {
55
            $courseId = $course->getId();
56
            $courseDirectory = $course->getDirectory();
57
58
            if (empty($courseDirectory)) {
59
                continue;
60
            }
61
62
            foreach ($updateConfigurations as $config) {
63
                $this->updateContent($config, $courseDirectory, $courseId, $documentRepo);
64
            }
65
66
            $this->updateHtmlContent($courseDirectory, $courseId, $documentRepo, $resourceNodeRepo);
67
        }
68
    }
69
70
    private function updateContent($config, $courseDirectory, $courseId, $documentRepo): void
71
    {
72
        if (isset($config['field'])) {
73
            $fields = [$config['field']];
74
        } elseif (isset($config['fields'])) {
75
            $fields = $config['fields'];
76
        } else {
77
            throw new Exception('No field or fields specified for updating.');
78
        }
79
80
        foreach ($fields as $field) {
81
            $sql = "SELECT iid, {$field} FROM {$config['table']} WHERE c_id = {$courseId}";
82
            $result = $this->connection->executeQuery($sql);
83
            $items = $result->fetchAllAssociative();
84
85
            foreach ($items as $item) {
86
                $originalText = $item[$field];
87
                if (!empty($originalText)) {
88
                    $updatedText = $this->replaceOldURLsWithNew($originalText, $courseDirectory, $courseId, $documentRepo);
89
                    if ($originalText !== $updatedText) {
90
                        $sql = "UPDATE {$config['table']} SET {$field} = :newText WHERE iid = :id";
91
                        $params = ['newText' => $updatedText, 'id' => $item['iid']];
92
                        $this->connection->executeQuery($sql, $params);
93
                    }
94
                }
95
            }
96
        }
97
    }
98
99
    private function updateHtmlContent($courseDirectory, $courseId, $documentRepo, $resourceNodeRepo): void
100
    {
101
        $sql = "SELECT iid, resource_node_id FROM c_document WHERE filetype = 'file'";
102
        $result = $this->connection->executeQuery($sql);
103
        $items = $result->fetchAllAssociative();
104
105
        foreach ($items as $item) {
106
            $document = $documentRepo->find($item['iid']);
107
            if ($document) {
108
                $resourceNode = $document->getResourceNode();
109
                if ($resourceNode && $resourceNode->hasResourceFile()) {
110
                    $resourceFile = $resourceNode->getResourceFile();
111
                    $filePath = $resourceFile->getTitle();
112
                    if ($resourceFile && $resourceFile->getMimeType() === 'text/html') {
113
                        error_log("Verifying HTML file: " . $filePath);
114
115
                        try {
116
                            $content = $resourceNodeRepo->getResourceNodeFileContent($resourceNode);
117
                            $updatedContent = $this->replaceOldURLsWithNew($content, $courseDirectory, $courseId, $documentRepo);
118
119
                            if ($content !== $updatedContent) {
120
                                $documentRepo->updateResourceFileContent($document, $updatedContent);
121
                                $documentRepo->update($document);
122
                            }
123
                        } catch (\Exception $e) {
124
                            error_log("Error processing file $filePath: " . $e->getMessage());
125
                        }
126
                    }
127
                }
128
            }
129
        }
130
    }
131
132
    private function replaceOldURLsWithNew($itemDataText, $courseDirectory, $courseId, $documentRepo): array|string|null
133
    {
134
        $contentText = $itemDataText;
135
        $specificCoursePattern = '/(src|href)=["\']((https?:\/\/[^\/]+)?(\/courses\/([^\/]+)\/document\/[^"\']+\.\w+))["\']/i';
136
        preg_match_all($specificCoursePattern, $contentText, $matches);
137
138
        foreach ($matches[2] as $index => $fullUrl) {
139
            $videoPath = parse_url($fullUrl, PHP_URL_PATH) ?: $fullUrl;
140
            $actualCourseDirectory = $matches[5][$index];
141
            if ($actualCourseDirectory !== $courseDirectory) {
142
                $videoPath = preg_replace("/^\\/courses\\/$actualCourseDirectory\\//i", "/courses/$courseDirectory/", $videoPath);
143
            }
144
145
            $documentPath = str_replace('/courses/'.$courseDirectory.'/document/', '/', $videoPath);
146
147
            $sql = "SELECT iid, path, resource_node_id FROM c_document WHERE c_id = $courseId AND path LIKE '$documentPath'";
148
            $result = $this->connection->executeQuery($sql);
149
            $documents = $result->fetchAllAssociative();
150
151
            if (!empty($documents)) {
152
                $this->replaceDocumentLinks($documents, $documentRepo, $matches, $index, $videoPath, $courseId, $contentText);
153
            } else {
154
                $document = $this->createNewDocument($videoPath, $courseId);
155
                if ($document) {
156
                    $newUrl = $documentRepo->getResourceFileUrl($document);
157
                    if ($newUrl) {
158
                        $replacement = $matches[1][$index].'="'.$newUrl.'"';
159
                        $contentText = str_replace($matches[0][$index], $replacement, $contentText);
160
                    }
161
                }
162
            }
163
        }
164
165
        return $contentText;
166
    }
167
168
    private function replaceDocumentLinks($documents, $documentRepo, $matches, $index, $videoPath, $courseId, &$contentText): void
169
    {
170
        foreach ($documents as $documentData) {
171
            $resourceNodeId = (int) $documentData['resource_node_id'];
172
            $documentFile = $documentRepo->getResourceFromResourceNode($resourceNodeId);
173
            if ($documentFile) {
174
                $newUrl = $documentRepo->getResourceFileUrl($documentFile);
175
                if (!empty($newUrl)) {
176
                    $patternForReplacement = '/'.preg_quote($matches[0][$index], '/').'/';
177
                    $replacement = $matches[1][$index].'="'.$newUrl.'"';
178
                    $contentText = preg_replace($patternForReplacement, $replacement, $contentText, 1);
179
                }
180
            }
181
        }
182
    }
183
184
    private function createNewDocument($videoPath, $courseId)
185
    {
186
        try {
187
            $documentRepo = $this->container->get(CDocumentRepository::class);
188
            $kernel = $this->container->get('kernel');
189
            $rootPath = $kernel->getProjectDir();
190
            $appCourseOldPath = $rootPath.'/app'.$videoPath;
191
            $title = basename($appCourseOldPath);
192
193
            $courseRepo = $this->container->get(CourseRepository::class);
194
            $course = $courseRepo->find($courseId);
195
            if (!$course) {
196
                throw new Exception("Course with ID $courseId not found.");
197
            }
198
199
            $document = $documentRepo->findCourseResourceByTitle($title, $course->getResourceNode(), $course);
200
            if (null !== $document) {
201
                return $document;
202
            }
203
204
            if (file_exists($appCourseOldPath) && !is_dir($appCourseOldPath)) {
205
                $document = new CDocument();
206
                $document->setFiletype('file')
207
                    ->setTitle($title)
208
                    ->setComment(null)
209
                    ->setReadonly(false)
210
                    ->setCreator($this->getAdmin())
211
                    ->setParent($course)
212
                    ->addCourseLink($course)
213
                ;
214
215
                $this->entityManager->persist($document);
216
                $this->entityManager->flush();
217
218
                $documentRepo->addFileFromPath($document, $title, $appCourseOldPath);
219
220
                return $document;
221
            }
222
            $generalCoursesPath = $rootPath.'/app/courses/';
223
            $foundPath = $this->recursiveFileSearch($generalCoursesPath, $title);
224
            if ($foundPath) {
225
                $document = new CDocument();
226
                $document->setFiletype('file')
227
                    ->setTitle($title)
228
                    ->setComment(null)
229
                    ->setReadonly(false)
230
                    ->setCreator($this->getAdmin())
231
                    ->setParent($course)
232
                    ->addCourseLink($course)
233
                ;
234
235
                $this->entityManager->persist($document);
236
                $this->entityManager->flush();
237
238
                $documentRepo->addFileFromPath($document, $title, $foundPath);
239
                error_log('File found in new location: '.$foundPath);
240
241
                return $document;
242
            }
243
244
            throw new Exception('File not found in any location.');
245
        } catch (Exception $e) {
246
            error_log('Migration error: '.$e->getMessage());
247
248
            return null;
249
        }
250
    }
251
252
    private function recursiveFileSearch($directory, $title)
253
    {
254
        $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory));
255
        foreach ($iterator as $file) {
256
            if ($file->isFile() && $file->getFilename() === $title) {
257
                return $file->getRealPath();
258
            }
259
        }
260
261
        return null;
262
    }
263
}
264