Passed
Pull Request — 1.11.x (#6923)
by
unknown
10:03
created

FileExport::processDocument()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 26
rs 9.8333
c 0
b 0
f 0
cc 3
nc 2
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
     * Get MIME type based on the file extension.
104
     */
105
    public function getMimeType($filePath): string
106
    {
107
        $extension = pathinfo($filePath, PATHINFO_EXTENSION);
108
        $mimeTypes = $this->getMimeTypes();
109
110
        return $mimeTypes[$extension] ?? 'application/octet-stream';
111
    }
112
113
    /**
114
     * Create a placeholder index.html file to prevent an empty directory.
115
     */
116
    private function createPlaceholderFile(string $filesDir): void
117
    {
118
        $placeholderFile = $filesDir.'/index.html';
119
        file_put_contents($placeholderFile, "<!-- Placeholder file to ensure the directory is not empty -->");
120
    }
121
122
    /**
123
     * Copy a file to the export directory using its contenthash.
124
     */
125
    private function copyFileToExportDir(array $file, string $filesDir): void
126
    {
127
        if ($file['filepath'] === '.') {
128
            return;
129
        }
130
131
        $contenthash = $file['contenthash'];
132
        $subDir = substr($contenthash, 0, 2);
133
        $filePath = $this->course->path.$file['documentpath'];
134
        $exportSubDir = $filesDir.'/'.$subDir;
135
136
        // Ensure the subdirectory exists
137
        if (!is_dir($exportSubDir)) {
138
            mkdir($exportSubDir, api_get_permissions_for_new_directories(), true);
139
        }
140
141
        // Copy the file to the export directory
142
        $destinationFile = $exportSubDir.'/'.$contenthash;
143
        if (file_exists($filePath)) {
144
            copy($filePath, $destinationFile);
145
        } else {
146
            throw new Exception("File {$filePath} not found.");
147
        }
148
    }
149
150
    /**
151
     * Create the files.xml with the provided file data.
152
     */
153
    private function createFilesXml(array $filesData, string $destinationDir): void
154
    {
155
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
156
        $xmlContent .= '<files>'.PHP_EOL;
157
158
        foreach ($filesData['files'] as $file) {
159
            $xmlContent .= $this->createFileXmlEntry($file);
160
        }
161
162
        $xmlContent .= '</files>'.PHP_EOL;
163
        file_put_contents($destinationDir.'/files.xml', $xmlContent);
164
    }
165
166
    /**
167
     * Create an XML entry for a file.
168
     */
169
    private function createFileXmlEntry(array $file): string
170
    {
171
        return '  <file id="'.$file['id'].'">'.PHP_EOL.
172
            '    <contenthash>'.htmlspecialchars($file['contenthash']).'</contenthash>'.PHP_EOL.
173
            '    <contextid>'.$file['contextid'].'</contextid>'.PHP_EOL.
174
            '    <component>'.htmlspecialchars($file['component']).'</component>'.PHP_EOL.
175
            '    <filearea>'.htmlspecialchars($file['filearea']).'</filearea>'.PHP_EOL.
176
            '    <itemid>0</itemid>'.PHP_EOL.
177
            '    <filepath>'.htmlspecialchars($file['filepath']).'</filepath>'.PHP_EOL.
178
            '    <filename>'.htmlspecialchars($file['filename']).'</filename>'.PHP_EOL.
179
            '    <userid>'.$file['userid'].'</userid>'.PHP_EOL.
180
            '    <filesize>'.$file['filesize'].'</filesize>'.PHP_EOL.
181
            '    <mimetype>'.htmlspecialchars($file['mimetype']).'</mimetype>'.PHP_EOL.
182
            '    <status>'.$file['status'].'</status>'.PHP_EOL.
183
            '    <timecreated>'.$file['timecreated'].'</timecreated>'.PHP_EOL.
184
            '    <timemodified>'.$file['timemodified'].'</timemodified>'.PHP_EOL.
185
            '    <source>'.htmlspecialchars($file['source']).'</source>'.PHP_EOL.
186
            '    <author>'.htmlspecialchars($file['author']).'</author>'.PHP_EOL.
187
            '    <license>'.htmlspecialchars($file['license']).'</license>'.PHP_EOL.
188
            '    <sortorder>0</sortorder>'.PHP_EOL.
189
            '    <repositorytype>$@NULL@$</repositorytype>'.PHP_EOL.
190
            '    <repositoryid>$@NULL@$</repositoryid>'.PHP_EOL.
191
            '    <reference>$@NULL@$</reference>'.PHP_EOL.
192
            '  </file>'.PHP_EOL;
193
    }
194
195
    /**
196
     * Process a document or folder and add its data to the files array.
197
     */
198
    private function processDocument(array $filesData, object $document): array
199
    {
200
        // Only real files are exported; folders are represented implicitly by "filepath"
201
        if ($document->file_type !== 'file') {
202
            return $filesData;
203
        }
204
205
        // Base file data (contenthash, size, title, etc.)
206
        $fileData = $this->getFileData($document);
207
208
        // Rebuild the relative filepath so Moodle can recreate the Documents tree
209
        $relDir = dirname($document->path);
210
        $filepath = $this->ensureTrailingSlash(
211
            $relDir === '.' ? '/' : '/'.$relDir.'/'
212
        );
213
214
        // Attach this file to the global "Documents" folder activity
215
        $fileData['filepath'] = $filepath;
216
        $fileData['contextid'] = ActivityExport::DOCS_MODULE_ID;
217
        $fileData['component'] = 'mod_folder';
218
        $fileData['filearea'] = 'content';
219
        $fileData['itemid'] = ActivityExport::DOCS_MODULE_ID;
220
221
        $filesData['files'][] = $fileData;
222
223
        return $filesData;
224
    }
225
226
    /**
227
     * Get file data for a single document.
228
     */
229
    private function getFileData(object $document): array
230
    {
231
        $adminData = MoodleExport::getAdminUserData();
232
        $adminId = $adminData['id'];
233
        $contenthash = hash('sha1', basename($document->path));
234
        $mimetype = $this->getMimeType($document->path);
235
236
        return [
237
            'id' => $document->source_id,
238
            'contenthash' => $contenthash,
239
            'contextid' => $document->source_id,
240
            'component' => 'mod_resource',
241
            'filearea' => 'content',
242
            'itemid' => (int) $document->source_id,
243
            'filepath' => '/',
244
            'documentpath' => $document->path,
245
            'filename' => basename($document->path),
246
            'userid' => $adminId,
247
            'filesize' => $document->size,
248
            'mimetype' => $mimetype,
249
            'status' => 0,
250
            'timecreated' => time() - 3600,
251
            'timemodified' => time(),
252
            'source' => $document->title,
253
            'author' => 'Unknown',
254
            'license' => 'allrightsreserved',
255
        ];
256
    }
257
258
    /**
259
     * Get file data for files inside a folder.
260
     */
261
    private function getFolderFileData(array $file, int $sourceId, string $parentPath = '/'): array
0 ignored issues
show
Unused Code introduced by
The method getFolderFileData() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
262
    {
263
        $adminData = MoodleExport::getAdminUserData();
264
        $adminId = $adminData['id'];
265
        $contenthash = hash('sha1', basename($file['path']));
266
        $mimetype = $this->getMimeType($file['path']);
267
        $filename = basename($file['path']);
268
        $relDir = dirname($file['path']);
269
        $filepath = $this->ensureTrailingSlash($relDir === '.' ? '/' : '/'.$relDir.'/');
270
271
        return [
272
            'id' => $file['id'],
273
            'contenthash' => $contenthash,
274
            'contextid' => ActivityExport::DOCS_MODULE_ID,
275
            'component' => 'mod_folder',
276
            'filearea' => 'content',
277
            'itemid' => ActivityExport::DOCS_MODULE_ID,
278
            'filepath' => $filepath,
279
            'documentpath' => 'document/'.$file['path'],
280
            'filename' => $filename,
281
            'userid' => $adminId,
282
            'filesize' => $file['size'],
283
            'mimetype' => $mimetype,
284
            'status' => 0,
285
            'timecreated' => time() - 3600,
286
            'timemodified' => time(),
287
            'source' => $file['title'],
288
            'author' => 'Unknown',
289
            'license' => 'allrightsreserved',
290
        ];
291
    }
292
293
    /**
294
     * Ensure the directory path has a trailing slash.
295
     */
296
    private function ensureTrailingSlash(string $path): string
297
    {
298
        if (empty($path) || $path === '.' || $path === '/') {
299
            return '/';
300
        }
301
302
        $path = preg_replace('/\/+/', '/', $path);
303
304
        return rtrim($path, '/').'/';
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