Completed
Push — master ( 4d7dee...e76cd5 )
by Julito
10:42 queued 17s
created

CourseArchiver::getAvailableBackups()   B

Complexity

Conditions 10
Paths 4

Size

Total Lines 35
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 24
nc 4
nop 1
dl 0
loc 35
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
namespace Chamilo\CourseBundle\Component\CourseCopy;
5
6
use Chamilo\CourseBundle\Component\CourseCopy\Resources\Asset;
7
use Chamilo\CourseBundle\Component\CourseCopy\Resources\Document;
8
use Symfony\Component\Filesystem\Filesystem;
9
10
/**
11
 * Some functions to write a course-object to a zip-file and to read a course-
12
 * object from such a zip-file.
13
 *
14
 * @author Bart Mollet <[email protected]>
15
 *
16
 * @package chamilo.backup
17
 *
18
 * @todo Use archive-folder of Chamilo?
19
 */
20
class CourseArchiver
21
{
22
    /**
23
     * @return string
24
     */
25
    public static function getBackupDir()
26
    {
27
        return api_get_path(SYS_ARCHIVE_PATH).'course_backups/';
28
    }
29
30
    /**
31
     * @return string
32
     */
33
    public static function createBackupDir()
34
    {
35
        $perms = api_get_permissions_for_new_directories();
36
        $dir = self::getBackupDir();
37
        $fs = new Filesystem();
38
        $fs->mkdir($dir, $perms);
39
40
        return $dir;
41
    }
42
43
    /**
44
     * Delete old temp-dirs.
45
     */
46
    public static function cleanBackupDir()
47
    {
48
        $dir = self::getBackupDir();
49
        if (is_dir($dir)) {
50
            if ($handle = @opendir($dir)) {
51
                while (($file = readdir($handle)) !== false) {
52
                    if ($file != "." && $file != ".." &&
53
                        strpos($file, 'CourseArchiver_') === 0 &&
54
                        is_dir($dir.'/'.$file)
55
                    ) {
56
                        rmdirr($dir.'/'.$file);
57
                    }
58
                }
59
                closedir($handle);
60
            }
61
        }
62
    }
63
64
    /**
65
     * Write a course and all its resources to a zip-file.
66
     *
67
     * @return string A pointer to the zip-file
68
     */
69
    public static function createBackup($course)
70
    {
71
        self::cleanBackupDir();
72
        self::createBackupDir();
73
74
        $perm_dirs = api_get_permissions_for_new_directories();
75
        $backupDirectory = self::getBackupDir();
76
77
        // Create a temp directory
78
        $backup_dir = $backupDirectory.'CourseArchiver_'.api_get_unique_id().'/';
79
80
        // All course-information will be stored in course_info.dat
81
        $course_info_file = $backup_dir.'course_info.dat';
82
83
        $user = api_get_user_info();
84
        $date = new \DateTime(api_get_local_time());
85
        $zipFileName = $user['user_id'].'_'.$course->code.'_'.$date->format('Ymd-His').'.zip';
86
        $zipFilePath = $backupDirectory.$zipFileName;
87
88
        $php_errormsg = '';
89
        $res = @mkdir($backup_dir, $perm_dirs);
90
        if ($res === false) {
91
            //TODO set and handle an error message telling the user to review the permissions on the archive directory
92
            error_log(__FILE__.' line '.__LINE__.': '.(ini_get('track_errors') != false ? $php_errormsg : 'error not recorded because track_errors is off in your php.ini').' - This error, occuring because your archive directory will not let this script write data into it, will prevent courses backups to be created', 0);
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing ini_get('track_errors') of type string to the boolean false. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
93
        }
94
        // Write the course-object to the file
95
        $fp = @fopen($course_info_file, 'w');
96
        if ($fp === false) {
97
            error_log(__FILE__.' line '.__LINE__.': '.(ini_get('track_errors') != false ? $php_errormsg : 'error not recorded because track_errors is off in your php.ini'), 0);
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing ini_get('track_errors') of type string to the boolean false. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
98
        }
99
100
        $res = @fwrite($fp, base64_encode(serialize($course)));
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

100
        $res = @fwrite(/** @scrutinizer ignore-type */ $fp, base64_encode(serialize($course)));
Loading history...
101
        if ($res === false) {
102
            error_log(__FILE__.' line '.__LINE__.': '.(ini_get('track_errors') != false ? $php_errormsg : 'error not recorded because track_errors is off in your php.ini'), 0);
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing ini_get('track_errors') of type string to the boolean false. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
103
        }
104
105
        $res = @fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

105
        $res = @fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
106
        if ($res === false) {
107
            error_log(__FILE__.' line '.__LINE__.': '.(ini_get('track_errors') != false ? $php_errormsg : 'error not recorded because track_errors is off in your php.ini'), 0);
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing ini_get('track_errors') of type string to the boolean false. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
108
        }
109
110
        // Copy all documents to the temp-dir
111
        if (isset($course->resources[RESOURCE_DOCUMENT]) && is_array($course->resources[RESOURCE_DOCUMENT])) {
112
            $webEditorCss = api_get_path(WEB_CSS_PATH).'editor.css';
113
            /** @var Document $document */
114
            foreach ($course->resources[RESOURCE_DOCUMENT] as $document) {
115
                if ($document->file_type == DOCUMENT) {
116
                    $doc_dir = $backup_dir.$document->path;
117
                    @mkdir(dirname($doc_dir), $perm_dirs, true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

117
                    /** @scrutinizer ignore-unhandled */ @mkdir(dirname($doc_dir), $perm_dirs, true);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
118
                    if (file_exists($course->path.$document->path)) {
119
                        copy($course->path.$document->path, $doc_dir);
120
                        // Check if is html or htm
121
                        $extension = pathinfo(basename($document->path), PATHINFO_EXTENSION);
122
                        switch ($extension) {
123
                            case 'html':
124
                            case 'htm':
125
                                $contents = file_get_contents($doc_dir);
126
                                $contents = str_replace(
127
                                    $webEditorCss,
128
                                    '{{css_editor}}',
129
                                    $contents
130
                                );
131
                                file_put_contents($doc_dir, $contents);
132
                                break;
133
                        }
134
                    }
135
                } else {
136
                    @mkdir($backup_dir.$document->path, $perm_dirs, true);
137
                }
138
            }
139
        }
140
141
        // Copy all scorm documents to the temp-dir
142
        if (isset($course->resources[RESOURCE_SCORM]) && is_array($course->resources[RESOURCE_SCORM])) {
143
            foreach ($course->resources[RESOURCE_SCORM] as $document) {
144
                copyDirTo($course->path.$document->path, $backup_dir.$document->path, false);
145
            }
146
        }
147
148
        // Copy calendar attachments.
149
        if (isset($course->resources[RESOURCE_EVENT]) && is_array($course->resources[RESOURCE_EVENT])) {
150
            $doc_dir = dirname($backup_dir.'/upload/calendar/');
151
            @mkdir($doc_dir, $perm_dirs, true);
152
            copyDirTo($course->path.'upload/calendar/', $doc_dir, false);
153
        }
154
155
        // Copy Learning path author image.
156
        if (isset($course->resources[RESOURCE_LEARNPATH]) && is_array($course->resources[RESOURCE_LEARNPATH])) {
157
            $doc_dir = dirname($backup_dir.'/upload/learning_path/');
158
            @mkdir($doc_dir, $perm_dirs, true);
159
            copyDirTo($course->path.'upload/learning_path/', $doc_dir, false);
160
        }
161
162
        // Copy announcements attachments.
163
        if (isset($course->resources[RESOURCE_ANNOUNCEMENT]) && is_array($course->resources[RESOURCE_ANNOUNCEMENT])) {
164
            $doc_dir = dirname($backup_dir.'/upload/announcements/');
165
            @mkdir($doc_dir, $perm_dirs, true);
166
            copyDirTo($course->path.'upload/announcements/', $doc_dir, false);
167
        }
168
169
        // Copy work folders (only folders)
170
        if (isset($course->resources[RESOURCE_WORK]) && is_array($course->resources[RESOURCE_WORK])) {
171
            $doc_dir = $backup_dir.'work';
172
            @mkdir($doc_dir, $perm_dirs, true);
173
            copyDirWithoutFilesTo($course->path.'work/', $doc_dir);
174
        }
175
176
        if (isset($course->resources[RESOURCE_ASSET]) && is_array($course->resources[RESOURCE_ASSET])) {
177
            /** @var Asset $asset */
178
            foreach ($course->resources[RESOURCE_ASSET] as $asset) {
179
                $doc_dir = $backup_dir.$asset->path;
180
                @mkdir(dirname($doc_dir), $perm_dirs, true);
181
                $assetPath = $course->path.$asset->path;
182
183
                if (!file_exists($assetPath)) {
184
                    continue;
185
                }
186
187
                if (is_dir($course->path.$asset->path)) {
188
                    copyDirTo($course->path.$asset->path, $doc_dir, false);
189
                    continue;
190
                }
191
                copy($course->path.$asset->path, $doc_dir);
192
            }
193
        }
194
195
        // Zip the course-contents
196
        $zip = new \PclZip($zipFilePath);
197
        $zip->create($backup_dir, PCLZIP_OPT_REMOVE_PATH, $backup_dir);
0 ignored issues
show
Bug introduced by
The constant Chamilo\CourseBundle\Com...\PCLZIP_OPT_REMOVE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
198
199
        // Remove the temp-dir.
200
        rmdirr($backup_dir);
201
202
        return $zipFileName;
203
    }
204
205
    /**
206
     * @param int $user_id
207
     *
208
     * @return array
209
     */
210
    public static function getAvailableBackups($user_id = null)
211
    {
212
        $backup_files = [];
213
        $dirname = self::getBackupDir();
214
215
        if (!file_exists($dirname)) {
216
            $dirname = self::createBackupDir();
217
        }
218
219
        if ($dir = opendir($dirname)) {
220
            while (($file = readdir($dir)) !== false) {
221
                $file_parts = explode('_', $file);
222
                if (count($file_parts) == 3) {
223
                    $owner_id = $file_parts[0];
224
                    $course_code = $file_parts[1];
225
                    $file_parts = explode('.', $file_parts[2]);
226
                    $date = $file_parts[0];
227
                    $ext = isset($file_parts[1]) ? $file_parts[1] : null;
228
                    if ($ext == 'zip' && ($user_id != null && $owner_id == $user_id || $user_id == null)) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $user_id of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison !== instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing $user_id of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
229
                        $date =
230
                            substr($date, 0, 4).'-'.substr($date, 4, 2).'-'.
231
                            substr($date, 6, 2).' '.substr($date, 9, 2).':'.
232
                            substr($date, 11, 2).':'.substr($date, 13, 2);
233
                        $backup_files[] = [
234
                            'file' => $file,
235
                            'date' => $date,
236
                            'course_code' => $course_code,
237
                        ];
238
                    }
239
                }
240
            }
241
            closedir($dir);
242
        }
243
244
        return $backup_files;
245
    }
246
247
    /**
248
     * @param array $file
249
     *
250
     * @return bool|string
251
     */
252
    public static function importUploadedFile($file)
253
    {
254
        $new_filename = uniqid('import_file', true).'.zip';
255
        $new_dir = self::getBackupDir();
256
        if (!is_dir($new_dir)) {
257
            $fs = new Filesystem();
258
            $fs->mkdir($new_dir);
259
        }
260
        if (is_dir($new_dir) && is_writable($new_dir)) {
261
            move_uploaded_file($file, $new_dir.$new_filename);
262
263
            return $new_filename;
264
        }
265
266
        return false;
267
    }
268
269
    /**
270
     * Read a course-object from a zip-file.
271
     *
272
     * @param string $filename
273
     * @param bool   $delete   Delete the file after reading the course?
274
     *
275
     * @return course The course
276
     *
277
     * @todo Check if the archive is a correct Chamilo-export
278
     */
279
    public static function readCourse($filename, $delete = false)
280
    {
281
        self::cleanBackupDir();
282
        // Create a temp directory
283
        $tmp_dir_name = 'CourseArchiver_'.uniqid('');
284
        $unzip_dir = self::getBackupDir().$tmp_dir_name;
285
        $filePath = self::getBackupDir().$filename;
286
287
        @mkdir($unzip_dir, api_get_permissions_for_new_directories(), true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

287
        /** @scrutinizer ignore-unhandled */ @mkdir($unzip_dir, api_get_permissions_for_new_directories(), true);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
288
        @copy(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for copy(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

288
        /** @scrutinizer ignore-unhandled */ @copy(

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
289
            $filePath,
290
            $unzip_dir.'/backup.zip'
291
        );
292
293
        // unzip the archive
294
        $zip = new \PclZip($unzip_dir.'/backup.zip');
295
        @chdir($unzip_dir);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

295
        /** @scrutinizer ignore-unhandled */ @chdir($unzip_dir);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
296
297
        $zip->extract(
298
            PCLZIP_OPT_TEMP_FILE_ON,
0 ignored issues
show
Bug introduced by
The constant Chamilo\CourseBundle\Com...PCLZIP_OPT_TEMP_FILE_ON was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
299
            PCLZIP_CB_PRE_EXTRACT,
0 ignored issues
show
Bug introduced by
The constant Chamilo\CourseBundle\Com...y\PCLZIP_CB_PRE_EXTRACT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
300
            'clean_up_files_in_zip'
301
        );
302
303
        // remove the archive-file
304
        if ($delete) {
305
            @unlink($filePath);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

305
            /** @scrutinizer ignore-unhandled */ @unlink($filePath);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
306
        }
307
308
        // read the course
309
        if (!is_file('course_info.dat')) {
310
            return new Course();
311
        }
312
313
        $fp = @fopen('course_info.dat', "r");
314
        $contents = @fread($fp, filesize('course_info.dat'));
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fread() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

314
        $contents = @fread(/** @scrutinizer ignore-type */ $fp, filesize('course_info.dat'));
Loading history...
315
        @fclose($fp);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for fclose(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

315
        /** @scrutinizer ignore-unhandled */ @fclose($fp);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

315
        @fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
316
317
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Course', 'Course');
318
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\Announcement', 'Announcement');
319
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\Attendance', 'Attendance');
320
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\CalendarEvent', 'CalendarEvent');
321
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\CourseCopyLearnpath', 'CourseCopyLearnpath');
322
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\CourseCopyTestCategory', 'CourseCopyTestCategory');
323
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\CourseDescription', 'CourseDescription');
324
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\CourseSession', 'CourseSession');
325
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\Document', 'Document');
326
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\Forum', 'Forum');
327
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\ForumCategory', 'ForumCategory');
328
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\ForumPost', 'ForumPost');
329
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\ForumTopic', 'ForumTopic');
330
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\Glossary', 'Glossary');
331
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\GradeBookBackup', 'GradeBookBackup');
332
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\Link', 'Link');
333
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\LinkCategory', 'LinkCategory');
334
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\Quiz', 'Quiz');
335
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\QuizQuestion', 'QuizQuestion');
336
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\QuizQuestionOption', 'QuizQuestionOption');
337
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\ScormDocument', 'ScormDocument');
338
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\Survey', 'Survey');
339
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\SurveyInvitation', 'SurveyInvitation');
340
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\SurveyQuestion', 'SurveyQuestion');
341
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\Thematic', 'Thematic');
342
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\ToolIntro', 'ToolIntro');
343
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\Wiki', 'Wiki');
344
        class_alias('Chamilo\CourseBundle\Component\CourseCopy\Resources\Work', 'Work');
345
346
        /** @var Course $course */
347
        $course = \UnserializeApi::unserialize('course', base64_decode($contents));
348
349
        if (!in_array(
350
            get_class($course),
351
            ['Course', 'Chamilo\CourseBundle\Component\CourseCopy\Course']
352
        )
353
        ) {
354
            return new Course();
355
        }
356
357
        $course->backup_path = $unzip_dir;
0 ignored issues
show
Bug Best Practice introduced by
The property backup_path does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
358
359
        return $course;
360
    }
361
}
362