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\Migrations\AbstractMigrationChamilo; |
10
|
|
|
use Chamilo\CoreBundle\Repository\Node\CourseRepository; |
11
|
|
|
use Chamilo\CourseBundle\Entity\CStudentPublication; |
12
|
|
|
use Chamilo\CourseBundle\Entity\CStudentPublicationComment; |
13
|
|
|
use Chamilo\CourseBundle\Repository\CStudentPublicationCommentRepository; |
14
|
|
|
use Chamilo\CourseBundle\Repository\CStudentPublicationRepository; |
15
|
|
|
use Doctrine\DBAL\Schema\Schema; |
16
|
|
|
|
17
|
|
|
final class Version20250501000100 extends AbstractMigrationChamilo |
18
|
|
|
{ |
19
|
|
|
public function getDescription(): string |
20
|
|
|
{ |
21
|
|
|
return 'Migrate student publications (works), corrections, and comments to ResourceNode/ResourceFile'; |
22
|
|
|
} |
23
|
|
|
|
24
|
|
|
public function up(Schema $schema): void |
25
|
|
|
{ |
26
|
|
|
$publicationRepo = $this->container->get(CStudentPublicationRepository::class); |
|
|
|
|
27
|
|
|
$commentRepo = $this->container->get(CStudentPublicationCommentRepository::class); |
28
|
|
|
$courseRepo = $this->container->get(CourseRepository::class); |
29
|
|
|
$kernel = $this->container->get('kernel'); |
30
|
|
|
$root = $kernel->getProjectDir(); |
31
|
|
|
|
32
|
|
|
$courses = $this->entityManager |
33
|
|
|
->createQuery('SELECT c FROM Chamilo\\CoreBundle\\Entity\\Course c') |
|
|
|
|
34
|
|
|
->toIterable(); |
35
|
|
|
|
36
|
|
|
foreach ($courses as $course) { |
37
|
|
|
$courseDir = $course->getDirectory(); |
38
|
|
|
$workPath = "{$root}/app/courses/{$courseDir}/work"; |
39
|
|
|
error_log("[MIGRATION] Processing course '{$course->getCode()}' (ID: {$course->getId()})"); |
40
|
|
|
|
41
|
|
|
$publications = $publicationRepo->createQueryBuilder('sp') |
42
|
|
|
->join('sp.resourceNode', 'rn') |
43
|
|
|
->join('rn.resourceLinks', 'rl') |
44
|
|
|
->where('rl.course = :course') |
45
|
|
|
->andWhere('sp.filetype = :file') |
46
|
|
|
->setParameter('course', $course) |
47
|
|
|
->setParameter('file', 'file') |
48
|
|
|
->getQuery() |
49
|
|
|
->getResult(); |
50
|
|
|
|
51
|
|
|
foreach ($publications as $publication) { |
52
|
|
|
if (!$publication instanceof CStudentPublication || !$publication->getResourceNode()) { |
53
|
|
|
continue; |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
$row = $this->connection->fetchAssociative( |
57
|
|
|
'SELECT * FROM c_student_publication WHERE iid = ?', |
58
|
|
|
[$publication->getIid()] |
59
|
|
|
); |
60
|
|
|
$resourceNode = $publication->getResourceNode(); |
61
|
|
|
|
62
|
|
|
$url = $row['url'] ?? null; |
63
|
|
|
if (!empty($url) && str_starts_with($url, 'work/') && !$this->resourceNodeHasFile($resourceNode, basename($url))) { |
64
|
|
|
$filename = basename($url); |
65
|
|
|
$source = "{$workPath}/{$url}"; |
66
|
|
|
error_log("[MIGRATION] Submission source: $source"); |
67
|
|
|
|
68
|
|
|
if ($this->fileExists($source)) { |
69
|
|
|
$this->addLegacyFileToResource($source, $publicationRepo, $publication, $row['iid'], $filename); |
70
|
|
|
$this->entityManager->persist($publication); |
71
|
|
|
} else { |
72
|
|
|
error_log("[MIGRATION][ERROR] Submission file not found: $source"); |
73
|
|
|
} |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
$correctionUrl = $row['url_correction'] ?? null; |
77
|
|
|
if (!empty($correctionUrl) && str_starts_with($correctionUrl, 'work/') && !$this->resourceNodeHasFile($resourceNode, basename($correctionUrl))) { |
78
|
|
|
$filename = basename($correctionUrl); |
79
|
|
|
$source = "{$workPath}/{$correctionUrl}"; |
80
|
|
|
error_log("[MIGRATION] Correction source: $source"); |
81
|
|
|
|
82
|
|
|
if ($this->fileExists($source)) { |
83
|
|
|
$this->addLegacyFileToResource($source, $publicationRepo, $publication, $row['iid'], $filename); |
84
|
|
|
$publication->setExtensions($filename); |
85
|
|
|
$this->entityManager->persist($publication); |
86
|
|
|
} else { |
87
|
|
|
error_log("[MIGRATION][WARN] Correction file not found: $source"); |
88
|
|
|
} |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
$this->entityManager->flush(); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
$comments = $commentRepo->createQueryBuilder('c') |
95
|
|
|
->join('c.publication', 'sp') |
96
|
|
|
->join('sp.resourceNode', 'rn') |
97
|
|
|
->join('rn.resourceLinks', 'rl') |
98
|
|
|
->where('rl.course = :course') |
99
|
|
|
->andWhere('c.file IS NOT NULL') |
100
|
|
|
->setParameter('course', $course) |
101
|
|
|
->getQuery() |
102
|
|
|
->getResult(); |
103
|
|
|
|
104
|
|
|
foreach ($comments as $comment) { |
105
|
|
|
if (!$comment instanceof CStudentPublicationComment || !$comment->getResourceNode()) { |
106
|
|
|
continue; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
$row = $this->connection->fetchAssociative( |
110
|
|
|
'SELECT * FROM c_student_publication_comment WHERE iid = ?', |
111
|
|
|
[$comment->getIid()] |
112
|
|
|
); |
113
|
|
|
|
114
|
|
|
$filename = basename($row['file']); |
115
|
|
|
$source = "{$workPath}/{$filename}"; |
116
|
|
|
$resourceNode = $comment->getResourceNode(); |
117
|
|
|
error_log("[MIGRATION] Comment source: $source"); |
118
|
|
|
|
119
|
|
|
if (!$this->resourceNodeHasFile($resourceNode, $filename)) { |
120
|
|
|
if ($this->fileExists($source)) { |
121
|
|
|
$this->addLegacyFileToResource($source, $commentRepo, $comment, $row['iid'], $filename); |
122
|
|
|
$this->entityManager->persist($comment); |
123
|
|
|
$this->entityManager->flush(); |
124
|
|
|
} else { |
125
|
|
|
error_log("[MIGRATION][WARN] Comment file not found: $source"); |
126
|
|
|
} |
127
|
|
|
} |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
$this->entityManager->clear(); |
131
|
|
|
error_log("[MIGRATION] Finished processing course '{$course->getCode()}'"); |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
private function resourceNodeHasFile($resourceNode, string $filename): bool |
136
|
|
|
{ |
137
|
|
|
foreach ($resourceNode->getResourceFiles() as $file) { |
138
|
|
|
if ($file->getTitle() === $filename || $file->getOriginalName() === $filename) { |
139
|
|
|
return true; |
140
|
|
|
} |
141
|
|
|
} |
142
|
|
|
return false; |
143
|
|
|
} |
144
|
|
|
} |
145
|
|
|
|
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.