Passed
Push — 1.11.x ( 120e79...41af88 )
by Yannick
15:40 queued 06:52
created

FileExport::getFolderFileData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 28
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 25
c 1
b 0
f 0
dl 0
loc 28
rs 9.52
cc 1
nc 1
nop 2
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
namespace moodleexport;
6
7
use DocumentManager;
8
use Exception;
9
10
/**
11
 * Class FileExport.
12
 * Handles the export of files and metadata from Moodle courses.
13
 *
14
 * @package moodleexport
15
 */
16
class FileExport
17
{
18
    private $course;
19
20
    /**
21
     * Constructor to initialize course data.
22
     *
23
     * @param object $course Course object containing resources and path data.
24
     */
25
    public function __construct($course)
26
    {
27
        $this->course = $course;
28
    }
29
30
    /**
31
     * Export files and metadata from files.xml to the specified directory.
32
     */
33
    public function exportFiles(array $filesData, string $exportDir): void
34
    {
35
        $filesDir = $exportDir . '/files';
36
37
        if (!is_dir($filesDir)) {
38
            mkdir($filesDir, api_get_permissions_for_new_directories(), true);
39
        }
40
41
        // Create placeholder index.html
42
        $this->createPlaceholderFile($filesDir);
43
44
        // Export each file
45
        foreach ($filesData['files'] as $file) {
46
            $this->copyFileToExportDir($file, $filesDir);
47
        }
48
49
        // Create files.xml in the export directory
50
        $this->createFilesXml($filesData, $exportDir);
51
    }
52
53
    /**
54
     * Create a placeholder index.html file to prevent an empty directory.
55
     */
56
    private function createPlaceholderFile(string $filesDir): void
57
    {
58
        $placeholderFile = $filesDir . '/index.html';
59
        file_put_contents($placeholderFile, "<!-- Placeholder file to ensure the directory is not empty -->");
60
    }
61
62
    /**
63
     * Copy a file to the export directory using its contenthash.
64
     */
65
    private function copyFileToExportDir(array $file, string $filesDir): void
66
    {
67
        if ($file['filepath'] === '.') {
68
            return;
69
        }
70
71
        $contenthash = $file['contenthash'];
72
        $subDir = substr($contenthash, 0, 2);
73
        $filePath = $this->course->path . $file['documentpath'];
74
        $exportSubDir = $filesDir . '/' . $subDir;
75
76
        // Ensure the subdirectory exists
77
        if (!is_dir($exportSubDir)) {
78
            mkdir($exportSubDir, api_get_permissions_for_new_directories(), true);
79
        }
80
81
        // Copy the file to the export directory
82
        $destinationFile = $exportSubDir . '/' . $contenthash;
83
        if (file_exists($filePath)) {
84
            copy($filePath, $destinationFile);
85
        } else {
86
            throw new Exception("File {$filePath} not found.");
87
        }
88
    }
89
90
    /**
91
     * Create the files.xml with the provided file data.
92
     */
93
    private function createFilesXml(array $filesData, string $destinationDir): void
94
    {
95
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL;
96
        $xmlContent .= '<files>' . PHP_EOL;
97
98
        foreach ($filesData['files'] as $file) {
99
            $xmlContent .= $this->createFileXmlEntry($file);
100
        }
101
102
        $xmlContent .= '</files>' . PHP_EOL;
103
        file_put_contents($destinationDir . '/files.xml', $xmlContent);
104
    }
105
106
    /**
107
     * Create an XML entry for a file.
108
     */
109
    private function createFileXmlEntry(array $file): string
110
    {
111
        return '  <file id="' . $file['id'] . '">' . PHP_EOL .
112
            '    <contenthash>' . htmlspecialchars($file['contenthash']) . '</contenthash>' . PHP_EOL .
113
            '    <contextid>' . $file['contextid'] . '</contextid>' . PHP_EOL .
114
            '    <component>' . htmlspecialchars($file['component']) . '</component>' . PHP_EOL .
115
            '    <filearea>' . htmlspecialchars($file['filearea']) . '</filearea>' . PHP_EOL .
116
            '    <itemid>0</itemid>' . PHP_EOL .
117
            '    <filepath>' . htmlspecialchars($file['filepath']) . '</filepath>' . PHP_EOL .
118
            '    <filename>' . htmlspecialchars($file['filename']) . '</filename>' . PHP_EOL .
119
            '    <userid>' . $file['userid'] . '</userid>' . PHP_EOL .
120
            '    <filesize>' . $file['filesize'] . '</filesize>' . PHP_EOL .
121
            '    <mimetype>' . htmlspecialchars($file['mimetype']) . '</mimetype>' . PHP_EOL .
122
            '    <status>' . $file['status'] . '</status>' . PHP_EOL .
123
            '    <timecreated>' . $file['timecreated'] . '</timecreated>' . PHP_EOL .
124
            '    <timemodified>' . $file['timemodified'] . '</timemodified>' . PHP_EOL .
125
            '    <source>' . htmlspecialchars($file['source']) . '</source>' . PHP_EOL .
126
            '    <author>' . htmlspecialchars($file['author']) . '</author>' . PHP_EOL .
127
            '    <license>' . htmlspecialchars($file['license']) . '</license>' . PHP_EOL .
128
            '    <sortorder>0</sortorder>' . PHP_EOL .
129
            '    <repositorytype>$@NULL@$</repositorytype>' . PHP_EOL .
130
            '    <repositoryid>$@NULL@$</repositoryid>' . PHP_EOL .
131
            '    <reference>$@NULL@$</reference>' . PHP_EOL .
132
            '  </file>' . PHP_EOL;
133
    }
134
135
    /**
136
     * Get file data from course resources. This is for testing purposes.
137
     */
138
    public function getFilesData(): array
139
    {
140
        $adminData = MoodleExport::getAdminUserData();
141
        $adminId = $adminData['id'];
142
143
        $filesData = ['files' => []];
144
145
        foreach ($this->course->resources[RESOURCE_DOCUMENT] as $document) {
146
            $filesData = $this->processDocument($filesData, $document);
147
        }
148
149
        foreach ($this->course->resources[RESOURCE_WORK] as $work) {
150
            $workFiles = getAllDocumentToWork($work->params['id'], $this->course->info['real_id']);
151
152
            if (!empty($workFiles)) {
153
                foreach ($workFiles as $file) {
154
                    $docData = DocumentManager::get_document_data_by_id($file['document_id'], $this->course->info['code']);
155
                    if (!empty($docData)) {
156
                        $filesData['files'][] = [
157
                            'id' => $file['document_id'],
158
                            'contenthash' => hash('sha1', basename($docData['path'])),
159
                            'contextid' => $this->course->info['real_id'],
160
                            'component' => 'mod_assign',
161
                            'filearea' => 'introattachment',
162
                            'itemid' => (int) $work->params['id'],
163
                            'filepath' => '/',
164
                            'documentpath' =>  'document/'.$docData['path'],
165
                            'filename' => basename($docData['path']),
166
                            'userid' => $adminId,
167
                            'filesize' => $docData['size'],
168
                            'mimetype' => $this->getMimeType($docData['path']),
169
                            'status' => 0,
170
                            'timecreated' => time() - 3600,
171
                            'timemodified' => time(),
172
                            'source' => $docData['title'],
173
                            'author' => 'Unknown',
174
                            'license' => 'allrightsreserved',
175
                        ];
176
                    }
177
                }
178
            }
179
        }
180
181
        return $filesData;
182
    }
183
184
    /**
185
     * Process a document or folder and add its data to the files array.
186
     */
187
    private function processDocument(array $filesData, object $document): array
188
    {
189
        if ($document->file_type === 'file') {
190
            $filesData['files'][] = $this->getFileData($document);
191
        } elseif ($document->file_type === 'folder') {
192
            $folderFiles = \DocumentManager::getAllDocumentsByParentId($this->course->info, $document->source_id);
193
            foreach ($folderFiles as $file) {
194
                $filesData['files'][] = $this->getFolderFileData($file, (int) $document->source_id);
195
            }
196
        }
197
        return $filesData;
198
    }
199
200
    /**
201
     * Get file data for a single document.
202
     */
203
    private function getFileData(object $document): array
204
    {
205
        $adminData = MoodleExport::getAdminUserData();
206
        $adminId = $adminData['id'];
207
        $contenthash = hash('sha1', basename($document->path));
208
        $mimetype = $this->getMimeType($document->path);
209
210
        return [
211
            'id' => $document->source_id,
212
            'contenthash' => $contenthash,
213
            'contextid' => $document->source_id,
214
            'component' => 'mod_resource',
215
            'filearea' => 'content',
216
            'itemid' => (int) $document->source_id,
217
            'filepath' => '/',
218
            'documentpath' => $document->path,
219
            'filename' => basename($document->path),
220
            'userid' => $adminId,
221
            'filesize' => $document->size,
222
            'mimetype' => $mimetype,
223
            'status' => 0,
224
            'timecreated' => time() - 3600,
225
            'timemodified' => time(),
226
            'source' => $document->title,
227
            'author' => 'Unknown',
228
            'license' => 'allrightsreserved',
229
        ];
230
    }
231
232
    /**
233
     * Get file data for files inside a folder.
234
     */
235
    private function getFolderFileData(array $file, int $sourceId): array
236
    {
237
        $adminData = MoodleExport::getAdminUserData();
238
        $adminId = $adminData['id'];
239
        $contenthash = hash('sha1', basename($file['path']));
240
        $mimetype = $this->getMimeType($file['path']);
241
        $filename = basename($file['path']);
242
        $filepath = $this->ensureTrailingSlash(dirname($file['path']));
243
244
        return [
245
            'id' => $file['id'],
246
            'contenthash' => $contenthash,
247
            'contextid' => $sourceId,
248
            'component' => 'mod_folder',
249
            'filearea' => 'content',
250
            'itemid' => (int) $file['id'],
251
            'filepath' => $filepath,
252
            'documentpath' => 'document/'.$file['path'],
253
            'filename' => $filename,
254
            'userid' => $adminId,
255
            'filesize' => $file['size'],
256
            'mimetype' => $mimetype,
257
            'status' => 0,
258
            'timecreated' => time() - 3600,
259
            'timemodified' => time(),
260
            'source' => $file['title'],
261
            'author' => 'Unknown',
262
            'license' => 'allrightsreserved',
263
        ];
264
    }
265
266
    /**
267
     * Ensure the directory path has a trailing slash.
268
     */
269
    private function ensureTrailingSlash($path): string
270
    {
271
        return empty($path) || $path === '.' || $path === '/' ? '/' : rtrim($path, '/') . '/';
272
    }
273
274
    /**
275
     * Get MIME type based on the file extension.
276
     */
277
    private function getMimeType($filePath): string
278
    {
279
        $extension = pathinfo($filePath, PATHINFO_EXTENSION);
280
        $mimeTypes = $this->getMimeTypes();
281
282
        return $mimeTypes[$extension] ?? 'application/octet-stream';
283
    }
284
285
    /**
286
     * Get an array of file extensions and their corresponding MIME types.
287
     */
288
    private function getMimeTypes(): array
289
    {
290
        return [
291
            'pdf' => 'application/pdf',
292
            'jpg' => 'image/jpeg',
293
            'jpeg' => 'image/jpeg',
294
            'png' => 'image/png',
295
            'gif' => 'image/gif',
296
            'html' => 'text/html',
297
            'txt' => 'text/plain',
298
            'doc' => 'application/msword',
299
            'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
300
            'xls' => 'application/vnd.ms-excel',
301
            'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
302
            'ppt' => 'application/vnd.ms-powerpoint',
303
            'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
304
            'zip' => 'application/zip',
305
            'rar' => 'application/x-rar-compressed',
306
        ];
307
    }
308
}
309