Passed
Pull Request — 1.11.x (#6335)
by
unknown
09:32
created

FileExport::processDocument()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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