DocumentManager   F
last analyzed

Complexity

Total Complexity 425

Size/Duplication

Total Lines 3352
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 1786
dl 0
loc 3352
rs 0.8
c 0
b 0
f 0
wmc 425

48 Methods

Rating   Name   Duplication   Size   Complexity  
A get_course_quota() 0 20 5
F file_send_for_download() 0 139 34
B smartReadFile() 0 54 11
B file_get_mime_type() 0 214 4
A get_document_id() 0 26 5
A getSessionFolderFilters() 0 13 3
A delete_document_from_search_engine() 0 20 3
B delete_document() 0 61 10
F get_all_document_folders() 0 176 23
A attach_gradebook_certificate() 0 19 4
A get_default_certificate_id() 0 28 5
B is_visible() 0 78 7
A setDocumentAsTemplate() 0 21 1
B get_document_data_by_id() 0 53 8
A generateAudioJavascript() 0 12 1
A getDocumentDefaultVisibility() 0 22 6
A create_directory_certificate_in_course() 0 19 3
A generateAudioPreview() 0 7 1
A replaceUrlWithNewCourseCode() 0 36 6
A get_document_id_of_directory_certificate() 0 10 1
D parse_HTML_attributes() 0 82 21
D build_document_icon_tag() 0 104 24
A isBasicCourseFolder() 0 6 1
D addDocument() 0 111 17
A generateNewUrlForCourseResource() 0 10 1
A enough_space() 0 14 3
A createDefaultAudioFolder() 0 7 2
B generateDefaultCertificate() 0 64 6
A getUniqueFileName() 0 22 2
A documentExists() 0 43 3
A folderExists() 0 40 3
A generateMediaPreview() 0 18 4
A get_system_folders() 0 12 1
D get_text_content() 0 72 20
A addSuffixToFileName() 0 17 4
F index_document() 0 220 31
A remove_attach_certificate() 0 23 6
A displaySimpleQuota() 0 10 1
A getDocumentSuffix() 0 8 3
A getProtectedFolderFromStudent() 0 8 1
B get_document_preview() 0 113 9
F get_resources_from_source_html() 0 364 66
C build_directory_selector() 0 107 17
A is_certificate_mode() 0 11 5
B is_folder_to_avoid() 0 49 10
F get_all_info_to_certificate() 0 158 13
A fixDocumentName() 0 14 3
B replace_user_info_into_html() 0 61 7

How to fix   Complexity   

Complex Class

Complex classes like DocumentManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DocumentManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Course;
6
use Chamilo\CoreBundle\Entity\ResourceLink;
7
use Chamilo\CoreBundle\Entity\ResourceNode;
8
use Chamilo\CoreBundle\Entity\User;
9
use Chamilo\CoreBundle\Framework\Container;
10
use Chamilo\CourseBundle\Entity\CDocument;
11
use ChamiloSession as Session;
12
use Chamilo\CoreBundle\Component\Utils\ObjectIcon;
13
14
/**
15
 *  Class DocumentManager
16
 *  This is the document library for Chamilo.
17
 *  It is / will be used to provide a service layer to all document-using tools.
18
 *  and eliminate code duplication fro group documents, scorm documents, main documents.
19
 *  Include/require it in your code to use its functionality.
20
 */
21
class DocumentManager
22
{
23
    /**
24
     * @param string $course_code
25
     *
26
     * @return int the document folder quota for the current course in megabytes
27
     *             or the default quota
28
     */
29
    public static function get_course_quota($course_code = null)
30
    {
31
        if (empty($course_code)) {
32
            $course_info = api_get_course_info();
33
        } else {
34
            $course_info = api_get_course_info($course_code);
35
        }
36
37
        $course_quota = null;
38
        if (empty($course_info)) {
39
            return DEFAULT_DOCUMENT_QUOTA;
40
        } else {
41
            $course_quota = $course_info['disk_quota'];
42
        }
43
        if (is_null($course_quota) || empty($course_quota)) {
44
            // Course table entry for quota was null, then use default value
45
            $course_quota = DEFAULT_DOCUMENT_QUOTA;
46
        }
47
48
        return $course_quota;
49
    }
50
51
    /**
52
     * Get the content type of a file by checking the extension
53
     * We could use mime_content_type() with php-versions > 4.3,
54
     * but this doesn't work as it should on Windows installations.
55
     *
56
     * @param string $filename or boolean TRUE to return complete array
57
     *
58
     * @author ? first version
59
     * @author Bert Vanderkimpen
60
     *
61
     * @return string
62
     */
63
    public static function file_get_mime_type($filename)
64
    {
65
        // All MIME types in an array (from 1.6, this is the authorative source)
66
        // Please, keep this alphabetical if you add something to this list!
67
        $mimeTypes = [
68
            'ai' => 'application/postscript',
69
            'aif' => 'audio/x-aiff',
70
            'aifc' => 'audio/x-aiff',
71
            'aiff' => 'audio/x-aiff',
72
            'asf' => 'video/x-ms-asf',
73
            'asc' => 'text/plain',
74
            'au' => 'audio/basic',
75
            'avi' => 'video/x-msvideo',
76
            'bcpio' => 'application/x-bcpio',
77
            'bin' => 'application/octet-stream',
78
            'bmp' => 'image/bmp',
79
            'cdf' => 'application/x-netcdf',
80
            'class' => 'application/octet-stream',
81
            'cpio' => 'application/x-cpio',
82
            'cpt' => 'application/mac-compactpro',
83
            'csh' => 'application/x-csh',
84
            'css' => 'text/css',
85
            'dcr' => 'application/x-director',
86
            'dir' => 'application/x-director',
87
            'djv' => 'image/vnd.djvu',
88
            'djvu' => 'image/vnd.djvu',
89
            'dll' => 'application/octet-stream',
90
            'dmg' => 'application/x-diskcopy',
91
            'dms' => 'application/octet-stream',
92
            'doc' => 'application/msword',
93
            'docm' => 'application/vnd.ms-word.document.macroEnabled.12',
94
            'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
95
            'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
96
            'dvi' => 'application/x-dvi',
97
            'dwg' => 'application/vnd.dwg',
98
            'dwf' => 'application/vnd.dwf',
99
            'dxf' => 'application/vnd.dxf',
100
            'dxr' => 'application/x-director',
101
            'eps' => 'application/postscript',
102
            'epub' => 'application/epub+zip',
103
            'etx' => 'text/x-setext',
104
            'exe' => 'application/octet-stream',
105
            'ez' => 'application/andrew-inset',
106
            'flv' => 'video/flv',
107
            'gif' => 'image/gif',
108
            'gtar' => 'application/x-gtar',
109
            'gz' => 'application/x-gzip',
110
            'hdf' => 'application/x-hdf',
111
            'hqx' => 'application/mac-binhex40',
112
            'htm' => 'text/html',
113
            'html' => 'text/html',
114
            'ice' => 'x-conference-xcooltalk',
115
            'ief' => 'image/ief',
116
            'iges' => 'model/iges',
117
            'igs' => 'model/iges',
118
            'jar' => 'application/java-archiver',
119
            'jpe' => 'image/jpeg',
120
            'jpeg' => 'image/jpeg',
121
            'jpg' => 'image/jpeg',
122
            'js' => 'application/x-javascript',
123
            'kar' => 'audio/midi',
124
            'lam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
125
            'latex' => 'application/x-latex',
126
            'lha' => 'application/octet-stream',
127
            'log' => 'text/plain',
128
            'lzh' => 'application/octet-stream',
129
            'm1a' => 'audio/mpeg',
130
            'm2a' => 'audio/mpeg',
131
            'm3u' => 'audio/x-mpegurl',
132
            'man' => 'application/x-troff-man',
133
            'me' => 'application/x-troff-me',
134
            'mesh' => 'model/mesh',
135
            'mid' => 'audio/midi',
136
            'midi' => 'audio/midi',
137
            'mov' => 'video/quicktime',
138
            'movie' => 'video/x-sgi-movie',
139
            'mp2' => 'audio/mpeg',
140
            'mp3' => 'audio/mpeg',
141
            'mp4' => 'video/mp4',
142
            'mpa' => 'audio/mpeg',
143
            'mpe' => 'video/mpeg',
144
            'mpeg' => 'video/mpeg',
145
            'mpg' => 'video/mpeg',
146
            'mpga' => 'audio/mpeg',
147
            'ms' => 'application/x-troff-ms',
148
            'msh' => 'model/mesh',
149
            'mxu' => 'video/vnd.mpegurl',
150
            'nc' => 'application/x-netcdf',
151
            'oda' => 'application/oda',
152
            'oga' => 'audio/ogg',
153
            'ogg' => 'application/ogg',
154
            'ogx' => 'application/ogg',
155
            'ogv' => 'video/ogg',
156
            'pbm' => 'image/x-portable-bitmap',
157
            'pct' => 'image/pict',
158
            'pdb' => 'chemical/x-pdb',
159
            'pdf' => 'application/pdf',
160
            'pgm' => 'image/x-portable-graymap',
161
            'pgn' => 'application/x-chess-pgn',
162
            'pict' => 'image/pict',
163
            'png' => 'image/png',
164
            'pnm' => 'image/x-portable-anymap',
165
            'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
166
            'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
167
            'pps' => 'application/vnd.ms-powerpoint',
168
            'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
169
            'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
170
            'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
171
            'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
172
            'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
173
            'ppm' => 'image/x-portable-pixmap',
174
            'ppt' => 'application/vnd.ms-powerpoint',
175
            'ps' => 'application/postscript',
176
            'qt' => 'video/quicktime',
177
            'ra' => 'audio/x-realaudio',
178
            'ram' => 'audio/x-pn-realaudio',
179
            'rar' => 'image/x-rar-compressed',
180
            'ras' => 'image/x-cmu-raster',
181
            'rgb' => 'image/x-rgb',
182
            'rm' => 'audio/x-pn-realaudio',
183
            'roff' => 'application/x-troff',
184
            'rpm' => 'audio/x-pn-realaudio-plugin',
185
            'rtf' => 'text/rtf',
186
            'rtx' => 'text/richtext',
187
            'sgm' => 'text/sgml',
188
            'sgml' => 'text/sgml',
189
            'sh' => 'application/x-sh',
190
            'shar' => 'application/x-shar',
191
            'silo' => 'model/mesh',
192
            'sib' => 'application/X-Sibelius-Score',
193
            'sit' => 'application/x-stuffit',
194
            'skd' => 'application/x-koan',
195
            'skm' => 'application/x-koan',
196
            'skp' => 'application/x-koan',
197
            'skt' => 'application/x-koan',
198
            'smi' => 'application/smil',
199
            'smil' => 'application/smil',
200
            'snd' => 'audio/basic',
201
            'so' => 'application/octet-stream',
202
            'spl' => 'application/x-futuresplash',
203
            'src' => 'application/x-wais-source',
204
            'sv4cpio' => 'application/x-sv4cpio',
205
            'sv4crc' => 'application/x-sv4crc',
206
            'svf' => 'application/vnd.svf',
207
            'svg' => 'image/svg+xml',
208
            //'svgz' => 'image/svg+xml',
209
            'swf' => 'application/x-shockwave-flash',
210
            'sxc' => 'application/vnd.sun.xml.calc',
211
            'sxi' => 'application/vnd.sun.xml.impress',
212
            'sxw' => 'application/vnd.sun.xml.writer',
213
            't' => 'application/x-troff',
214
            'tar' => 'application/x-tar',
215
            'tcl' => 'application/x-tcl',
216
            'tex' => 'application/x-tex',
217
            'texi' => 'application/x-texinfo',
218
            'texinfo' => 'application/x-texinfo',
219
            'tga' => 'image/x-targa',
220
            'tif' => 'image/tif',
221
            'tiff' => 'image/tiff',
222
            'tr' => 'application/x-troff',
223
            'tsv' => 'text/tab-seperated-values',
224
            'txt' => 'text/plain',
225
            'ustar' => 'application/x-ustar',
226
            'vcd' => 'application/x-cdlink',
227
            'vrml' => 'model/vrml',
228
            'wav' => 'audio/x-wav',
229
            'wbmp' => 'image/vnd.wap.wbmp',
230
            'wbxml' => 'application/vnd.wap.wbxml',
231
            'webp' => 'image/webp',
232
            'wml' => 'text/vnd.wap.wml',
233
            'wmlc' => 'application/vnd.wap.wmlc',
234
            'wmls' => 'text/vnd.wap.wmlscript',
235
            'wmlsc' => 'application/vnd.wap.wmlscriptc',
236
            'wma' => 'audio/x-ms-wma',
237
            'wmv' => 'video/x-ms-wmv',
238
            'wrl' => 'model/vrml',
239
            'xbm' => 'image/x-xbitmap',
240
            'xht' => 'application/xhtml+xml',
241
            'xhtml' => 'application/xhtml+xml',
242
            'xls' => 'application/vnd.ms-excel',
243
            'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
244
            'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
245
            'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
246
            'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
247
            'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
248
            'xml' => 'text/xml',
249
            'xpm' => 'image/x-xpixmap',
250
            'xsl' => 'text/xml',
251
            'xwd' => 'image/x-windowdump',
252
            'xyz' => 'chemical/x-xyz',
253
            'zip' => 'application/zip',
254
        ];
255
256
        if (true === $filename) {
257
            return $mimeTypes;
258
        }
259
260
        // Get the extension of the file
261
        $extension = explode('.', $filename);
262
263
        // $filename will be an array if a . was found
264
        if (is_array($extension)) {
265
            $extension = strtolower($extension[count($extension) - 1]);
266
        } else {
267
            //file without extension
268
            $extension = 'empty';
269
        }
270
271
        //if the extension is found, return the content type
272
        if (isset($mimeTypes[$extension])) {
273
            return $mimeTypes[$extension];
274
        }
275
276
        return 'application/octet-stream';
277
    }
278
279
    /**
280
     * This function smart streams a file to the client using HTTP headers.
281
     *
282
     * @param string $fullFilename The full path of the file to be sent
283
     * @param string $filename     The name of the file as shown to the client
284
     * @param string $contentType  The MIME type of the file
285
     *
286
     * @return bool false if file doesn't exist, true if stream succeeded
287
     */
288
    public static function smartReadFile($fullFilename, $filename, $contentType = 'application/octet-stream')
289
    {
290
        if (!file_exists($fullFilename)) {
291
            header("HTTP/1.1 404 Not Found");
292
293
            return false;
294
        }
295
296
        $size = filesize($fullFilename);
297
        $time = date('r', filemtime($fullFilename));
298
299
        $fm = @fopen($fullFilename, 'rb');
300
        if (!$fm) {
0 ignored issues
show
introduced by
$fm is of type false|resource, thus it always evaluated to false.
Loading history...
301
            header("HTTP/1.1 505 Internal server error");
302
303
            return false;
304
        }
305
306
        $begin = 0;
307
        $end = $size - 1;
308
309
        if (isset($_SERVER['HTTP_RANGE'])) {
310
            if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
311
                $begin = intval($matches[1]);
312
                if (!empty($matches[2])) {
313
                    $end = intval($matches[2]);
314
                }
315
            }
316
        }
317
318
        if (isset($_SERVER['HTTP_RANGE'])) {
319
            header('HTTP/1.1 206 Partial Content');
320
        } else {
321
            header('HTTP/1.1 200 OK');
322
        }
323
324
        header("Content-Type: $contentType");
325
        header('Cache-Control: public, must-revalidate, max-age=0');
326
        header('Pragma: no-cache');
327
        header('Accept-Ranges: bytes');
328
        header('Content-Length:'.(($end - $begin) + 1));
329
        if (isset($_SERVER['HTTP_RANGE'])) {
330
            header("Content-Range: bytes $begin-$end/$size");
331
        }
332
        header("Content-Disposition: inline; filename=$filename");
333
        header("Content-Transfer-Encoding: binary");
334
        header("Last-Modified: $time");
335
336
        $cur = $begin;
337
        fseek($fm, $begin, 0);
338
339
        while (!feof($fm) && $cur <= $end && (0 == connection_status())) {
340
            echo fread($fm, min(1024 * 16, ($end - $cur) + 1));
341
            $cur += 1024 * 16;
342
        }
343
    }
344
345
    /**
346
     * This function streams a file to the client.
347
     *
348
     * @param string $full_file_name
349
     * @param bool   $forced              Whether to force the browser to download the file
350
     * @param string $name
351
     * @param bool   $fixLinksHttpToHttps change file content from http to https
352
     * @param array  $extraHeaders        Additional headers to be sent
353
     *
354
     * @return false if file doesn't exist, true if stream succeeded
355
     */
356
    public static function file_send_for_download(
357
        $full_file_name,
358
        $forced = false,
359
        $name = '',
360
        $fixLinksHttpToHttps = false,
361
        $extraHeaders = []
362
    ) {
363
        session_write_close(); //we do not need write access to session anymore
364
        if (!is_file($full_file_name)) {
365
            return false;
366
        }
367
        $filename = '' == $name ? basename($full_file_name) : api_replace_dangerous_char($name);
368
        $len = filesize($full_file_name);
369
        // Fixing error when file name contains a ","
370
        $filename = str_replace(',', '', $filename);
371
        $sendFileHeaders = ('true' === api_get_setting('document.enable_x_sendfile_headers'));
372
373
        // Allows chrome to make videos and audios seekable
374
        header('Accept-Ranges: bytes');
375
        if (!empty($extraHeaders)) {
376
            foreach ($extraHeaders as $name => $value) {
377
                //TODO: add restrictions to allowed headers?
378
                header($name.': '.$value);
379
            }
380
        }
381
382
        if ($forced) {
383
            // Force the browser to save the file instead of opening it
384
            if (isset($sendFileHeaders) &&
385
                !empty($sendFileHeaders)) {
386
                header("X-Sendfile: $filename");
387
            }
388
389
            header('Content-type: application/octet-stream');
390
            header('Content-length: '.$len);
391
            if (preg_match("/MSIE 5.5/", $_SERVER['HTTP_USER_AGENT'])) {
392
                header('Content-Disposition: filename= '.$filename);
393
            } else {
394
                header('Content-Disposition: attachment; filename= '.$filename);
395
            }
396
            if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
397
                header('Pragma: ');
398
                header('Cache-Control: ');
399
                header('Cache-Control: public'); // IE cannot download from sessions without a cache
400
            }
401
            header('Content-Description: '.$filename);
402
            header('Content-Transfer-Encoding: binary');
403
404
            if (function_exists('ob_end_clean') && ob_get_length()) {
405
                // Use ob_end_clean() to avoid weird buffering situations
406
                // where file is sent broken/incomplete for download
407
                ob_end_clean();
408
            }
409
410
            $res = fopen($full_file_name, 'r');
411
            fpassthru($res);
412
413
            return true;
414
        } else {
415
            // no forced download, just let the browser decide what to do according to the mimetype
416
            $lpFixedEncoding = 'true' === api_get_setting('lp.lp_fixed_encoding');
417
418
            // Commented to let courses content to be cached in order to improve performance:
419
            //header('Expires: Wed, 01 Jan 1990 00:00:00 GMT');
420
            //header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
421
422
            // Commented to avoid double caching declaration when playing with IE and HTTPS
423
            //header('Cache-Control: no-cache, must-revalidate');
424
            //header('Pragma: no-cache');
425
426
            $contentType = self::file_get_mime_type($filename);
427
428
            switch ($contentType) {
429
                case 'text/html':
430
                    if (isset($lpFixedEncoding) && 'true' === $lpFixedEncoding) {
431
                        $contentType .= '; charset=UTF-8';
432
                    } else {
433
                        $encoding = @api_detect_encoding_html(file_get_contents($full_file_name));
434
                        if (!empty($encoding)) {
435
                            $contentType .= '; charset='.$encoding;
436
                        }
437
                    }
438
                    break;
439
                case 'text/plain':
440
                    if (isset($lpFixedEncoding) && 'true' === $lpFixedEncoding) {
441
                        $contentType .= '; charset=UTF-8';
442
                    } else {
443
                        $encoding = @api_detect_encoding(strip_tags(file_get_contents($full_file_name)));
444
                        if (!empty($encoding)) {
445
                            $contentType .= '; charset='.$encoding;
446
                        }
447
                    }
448
                    break;
449
                case 'video/mp4':
450
                case 'audio/mpeg':
451
                case 'audio/mp4':
452
                case 'audio/ogg':
453
                case 'audio/webm':
454
                case 'audio/wav':
455
                case 'video/ogg':
456
                case 'video/webm':
457
                    self::smartReadFile($full_file_name, $filename, $contentType);
458
                    exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
459
                case 'application/vnd.dwg':
460
                case 'application/vnd.dwf':
461
                    header('Content-type: application/octet-stream');
462
                    break;
463
            }
464
465
            header('Content-type: '.$contentType);
466
            header('Content-Length: '.$len);
467
            $userAgent = strtolower($_SERVER['HTTP_USER_AGENT']);
468
469
            if (strpos($userAgent, 'msie')) {
470
                header('Content-Disposition: ; filename= '.$filename);
471
            } else {
472
                //header('Content-Disposition: inline');
473
                header('Content-Disposition: inline;');
474
            }
475
476
            if ($fixLinksHttpToHttps) {
477
                $content = file_get_contents($full_file_name);
478
                $content = str_replace(
479
                    ['http%3A%2F%2F', 'http://'],
480
                    ['https%3A%2F%2F', 'https://'],
481
                    $content
482
                );
483
                echo $content;
484
            } else {
485
                if (function_exists('ob_end_clean') && ob_get_length()) {
486
                    // Use ob_end_clean() to avoid weird buffering situations
487
                    // where file is sent broken/incomplete for download
488
                    ob_end_clean();
489
                }
490
491
                readfile($full_file_name);
492
            }
493
494
            return true;
495
        }
496
    }
497
498
    /**
499
     * Session folder filters.
500
     *
501
     * @param string $path
502
     * @param int    $sessionId
503
     *
504
     * @return string|null
505
     */
506
    public static function getSessionFolderFilters($path, $sessionId)
507
    {
508
        $sessionId = (int) $sessionId;
509
        $condition = null;
510
511
        if (!empty($sessionId)) {
512
            // Chat folder filter
513
            if ('/chat_files' == $path) {
514
                $condition .= " AND (docs.session_id = '$sessionId') ";
515
            }
516
        }
517
518
        return $condition;
519
    }
520
521
    /**
522
     * Gets the paths of all folders in a course
523
     * can show all folders (except for the deleted ones) or only visible ones.
524
     *
525
     * @param array  $courseInfo
526
     * @param int    $groupIid          iid
527
     * @param bool   $can_see_invisible
528
     * @param bool   $getInvisibleList
529
     * @param string $path              current path
530
     *
531
     * @return array with paths
532
     */
533
    public static function get_all_document_folders(
534
        $courseInfo,
535
        $groupIid = 0,
536
        $can_see_invisible = false,
537
        $getInvisibleList = false,
538
        $path = ''
539
    ) {
540
        if (empty($courseInfo)) {
541
            return [];
542
        }
543
544
        $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
545
        $groupIid = (int) $groupIid;
546
        $courseId = $courseInfo['real_id'];
547
        $sessionId = api_get_session_id();
548
549
        $folders = [];
550
        $students = CourseManager::get_user_list_from_course_code(
551
            $courseInfo['code'],
552
            api_get_session_id()
553
        );
554
555
        $conditionList = [];
556
        if (!empty($students)) {
557
            foreach ($students as $studentId => $studentInfo) {
558
                $conditionList[] = '/shared_folder/sf_user_'.$studentInfo['user_id'];
559
            }
560
        }
561
562
        $groupCondition = " l.group_id = $groupIid";
563
        if (empty($groupIid)) {
564
            $groupCondition = ' (l.group_id = 0 OR l.group_id IS NULL)';
565
        }
566
567
        $show_users_condition = '';
568
        if ($can_see_invisible) {
569
            $sessionId = $sessionId ?: api_get_session_id();
570
            $condition_session = " AND (l.session_id = '$sessionId' OR (l.session_id = '0' OR l.session_id IS NULL) )";
571
            $condition_session .= self::getSessionFolderFilters($path, $sessionId);
572
573
            $sql = "SELECT DISTINCT docs.iid, n.path
574
                    FROM resource_node AS n
575
                    INNER JOIN $TABLE_DOCUMENT AS docs
576
                    ON (docs.resource_node_id = n.id)
577
                    INNER JOIN resource_link l
578
                    ON (l.resource_node_id = n.id)
579
                    WHERE
580
                        l.c_id = $courseId AND
581
                        docs.filetype = 'folder' AND
582
                        $groupCondition AND
583
                        n.path NOT LIKE '%shared_folder%' AND
584
                        l.deleted_at IS NULL
585
                        $condition_session ";
586
587
            if (0 != $groupIid) {
588
                $sql .= " AND n.path NOT LIKE '%shared_folder%' ";
589
            } else {
590
                $sql .= $show_users_condition;
591
            }
592
593
            $result = Database::query($sql);
594
            if ($result && 0 != Database::num_rows($result)) {
595
                while ($row = Database::fetch_assoc($result)) {
596
                    if (self::is_folder_to_avoid($row['path'])) {
597
                        continue;
598
                    }
599
600
                    if (false !== strpos($row['path'], '/shared_folder/')) {
601
                        if (!in_array($row['path'], $conditionList)) {
602
                            continue;
603
                        }
604
                    }
605
606
                    $folders[$row['iid']] = $row['path'];
607
                }
608
609
                if (!empty($folders)) {
610
                    natsort($folders);
611
                }
612
613
                return $folders;
614
            }
615
616
            return false;
617
        } else {
618
            // No invisible folders
619
            // Condition for the session
620
            $condition_session = api_get_session_condition(
621
                $sessionId,
622
                true,
623
                false,
624
                'docs.session_id'
625
            );
626
627
            $visibilityCondition = 'l.visibility = 1';
628
            $fileType = "docs.filetype = 'folder' AND";
629
            if ($getInvisibleList) {
630
                $visibilityCondition = 'l.visibility = 0';
631
                $fileType = '';
632
            }
633
634
            //get visible folders
635
            $sql = "SELECT DISTINCT docs.id
636
                    FROM resource_node AS n
637
                    INNER JOIN $TABLE_DOCUMENT  AS docs
638
                    ON (docs.resource_node_id = n.id)
639
                    INNER JOIN resource_link l
640
                    ON (l.resource_node_id = n.id)
641
                    WHERE
642
                        $fileType
643
                        $groupCondition AND
644
                        $visibilityCondition
645
                        $show_users_condition
646
                        $condition_session AND
647
                        l.c_id = $courseId ";
648
            $result = Database::query($sql);
649
            $visibleFolders = [];
650
            while ($row = Database::fetch_assoc($result)) {
651
                $visibleFolders[$row['id']] = $row['path'];
652
            }
653
654
            if ($getInvisibleList) {
655
                return $visibleFolders;
656
            }
657
658
            // get invisible folders
659
            $sql = "SELECT DISTINCT docs.iid, n.path
660
                    FROM resource_node AS n
661
                    INNER JOIN $TABLE_DOCUMENT  AS docs
662
                    ON (docs.resource_node_id = n.id)
663
                    INNER JOIN resource_link l
664
                    ON (l.resource_node_id = n.id)
665
                    WHERE
666
                        docs.filetype = 'folder' AND
667
                        $groupCondition AND
668
                        l.visibility IN ('".ResourceLink::VISIBILITY_PENDING."')
669
                        $condition_session AND
670
                        l.c_id = $courseId ";
671
            $result = Database::query($sql);
672
            $invisibleFolders = [];
673
            while ($row = Database::fetch_assoc($result)) {
674
                //get visible folders in the invisible ones -> they are invisible too
675
                $sql = "SELECT DISTINCT docs.iid, n.path
676
                        FROM resource_node AS n
677
                        INNER JOIN $TABLE_DOCUMENT  AS docs
678
                        ON (docs.resource_node_id = n.id)
679
                        INNER JOIN resource_link l
680
                        ON (l.resource_node_id = n.id)
681
                        WHERE
682
                            docs.filetype = 'folder' AND
683
                            $groupCondition AND
684
                            l.deleted_at IS NULL
685
                            $condition_session AND
686
                            l.c_id = $courseId ";
687
                $folder_in_invisible_result = Database::query($sql);
688
                while ($folders_in_invisible_folder = Database::fetch_assoc($folder_in_invisible_result)) {
689
                    $invisibleFolders[$folders_in_invisible_folder['id']] = $folders_in_invisible_folder['path'];
690
                }
691
            }
692
693
            // If both results are arrays -> //calculate the difference between the 2 arrays -> only visible folders are left :)
694
            if (is_array($visibleFolders) && is_array($invisibleFolders)) {
695
                $folders = array_diff($visibleFolders, $invisibleFolders);
696
                natsort($folders);
697
698
                return $folders;
699
            }
700
701
            if (is_array($visibleFolders)) {
702
                natsort($visibleFolders);
703
704
                return $visibleFolders;
705
            }
706
707
            // no visible folders found
708
            return false;
709
        }
710
    }
711
712
    /**
713
     * This deletes a document by changing visibility to 2, renaming it to filename_DELETED_#id
714
     * Files/folders that are inside a deleted folder get visibility 2.
715
     *
716
     * @param array  $_course
717
     * @param string $path          Path stored in the database
718
     * @param string $base_work_dir Path to the documents folder (if not defined, $documentId must be used)
719
     * @param int    $sessionId     The ID of the session, if any
720
     * @param int    $documentId    The document id, if available
721
     * @param int    $groupId       iid
722
     *
723
     * @return bool true/false
724
     *
725
     * @todo now only files/folders in a folder get visibility 2, we should rename them too.
726
     * @todo We should be able to get rid of this later when using only documentId (check further usage)
727
     */
728
    public static function delete_document(
729
        $_course,
730
        $path = null,
731
        $base_work_dir = null,
732
        $sessionId = null,
733
        $documentId = null,
734
        $groupId = 0
735
    ) {
736
        $groupId = (int) $groupId;
737
        if (empty($groupId)) {
738
            $groupId = api_get_group_id();
739
        }
740
741
        $sessionId = (int) $sessionId;
742
        if (empty($sessionId)) {
743
            $sessionId = api_get_session_id();
744
        }
745
746
        $course_id = $_course['real_id'];
747
748
        if (empty($course_id)) {
749
            return false;
750
        }
751
752
        if (empty($documentId)) {
753
            $documentId = self::get_document_id($_course, $path, $sessionId);
754
            $docInfo = self::get_document_data_by_id(
755
                $documentId,
756
                $_course['code'],
757
                false,
758
                $sessionId
759
            );
760
            $path = $docInfo['path'];
761
        } else {
762
            $docInfo = self::get_document_data_by_id(
763
                $documentId,
764
                $_course['code'],
765
                false,
766
                $sessionId
767
            );
768
            if (empty($docInfo)) {
769
                return false;
770
            }
771
            $path = $docInfo['path'];
772
        }
773
774
        $documentId = (int) $documentId;
775
776
        if (empty($path) || empty($docInfo) || empty($documentId)) {
777
            return false;
778
        }
779
780
        $repo = Container::getDocumentRepository();
781
        $document = $repo->find($docInfo['iid']);
782
        if ($document) {
783
            $repo->hardDelete($document);
784
785
            return true;
786
        }
787
788
        return false;
789
    }
790
791
    /**
792
     * Removes documents from search engine database.
793
     *
794
     * @param string $course_id   Course code
795
     * @param int    $document_id Document id to delete
796
     */
797
    public static function delete_document_from_search_engine($course_id, $document_id)
798
    {
799
        // remove from search engine if enabled
800
        if ('true' === api_get_setting('search_enabled')) {
801
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
802
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
803
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_DOCUMENT, $document_id);
804
            $res = Database::query($sql);
805
            if (Database::num_rows($res) > 0) {
806
                $row2 = Database::fetch_array($res);
807
                $di = new ChamiloIndexer();
808
                $di->remove_document($row2['search_did']);
809
            }
810
            $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
811
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_DOCUMENT, $document_id);
812
            Database::query($sql);
813
814
            // remove terms from db
815
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
816
            delete_all_values_for_item($course_id, TOOL_DOCUMENT, $document_id);
817
        }
818
    }
819
820
    /**
821
     * Gets the id of a document with a given path.
822
     *
823
     * @param array  $courseInfo
824
     * @param string $path
825
     * @param int    $sessionId
826
     *
827
     * @return int id of document / false if no doc found
828
     */
829
    public static function get_document_id($courseInfo, $path, $sessionId = 0)
830
    {
831
        $table = Database::get_course_table(TABLE_DOCUMENT);
832
        $courseId = $courseInfo['real_id'];
833
834
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
835
        $sessionCondition = api_get_session_condition($sessionId, true);
836
837
        $path = Database::escape_string($path);
838
        if (!empty($courseId) && !empty($path)) {
839
            $sql = "SELECT iid FROM $table
840
                    WHERE
841
                        c_id = $courseId AND
842
                        path LIKE BINARY '$path'
843
                        $sessionCondition
844
                    LIMIT 1";
845
846
            $result = Database::query($sql);
847
            if (Database::num_rows($result)) {
848
                $row = Database::fetch_array($result);
849
850
                return (int) $row['iid'];
851
            }
852
        }
853
854
        return false;
855
    }
856
857
    /**
858
     * Gets the document data with a given id.
859
     *
860
     * @param int    $id            Document Id (id field in c_document table)
861
     * @param string $course_code   Course code
862
     * @param bool   $load_parents  load folder parents
863
     * @param int    $session_id    The session ID,
864
     *                              0 if requires context *out of* session, and null to use global context
865
     * @param bool   $ignoreDeleted
866
     *
867
     * @deprecated  use $repo->find()
868
     *
869
     * @return array document content
870
     */
871
    public static function get_document_data_by_id(
872
        int    $id,
873
        string $course_code,
874
        bool   $load_parents = false,
875
        int    $session_id = null,
876
        bool $ignoreDeleted = false
877
    ): bool|array {
878
        $course_info = api_get_course_info($course_code);
879
        if (empty($course_info)) {
880
            return false;
881
        }
882
883
        $course_id = $course_info['real_id'];
884
        $session_id = empty($session_id) ? api_get_session_id() : (int) $session_id;
885
        $groupId = api_get_group_id();
886
887
        $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
888
        $id = (int) $id;
889
890
        $sql = "SELECT * FROM $TABLE_DOCUMENT WHERE iid = $id";
891
        $result = Database::query($sql);
892
        if ($result && 1 == Database::num_rows($result)) {
893
            $row = Database::fetch_assoc($result);
894
895
            // Adjust paths for URLs based on new system
896
            $row['url'] = api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id;
897
            $row['document_url'] = api_get_path(WEB_CODE_PATH).'document/document.php?id='.$id;
898
            // Consider storing a relative path or identifier in the database to construct paths
899
            $row['basename'] = $id;  // You may store and use titles or another unique identifier
900
901
            // Handling the parent ID should be adjusted if the path isn't available
902
            $row['parent_id'] = $row['parent_resource_node_id'] ?? '0';  // Adjust according to your schema
903
904
            $parents = [];
905
            if ($load_parents) {
906
                // Modify this logic to work with parent IDs stored directly in the database
907
                $current_id = $row['parent_id'];
908
                while ($current_id != '0') {
909
                    $parent_data = self::get_document_data_by_id($current_id, $course_code, false, $session_id);
910
                    if ($parent_data) {
911
                        $parents[] = $parent_data;
912
                        $current_id = $parent_data['parent_id'] ?? '0';
913
                    } else {
914
                        break;
915
                    }
916
                }
917
            }
918
            $row['parents'] = array_reverse($parents);
919
920
            return $row;
921
        }
922
923
        return false;
924
    }
925
926
    /**
927
     * Allow to set a specific document as a new template for CKeditor
928
     * for a particular user in a particular course.
929
     *
930
     * @param string $title
931
     * @param string $description
932
     * @param int    $document_id_for_template the document id
933
     * @param int    $courseId
934
     * @param int    $user_id
935
     * @param string $image
936
     *
937
     * @return bool
938
     */
939
    public static function setDocumentAsTemplate(
940
        $title,
941
        $description,
942
        $document_id_for_template,
943
        $courseId,
944
        $user_id,
945
        $image
946
    ) {
947
        // Database table definition
948
        $table_template = Database::get_main_table(TABLE_MAIN_TEMPLATES);
949
        $params = [
950
            'title' => $title,
951
            'description' => $description,
952
            'c_id' => $courseId,
953
            'user_id' => $user_id,
954
            'ref_doc' => $document_id_for_template,
955
            'image' => $image,
956
        ];
957
        Database::insert($table_template, $params);
958
959
        return true;
960
    }
961
962
    /**
963
     * Check document visibility.
964
     *
965
     * @param string $doc_path the relative complete path of the document
966
     * @param array  $course   the _course array info of the document's course
967
     * @param int
968
     * @param string
969
     *
970
     * @return bool
971
     */
972
    public static function is_visible(
973
        $doc_path,
974
        $course,
975
        $session_id = 0,
976
        $file_type = 'file'
977
    ) {
978
        $docTable = Database::get_course_table(TABLE_DOCUMENT);
979
980
        $course_id = $course['real_id'];
981
        // note the extra / at the end of doc_path to match every path in
982
        // the document table that is part of the document path
983
        $session_id = (int) $session_id;
984
        $condition = " AND d.session_id IN  ('$session_id', '0') ";
985
        // The " d.filetype='file' " let the user see a file even if the folder is hidden see #2198
986
987
        /*
988
          When using hotpotatoes files, a new html files are generated
989
          in the hotpotatoes folder to display the test.
990
          The genuine html file is copied to math4.htm(user_id).t.html
991
          Images files are not copied, and keep same name.
992
          To check the html file visibility, we don't have to check file math4.htm(user_id).t.html but file math4.htm
993
          In this case, we have to remove (user_id).t.html to check the visibility of the file
994
          For images, we just check the path of the image file.
995
996
          Exemple of hotpotatoes folder :
997
          A.jpg
998
          maths4-consigne.jpg
999
          maths4.htm
1000
          maths4.htm1.t.html
1001
          maths4.htm52.t.html
1002
          maths4.htm654.t.html
1003
          omega.jpg
1004
          theta.jpg
1005
         */
1006
1007
        if (strpos($doc_path, 'HotPotatoes_files') && preg_match("/\.t\.html$/", $doc_path)) {
1008
            $doc_path = substr($doc_path, 0, strlen($doc_path) - 7 - strlen(api_get_user_id()));
1009
        }
1010
1011
        if (!in_array($file_type, ['file', 'folder'])) {
1012
            $file_type = 'file';
1013
        }
1014
        $doc_path = Database::escape_string($doc_path).'/';
1015
1016
        $sql = "SELECT iid
1017
                FROM $docTable d
1018
        		WHERE
1019
        		    d.c_id  = $course_id AND
1020
        		    $condition AND
1021
        			filetype = '$file_type' AND
1022
        			locate(concat(path,'/'), '$doc_path')=1
1023
                ";
1024
1025
        $result = Database::query($sql);
1026
        $is_visible = false;
1027
        if (Database::num_rows($result) > 0) {
1028
            $row = Database::fetch_assoc($result);
1029
1030
            $em = Database::getManager();
1031
1032
            $repo = $em->getRepository(CDocument::class);
1033
            /** @var \Chamilo\CourseBundle\Entity\CDocument $document */
1034
            $document = $repo->find($row['iid']);
1035
            if (ResourceLink::VISIBILITY_PUBLISHED === $document->getVisibility()) {
1036
                $is_visible = api_is_allowed_in_course() || api_is_platform_admin();
1037
            }
1038
        }
1039
1040
        /* improved protection of documents viewable directly through the url:
1041
            incorporates the same protections of the course at the url of
1042
            documents:
1043
            access allowed for the whole world Open, access allowed for
1044
            users registered on the platform Private access, document accessible
1045
            only to course members (see the Users list), Completely closed;
1046
            the document is only accessible to the course admin and
1047
            teaching assistants.*/
1048
        //return $_SESSION ['is_allowed_in_course'] || api_is_platform_admin();
1049
        return $is_visible;
1050
    }
1051
1052
    /**
1053
     * Allow attach a certificate to a course.
1054
     *
1055
     * @todo move to certificate.lib.php
1056
     *
1057
     * @param int $courseId
1058
     * @param int $document_id
1059
     * @param int $sessionId
1060
     */
1061
    public static function attach_gradebook_certificate($courseId, $document_id, $sessionId = 0)
1062
    {
1063
        $tbl_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1064
        $sessionId = intval($sessionId);
1065
        $courseId = (int) $courseId;
1066
        if (empty($sessionId)) {
1067
            $sessionId = api_get_session_id();
1068
        }
1069
1070
        if (empty($sessionId)) {
1071
            $sql_session = 'AND (session_id = 0 OR isnull(session_id)) ';
1072
        } elseif ($sessionId > 0) {
1073
            $sql_session = 'AND session_id='.$sessionId;
1074
        } else {
1075
            $sql_session = '';
1076
        }
1077
        $sql = 'UPDATE '.$tbl_category.' SET document_id="'.intval($document_id).'"
1078
                WHERE c_id ="'.$courseId.'" '.$sql_session;
1079
        Database::query($sql);
1080
    }
1081
1082
    /**
1083
     * get the document id of default certificate.
1084
     *
1085
     * @todo move to certificate.lib.php
1086
     *
1087
     * @param int $courseId
1088
     * @param int $session_id
1089
     *
1090
     * @return int The default certificate id
1091
     */
1092
    public static function get_default_certificate_id($courseId, $session_id = 0)
1093
    {
1094
        $tbl_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1095
        $session_id = (int) $session_id;
1096
        $courseId = (int) $courseId;
1097
        if (empty($session_id)) {
1098
            $session_id = api_get_session_id();
1099
        }
1100
1101
        if (empty($session_id)) {
1102
            $sql_session = 'AND (session_id = 0 OR isnull(session_id)) ';
1103
        } elseif ($session_id > 0) {
1104
            $sql_session = 'AND session_id='.$session_id;
1105
        } else {
1106
            $sql_session = '';
1107
        }
1108
1109
        $sql = 'SELECT document_id FROM '.$tbl_category.'
1110
                WHERE c_id ="'.$courseId.'" '.$sql_session;
1111
1112
        $rs = Database::query($sql);
1113
        $num = Database::num_rows($rs);
1114
        if (0 == $num) {
1115
            return null;
1116
        }
1117
        $row = Database::fetch_array($rs);
1118
1119
        return $row['document_id'];
1120
    }
1121
1122
    /**
1123
     * Allow replace user info in file html.
1124
     *
1125
     * @param int   $user_id
1126
     * @param array $courseInfo
1127
     * @param int   $sessionId
1128
     * @param bool  $is_preview
1129
     *
1130
     * @return array
1131
     */
1132
    public static function replace_user_info_into_html(
1133
        $user_id,
1134
        $courseInfo,
1135
        $sessionId,
1136
        $is_preview = false
1137
    ) {
1138
        $user_id = (int) $user_id;
1139
        $course_id = $courseInfo['real_id'];
1140
        $document_id = self::get_default_certificate_id($course_id, $sessionId);
1141
1142
        $my_content_html = null;
1143
        if ($document_id) {
1144
            $repo = Container::getDocumentRepository();
1145
            $doc = $repo->find($document_id);
1146
            $new_content = '';
1147
            $all_user_info = [];
1148
            if ($doc) {
1149
                try {
1150
                    // Validate if the document content is not empty
1151
                    $my_content_html = $repo->getResourceFileContent($doc);
1152
                    if (empty($my_content_html)) {
1153
                        throw new Exception("The document content is empty.");
1154
                    }
1155
1156
                    // Retrieve user information for the certificate
1157
                    $all_user_info = self::get_all_info_to_certificate(
1158
                        $user_id,
1159
                        $course_id,
1160
                        $is_preview
1161
                    );
1162
1163
                    // Ensure user info array is properly structured
1164
                    if (!isset($all_user_info[0]) || !isset($all_user_info[1])) {
1165
                        throw new Exception("Error retrieving user information for the certificate.");
1166
                    }
1167
1168
                    $info_to_be_replaced_in_content_html = $all_user_info[0];
1169
                    $info_to_replace_in_content_html = $all_user_info[1];
1170
1171
                    // Replace placeholders in the certificate template with user info
1172
                    $new_content = str_replace(
1173
                        $info_to_be_replaced_in_content_html,
1174
                        $info_to_replace_in_content_html,
1175
                        $my_content_html
1176
                    );
1177
                } catch (Exception $e) {
1178
                    error_log("Error in replace_user_info_into_html: " . $e->getMessage());
1179
                    return [
1180
                        'content' => '',
1181
                        'variables' => [],
1182
                    ];
1183
                }
1184
            }
1185
1186
            return [
1187
                'content' => $new_content,
1188
                'variables' => $all_user_info,
1189
            ];
1190
        }
1191
1192
        return [];
1193
    }
1194
1195
    /**
1196
     * Return all content to replace and all content to be replace.
1197
     *
1198
     * @param int  $user_id
1199
     * @param bool $is_preview
1200
     *
1201
     * @return array
1202
     */
1203
    public static function get_all_info_to_certificate($user_id, $courseId, $sessionId, $is_preview = false)
1204
    {
1205
        $info_list = [];
1206
        $user_id = (int) $user_id;
1207
        $sessionId = (int) $sessionId;
1208
        $course_info = api_get_course_info_by_id($courseId);
1209
        $courseCode = $course_info['code'];
1210
1211
        // Portal info
1212
        $organization_name = api_get_setting('Institution');
1213
        $portal_name = api_get_setting('siteName');
1214
1215
        // Extra user data information
1216
        $extra_user_info_data = UserManager::get_extra_user_data(
1217
            $user_id,
1218
            false,
1219
            false,
1220
            false,
1221
            true
1222
        );
1223
1224
        // get extra fields
1225
        $extraField = new ExtraField('user');
1226
        $extraFields = $extraField->get_all(['filter = ? AND visible_to_self = ?' => [1, 1]]);
1227
1228
        // Student information
1229
        $user_info = api_get_user_info($user_id);
1230
        $first_name = $user_info['firstname'];
1231
        $last_name = $user_info['lastname'];
1232
        $username = $user_info['username'];
1233
        $official_code = $user_info['official_code'];
1234
1235
        // Teacher information
1236
        $info_teacher_id = UserManager::get_user_id_of_course_admin_or_session_admin($course_info);
1237
        $teacher_info = api_get_user_info($info_teacher_id);
1238
        $teacher_first_name = $teacher_info['firstname'];
1239
        $teacher_last_name = $teacher_info['lastname'];
1240
1241
        // info gradebook certificate
1242
        $info_grade_certificate = UserManager::get_info_gradebook_certificate($course_info, $sessionId, $user_id);
1243
        $date_long_certificate = '';
1244
        $date_certificate = '';
1245
        $url = '';
1246
        if ($info_grade_certificate) {
1247
            $date_certificate = $info_grade_certificate['created_at'];
1248
            $url = api_get_path(WEB_PATH).'certificates/index.php?id='.$info_grade_certificate['id'];
1249
        }
1250
        $date_no_time = api_convert_and_format_date(api_get_utc_datetime(), DATE_FORMAT_LONG_NO_DAY);
1251
        if (!empty($date_certificate)) {
1252
            $date_long_certificate = api_convert_and_format_date($date_certificate);
1253
            $date_no_time = api_convert_and_format_date($date_certificate, DATE_FORMAT_LONG_NO_DAY);
1254
        }
1255
1256
        if ($is_preview) {
1257
            $date_long_certificate = api_convert_and_format_date(api_get_utc_datetime());
1258
            $date_no_time = api_convert_and_format_date(api_get_utc_datetime(), DATE_FORMAT_LONG_NO_DAY);
1259
        }
1260
1261
        $externalStyleFile = api_get_path(SYS_CSS_PATH).'themes/'.api_get_visual_theme().'/certificate.css';
1262
        $externalStyle = '';
1263
        if (is_file($externalStyleFile)) {
1264
            $externalStyle = file_get_contents($externalStyleFile);
1265
        }
1266
        $timeInCourse = Tracking::get_time_spent_on_the_course($user_id, $course_info['real_id'], $sessionId);
1267
        $timeInCourse = api_time_to_hms($timeInCourse, ':', false, true);
1268
1269
        $timeInCourseInAllSessions = 0;
1270
        $sessions = SessionManager::get_session_by_course($course_info['real_id']);
1271
1272
        if (!empty($sessions)) {
1273
            foreach ($sessions as $session) {
1274
                $timeInCourseInAllSessions += Tracking::get_time_spent_on_the_course($user_id, $course_info['real_id'], $session['id']);
1275
            }
1276
        }
1277
        $timeInCourseInAllSessions = api_time_to_hms($timeInCourseInAllSessions, ':', false, true);
1278
1279
        $first = Tracking::get_first_connection_date_on_the_course($user_id, $course_info['real_id'], $sessionId, false);
1280
        $first = substr($first, 0, 10);
1281
        $last = Tracking::get_last_connection_date_on_the_course($user_id, $course_info, $sessionId, false);
1282
        $last = substr($last, 0, 10);
1283
1284
        if ($first === $last) {
1285
            $startDateAndEndDate = get_lang('From').' '.$first;
1286
        } else {
1287
            $startDateAndEndDate = sprintf(
1288
                get_lang('From %s to %s'),
1289
                $first,
1290
                $last
1291
            );
1292
        }
1293
        $courseDescription = new CourseDescription();
1294
        $description = $courseDescription->get_data_by_description_type(2, $course_info['real_id'], $sessionId);
1295
        $courseObjectives = '';
1296
        if ($description) {
1297
            $courseObjectives = $description['description_content'];
1298
        }
1299
1300
        // Replace content
1301
        $info_to_replace_in_content_html = [
1302
            $first_name,
1303
            $last_name,
1304
            $username,
1305
            $organization_name,
1306
            $portal_name,
1307
            $teacher_first_name,
1308
            $teacher_last_name,
1309
            $official_code,
1310
            $date_long_certificate,
1311
            $date_no_time,
1312
            $course_info['code'],
1313
            $course_info['name'],
1314
            isset($info_grade_certificate['grade']) ? $info_grade_certificate['grade'] : '',
1315
            $url,
1316
            '<a href="'.$url.'" target="_blank">'.get_lang('Online link to certificate').'</a>',
1317
            '((certificate_barcode))',
1318
            $externalStyle,
1319
            $timeInCourse,
1320
            $timeInCourseInAllSessions,
1321
            $startDateAndEndDate,
1322
            $courseObjectives,
1323
        ];
1324
1325
        $tags = [
1326
            '((user_firstname))',
1327
            '((user_lastname))',
1328
            '((user_username))',
1329
            '((gradebook_institution))',
1330
            '((gradebook_sitename))',
1331
            '((teacher_firstname))',
1332
            '((teacher_lastname))',
1333
            '((official_code))',
1334
            '((date_certificate))',
1335
            '((date_certificate_no_time))',
1336
            '((course_code))',
1337
            '((course_title))',
1338
            '((gradebook_grade))',
1339
            '((certificate_link))',
1340
            '((certificate_link_html))',
1341
            '((certificate_barcode))',
1342
            '((external_style))',
1343
            '((time_in_course))',
1344
            '((time_in_course_in_all_sessions))',
1345
            '((start_date_and_end_date))',
1346
            '((course_objectives))',
1347
        ];
1348
1349
        if (!empty($extraFields)) {
1350
            foreach ($extraFields as $extraField) {
1351
                $valueExtra = isset($extra_user_info_data[$extraField['variable']]) ? $extra_user_info_data[$extraField['variable']] : '';
1352
                $tags[] = '(('.strtolower($extraField['variable']).'))';
1353
                $info_to_replace_in_content_html[] = $valueExtra;
1354
            }
1355
        }
1356
1357
        $info_list[] = $tags;
1358
        $info_list[] = $info_to_replace_in_content_html;
1359
1360
        return $info_list;
1361
    }
1362
1363
    /**
1364
     * Remove default certificate.
1365
     *
1366
     * @param int $course_id              The course code
1367
     * @param int $default_certificate_id The document id of the default certificate
1368
     */
1369
    public static function remove_attach_certificate($course_id, $default_certificate_id)
1370
    {
1371
        if (empty($default_certificate_id)) {
1372
            return false;
1373
        }
1374
1375
        $default_certificate = self::get_default_certificate_id($course_id);
1376
        if ((int) $default_certificate == (int) $default_certificate_id) {
1377
            $tbl_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1378
            $session_id = api_get_session_id();
1379
            if (0 == $session_id || is_null($session_id)) {
1380
                $sql_session = 'AND (session_id='.intval($session_id).' OR isnull(session_id)) ';
1381
            } elseif ($session_id > 0) {
1382
                $sql_session = 'AND session_id='.intval($session_id);
1383
            } else {
1384
                $sql_session = '';
1385
            }
1386
1387
            $sql = 'UPDATE '.$tbl_category.' SET document_id = null
1388
                    WHERE
1389
                        c_id = "'.Database::escape_string($course_id).'" AND
1390
                        document_id="'.$default_certificate_id.'" '.$sql_session;
1391
            Database::query($sql);
1392
        }
1393
    }
1394
1395
    /**
1396
     * Create directory certificate.
1397
     *
1398
     * @param array $courseInfo
1399
     */
1400
    public static function create_directory_certificate_in_course($courseInfo)
1401
    {
1402
        if (!empty($courseInfo)) {
1403
            $dir_name = '/certificates';
1404
            $post_dir_name = get_lang('Certificates');
1405
            $id = self::get_document_id_of_directory_certificate();
1406
            if (empty($id)) {
1407
                create_unexisting_directory(
1408
                    $courseInfo,
1409
                    api_get_user_id(),
1410
                    api_get_session_id(),
1411
                    0,
1412
                    0,
1413
                    '',
1414
                    $dir_name,
1415
                    $post_dir_name,
1416
                    null,
1417
                    false,
1418
                    false
1419
                );
1420
            }
1421
        }
1422
    }
1423
1424
    /**
1425
     * Get the document id of the directory certificate.
1426
     *
1427
     * @return int The document id of the directory certificate
1428
     *
1429
     * @todo move to certificate.lib.php
1430
     */
1431
    public static function get_document_id_of_directory_certificate()
1432
    {
1433
        $tbl_document = Database::get_course_table(TABLE_DOCUMENT);
1434
        $course_id = api_get_course_int_id();
1435
        $sql = "SELECT id FROM $tbl_document
1436
                WHERE c_id = $course_id AND path='/certificates' ";
1437
        $rs = Database::query($sql);
1438
        $row = Database::fetch_array($rs);
1439
1440
        return $row['id'];
1441
    }
1442
1443
    /**
1444
     * Check if a directory given is for certificate.
1445
     *
1446
     * @todo move to certificate.lib.php
1447
     *
1448
     * @param string $dir path of directory
1449
     *
1450
     * @return bool true if is a certificate or false otherwise
1451
     */
1452
    public static function is_certificate_mode($dir)
1453
    {
1454
        // I'm in the certification module?
1455
        $is_certificate_mode = false;
1456
        $is_certificate_array = explode('/', $dir);
1457
        array_shift($is_certificate_array);
1458
        if (isset($is_certificate_array[0]) && 'certificates' == $is_certificate_array[0]) {
1459
            $is_certificate_mode = true;
1460
        }
1461
1462
        return $is_certificate_mode || (isset($_GET['certificate']) && 'true' === $_GET['certificate']);
1463
    }
1464
1465
    /**
1466
     * Gets the list of included resources as a list of absolute or relative paths from a html file or string html
1467
     * This allows for a better SCORM export or replace urls inside content html from copy course
1468
     * The list will generally include pictures, flash objects, java applets, or any other
1469
     * stuff included in the source of the current item. The current item is expected
1470
     * to be an HTML file or string html. If it is not, then the function will return and empty list.
1471
     *
1472
     * @param    string  source html (content or path)
1473
     * @param    bool    is file or string html
1474
     * @param    string    type (one of the app tools) - optional (otherwise takes the current item's type)
1475
     * @param    int        level of recursivity we're in
1476
     *
1477
     * @return array List of file paths. An additional field containing 'local' or 'remote' helps determine
1478
     *               if the file should be copied into the zip or just linked
1479
     */
1480
    public static function get_resources_from_source_html(
1481
        $source_html,
1482
        $is_file = false,
1483
        $type = null,
1484
        $recursivity = 1
1485
    ) {
1486
        $max = 5;
1487
        $attributes = [];
1488
        $wanted_attributes = [
1489
            'src',
1490
            'url',
1491
            '@import',
1492
            'href',
1493
            'value',
1494
            'flashvars',
1495
            'poster',
1496
        ];
1497
        $explode_attributes = ['flashvars' => 'file'];
1498
        $abs_path = '';
1499
1500
        if ($recursivity > $max) {
1501
            return [];
1502
        }
1503
1504
        if (!isset($type)) {
1505
            $type = TOOL_DOCUMENT;
1506
        }
1507
1508
        if (!$is_file) {
1509
            $attributes = self::parse_HTML_attributes(
1510
                $source_html,
1511
                $wanted_attributes,
1512
                $explode_attributes
1513
            );
1514
        } else {
1515
            if (is_file($source_html)) {
1516
                $abs_path = $source_html;
1517
                //for now, read the whole file in one go (that's gonna be a problem when the file is too big)
1518
                $info = pathinfo($abs_path);
1519
                $ext = $info['extension'];
1520
                switch (strtolower($ext)) {
1521
                    case 'html':
1522
                    case 'htm':
1523
                    case 'shtml':
1524
                    case 'css':
1525
                        $file_content = file_get_contents($abs_path);
1526
                        // get an array of attributes from the HTML source
1527
                        $attributes = self::parse_HTML_attributes(
1528
                            $file_content,
1529
                            $wanted_attributes,
1530
                            $explode_attributes
1531
                        );
1532
                        break;
1533
                    default:
1534
                        break;
1535
                }
1536
            } else {
1537
                return [];
1538
            }
1539
        }
1540
1541
        $files_list = [];
1542
        switch ($type) {
1543
            case TOOL_DOCUMENT:
1544
            case TOOL_QUIZ:
1545
            case 'sco':
1546
                foreach ($wanted_attributes as $attr) {
1547
                    if (isset($attributes[$attr])) {
1548
                        //find which kind of path these are (local or remote)
1549
                        $sources = $attributes[$attr];
1550
                        foreach ($sources as $source) {
1551
                            //skip what is obviously not a resource
1552
                            if (strpos($source, '+this.')) {
1553
                                continue; //javascript code - will still work unaltered
1554
                            }
1555
                            if (false === strpos($source, '.')) {
1556
                                continue; //no dot, should not be an external file anyway
1557
                            }
1558
                            if (strpos($source, 'mailto:')) {
1559
                                continue; //mailto link
1560
                            }
1561
                            if (strpos($source, ';') && !strpos($source, '&amp;')) {
1562
                                continue; //avoid code - that should help
1563
                            }
1564
1565
                            if ('value' == $attr) {
1566
                                if (strpos($source, 'mp3file')) {
1567
                                    $files_list[] = [
1568
                                        substr($source, 0, strpos($source, '.swf') + 4),
1569
                                        'local',
1570
                                        'abs',
1571
                                    ];
1572
                                    $mp3file = substr($source, strpos($source, 'mp3file=') + 8);
1573
                                    if ('/' == substr($mp3file, 0, 1)) {
1574
                                        $files_list[] = [$mp3file, 'local', 'abs'];
1575
                                    } else {
1576
                                        $files_list[] = [$mp3file, 'local', 'rel'];
1577
                                    }
1578
                                } elseif (0 === strpos($source, 'flv=')) {
1579
                                    $source = substr($source, 4);
1580
                                    if (strpos($source, '&') > 0) {
1581
                                        $source = substr($source, 0, strpos($source, '&'));
1582
                                    }
1583
                                    if (strpos($source, '://') > 0) {
1584
                                        if (false !== strpos($source, api_get_path(WEB_PATH))) {
1585
                                            //we found the current portal url
1586
                                            $files_list[] = [$source, 'local', 'url'];
1587
                                        } else {
1588
                                            //we didn't find any trace of current portal
1589
                                            $files_list[] = [$source, 'remote', 'url'];
1590
                                        }
1591
                                    } else {
1592
                                        $files_list[] = [$source, 'local', 'abs'];
1593
                                    }
1594
                                    /* skipping anything else to avoid two entries
1595
                                    (while the others can have sub-files in their url, flv's can't)*/
1596
                                    continue;
1597
                                }
1598
                            }
1599
                            if (strpos($source, '://') > 0) {
1600
                                //cut at '?' in a URL with params
1601
                                if (strpos($source, '?') > 0) {
1602
                                    $second_part = substr($source, strpos($source, '?'));
1603
                                    if (strpos($second_part, '://') > 0) {
1604
                                        //if the second part of the url contains a url too, treat the second one before cutting
1605
                                        $pos1 = strpos($second_part, '=');
1606
                                        $pos2 = strpos($second_part, '&');
1607
                                        $second_part = substr($second_part, $pos1 + 1, $pos2 - ($pos1 + 1));
1608
                                        if (false !== strpos($second_part, api_get_path(WEB_PATH))) {
1609
                                            //we found the current portal url
1610
                                            $files_list[] = [$second_part, 'local', 'url'];
1611
                                            $in_files_list[] = self::get_resources_from_source_html(
1612
                                                $second_part,
1613
                                                true,
1614
                                                TOOL_DOCUMENT,
1615
                                                $recursivity + 1
1616
                                            );
1617
                                            if (count($in_files_list) > 0) {
1618
                                                $files_list = array_merge($files_list, $in_files_list);
1619
                                            }
1620
                                        } else {
1621
                                            //we didn't find any trace of current portal
1622
                                            $files_list[] = [$second_part, 'remote', 'url'];
1623
                                        }
1624
                                    } elseif (strpos($second_part, '=') > 0) {
1625
                                        if ('/' === substr($second_part, 0, 1)) {
1626
                                            //link starts with a /, making it absolute (relative to DocumentRoot)
1627
                                            $files_list[] = [$second_part, 'local', 'abs'];
1628
                                            $in_files_list[] = self::get_resources_from_source_html(
1629
                                                $second_part,
1630
                                                true,
1631
                                                TOOL_DOCUMENT,
1632
                                                $recursivity + 1
1633
                                            );
1634
                                            if (count($in_files_list) > 0) {
1635
                                                $files_list = array_merge($files_list, $in_files_list);
1636
                                            }
1637
                                        } elseif (0 === strstr($second_part, '..')) {
1638
                                            //link is relative but going back in the hierarchy
1639
                                            $files_list[] = [$second_part, 'local', 'rel'];
1640
                                            //$dir = api_get_path(SYS_CODE_PATH);//dirname($abs_path);
1641
                                            //$new_abs_path = realpath($dir.'/'.$second_part);
1642
                                            $dir = '';
1643
                                            if (!empty($abs_path)) {
1644
                                                $dir = dirname($abs_path).'/';
1645
                                            }
1646
                                            $new_abs_path = realpath($dir.$second_part);
1647
                                            $in_files_list[] = self::get_resources_from_source_html(
1648
                                                $new_abs_path,
1649
                                                true,
1650
                                                TOOL_DOCUMENT,
1651
                                                $recursivity + 1
1652
                                            );
1653
                                            if (count($in_files_list) > 0) {
1654
                                                $files_list = array_merge($files_list, $in_files_list);
1655
                                            }
1656
                                        } else {
1657
                                            //no starting '/', making it relative to current document's path
1658
                                            if ('./' == substr($second_part, 0, 2)) {
1659
                                                $second_part = substr($second_part, 2);
1660
                                            }
1661
                                            $files_list[] = [$second_part, 'local', 'rel'];
1662
                                            $dir = '';
1663
                                            if (!empty($abs_path)) {
1664
                                                $dir = dirname($abs_path).'/';
1665
                                            }
1666
                                            $new_abs_path = realpath($dir.$second_part);
1667
                                            $in_files_list[] = self::get_resources_from_source_html(
1668
                                                $new_abs_path,
1669
                                                true,
1670
                                                TOOL_DOCUMENT,
1671
                                                $recursivity + 1
1672
                                            );
1673
                                            if (count($in_files_list) > 0) {
1674
                                                $files_list = array_merge($files_list, $in_files_list);
1675
                                            }
1676
                                        }
1677
                                    }
1678
                                    //leave that second part behind now
1679
                                    $source = substr($source, 0, strpos($source, '?'));
1680
                                    if (strpos($source, '://') > 0) {
1681
                                        if (false !== strpos($source, api_get_path(WEB_PATH))) {
1682
                                            //we found the current portal url
1683
                                            $files_list[] = [$source, 'local', 'url'];
1684
                                            $in_files_list[] = self::get_resources_from_source_html(
1685
                                                $source,
1686
                                                true,
1687
                                                TOOL_DOCUMENT,
1688
                                                $recursivity + 1
1689
                                            );
1690
                                            if (count($in_files_list) > 0) {
1691
                                                $files_list = array_merge($files_list, $in_files_list);
1692
                                            }
1693
                                        } else {
1694
                                            //we didn't find any trace of current portal
1695
                                            $files_list[] = [$source, 'remote', 'url'];
1696
                                        }
1697
                                    } else {
1698
                                        //no protocol found, make link local
1699
                                        if ('/' === substr($source, 0, 1)) {
1700
                                            //link starts with a /, making it absolute (relative to DocumentRoot)
1701
                                            $files_list[] = [$source, 'local', 'abs'];
1702
                                            $in_files_list[] = self::get_resources_from_source_html(
1703
                                                $source,
1704
                                                true,
1705
                                                TOOL_DOCUMENT,
1706
                                                $recursivity + 1
1707
                                            );
1708
                                            if (count($in_files_list) > 0) {
1709
                                                $files_list = array_merge($files_list, $in_files_list);
1710
                                            }
1711
                                        } elseif (0 === strstr($source, '..')) {
1712
                                            //link is relative but going back in the hierarchy
1713
                                            $files_list[] = [$source, 'local', 'rel'];
1714
                                            $dir = '';
1715
                                            if (!empty($abs_path)) {
1716
                                                $dir = dirname($abs_path).'/';
1717
                                            }
1718
                                            $new_abs_path = realpath($dir.$source);
1719
                                            $in_files_list[] = self::get_resources_from_source_html(
1720
                                                $new_abs_path,
1721
                                                true,
1722
                                                TOOL_DOCUMENT,
1723
                                                $recursivity + 1
1724
                                            );
1725
                                            if (count($in_files_list) > 0) {
1726
                                                $files_list = array_merge($files_list, $in_files_list);
1727
                                            }
1728
                                        } else {
1729
                                            //no starting '/', making it relative to current document's path
1730
                                            if ('./' == substr($source, 0, 2)) {
1731
                                                $source = substr($source, 2);
1732
                                            }
1733
                                            $files_list[] = [$source, 'local', 'rel'];
1734
                                            $dir = '';
1735
                                            if (!empty($abs_path)) {
1736
                                                $dir = dirname($abs_path).'/';
1737
                                            }
1738
                                            $new_abs_path = realpath($dir.$source);
1739
                                            $in_files_list[] = self::get_resources_from_source_html(
1740
                                                $new_abs_path,
1741
                                                true,
1742
                                                TOOL_DOCUMENT,
1743
                                                $recursivity + 1
1744
                                            );
1745
                                            if (count($in_files_list) > 0) {
1746
                                                $files_list = array_merge($files_list, $in_files_list);
1747
                                            }
1748
                                        }
1749
                                    }
1750
                                }
1751
                                //found some protocol there
1752
                                if (false !== strpos($source, api_get_path(WEB_PATH))) {
1753
                                    //we found the current portal url
1754
                                    $files_list[] = [$source, 'local', 'url'];
1755
                                    $in_files_list[] = self::get_resources_from_source_html(
1756
                                        $source,
1757
                                        true,
1758
                                        TOOL_DOCUMENT,
1759
                                        $recursivity + 1
1760
                                    );
1761
                                    if (count($in_files_list) > 0) {
1762
                                        $files_list = array_merge($files_list, $in_files_list);
1763
                                    }
1764
                                } else {
1765
                                    //we didn't find any trace of current portal
1766
                                    $files_list[] = [$source, 'remote', 'url'];
1767
                                }
1768
                            } else {
1769
                                //no protocol found, make link local
1770
                                if ('/' === substr($source, 0, 1)) {
1771
                                    //link starts with a /, making it absolute (relative to DocumentRoot)
1772
                                    $files_list[] = [$source, 'local', 'abs'];
1773
                                    $in_files_list[] = self::get_resources_from_source_html(
1774
                                        $source,
1775
                                        true,
1776
                                        TOOL_DOCUMENT,
1777
                                        $recursivity + 1
1778
                                    );
1779
                                    if (count($in_files_list) > 0) {
1780
                                        $files_list = array_merge($files_list, $in_files_list);
1781
                                    }
1782
                                } elseif (0 === strpos($source, '..')) {
1783
                                    //link is relative but going back in the hierarchy
1784
                                    $files_list[] = [$source, 'local', 'rel'];
1785
                                    $dir = '';
1786
                                    if (!empty($abs_path)) {
1787
                                        $dir = dirname($abs_path).'/';
1788
                                    }
1789
                                    $new_abs_path = realpath($dir.$source);
1790
                                    $in_files_list[] = self::get_resources_from_source_html(
1791
                                        $new_abs_path,
1792
                                        true,
1793
                                        TOOL_DOCUMENT,
1794
                                        $recursivity + 1
1795
                                    );
1796
                                    if (count($in_files_list) > 0) {
1797
                                        $files_list = array_merge($files_list, $in_files_list);
1798
                                    }
1799
                                } else {
1800
                                    //no starting '/', making it relative to current document's path
1801
                                    if ('./' == substr($source, 0, 2)) {
1802
                                        $source = substr($source, 2);
1803
                                    }
1804
                                    $files_list[] = [$source, 'local', 'rel'];
1805
                                    $dir = '';
1806
                                    if (!empty($abs_path)) {
1807
                                        $dir = dirname($abs_path).'/';
1808
                                    }
1809
                                    $new_abs_path = realpath($dir.$source);
1810
                                    $in_files_list[] = self::get_resources_from_source_html(
1811
                                        $new_abs_path,
1812
                                        true,
1813
                                        TOOL_DOCUMENT,
1814
                                        $recursivity + 1
1815
                                    );
1816
                                    if (count($in_files_list) > 0) {
1817
                                        $files_list = array_merge($files_list, $in_files_list);
1818
                                    }
1819
                                }
1820
                            }
1821
                        }
1822
                    }
1823
                }
1824
                break;
1825
            default: //ignore
1826
                break;
1827
        }
1828
1829
        $checked_files_list = [];
1830
        $checked_array_list = [];
1831
1832
        if (count($files_list) > 0) {
1833
            foreach ($files_list as $idx => $file) {
1834
                if (!empty($file[0])) {
1835
                    if (!in_array($file[0], $checked_files_list)) {
1836
                        $checked_files_list[] = $files_list[$idx][0];
1837
                        $checked_array_list[] = $files_list[$idx];
1838
                    }
1839
                }
1840
            }
1841
        }
1842
1843
        return $checked_array_list;
1844
    }
1845
1846
    /**
1847
     * Parses the HTML attributes given as string.
1848
     *
1849
     * @param string HTML attribute string
1850
     * @param array List of attributes that we want to get back
1851
     * @param array
1852
     *
1853
     * @return array An associative array of attributes
1854
     *
1855
     * @author Based on a function from the HTML_Common2 PEAR module     *
1856
     */
1857
    public static function parse_HTML_attributes($attrString, $wanted = [], $explode_variables = [])
1858
    {
1859
        $attributes = [];
1860
        $regs = [];
1861
        $reduced = false;
1862
        if (count($wanted) > 0) {
1863
            $reduced = true;
1864
        }
1865
        try {
1866
            //Find all occurences of something that looks like a URL
1867
            // The structure of this regexp is:
1868
            // (find protocol) then
1869
            // (optionally find some kind of space 1 or more times) then
1870
            // find (either an equal sign or a bracket) followed by an optional space
1871
            // followed by some text without quotes (between quotes itself or not)
1872
            // then possible closing brackets if we were in the opening bracket case
1873
            // OR something like @import()
1874
            $res = preg_match_all(
1875
                '/(((([A-Za-z_:])([A-Za-z0-9_:\.-]*))'.
1876
                // '/(((([A-Za-z_:])([A-Za-z0-9_:\.-]|[^\x00-\x7F])*)' . -> seems to be taking too much
1877
                // '/(((([A-Za-z_:])([^\x00-\x7F])*)' . -> takes only last letter of parameter name
1878
                '([ \n\t\r]+)?('.
1879
                // '(=([ \n\t\r]+)?("[^"]+"|\'[^\']+\'|[^ \n\t\r]+))' . -> doesn't restrict close enough to the url itself
1880
                '(=([ \n\t\r]+)?("[^"\)]+"|\'[^\'\)]+\'|[^ \n\t\r\)]+))'.
1881
                '|'.
1882
                // '(\(([ \n\t\r]+)?("[^"]+"|\'[^\']+\'|[^ \n\t\r]+)\))' . -> doesn't restrict close enough to the url itself
1883
                '(\(([ \n\t\r]+)?("[^"\)]+"|\'[^\'\)]+\'|[^ \n\t\r\)]+)\))'.
1884
                '))'.
1885
                '|'.
1886
                // '(@import([ \n\t\r]+)?("[^"]+"|\'[^\']+\'|[^ \n\t\r]+)))?/', -> takes a lot (like 100's of thousands of empty possibilities)
1887
                '(@import([ \n\t\r]+)?("[^"]+"|\'[^\']+\'|[^ \n\t\r]+)))/',
1888
                $attrString,
1889
                $regs
1890
            );
1891
        } catch (Exception $e) {
1892
            error_log('Caught exception: '.$e->getMessage(), 0);
1893
        }
1894
        if ($res) {
1895
            for ($i = 0; $i < count($regs[1]); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1896
                $name = trim($regs[3][$i]);
1897
                $check = trim($regs[0][$i]);
1898
                $value = trim($regs[10][$i]);
1899
                if (empty($value) and !empty($regs[13][$i])) {
1900
                    $value = $regs[13][$i];
1901
                }
1902
                if (empty($name) && !empty($regs[16][$i])) {
1903
                    $name = '@import';
1904
                    $value = trim($regs[16][$i]);
1905
                }
1906
                if (!empty($name)) {
1907
                    if (!$reduced || in_array(strtolower($name), $wanted)) {
1908
                        if ($name == $check) {
1909
                            $attributes[strtolower($name)][] = strtolower($name);
1910
                        } else {
1911
                            if (!empty($value) && ('\'' == $value[0] || '"' == $value[0])) {
1912
                                $value = substr($value, 1, -1);
1913
                            }
1914
1915
                            if ('API.LMSGetValue(name' == $value) {
1916
                                $value = 'API.LMSGetValue(name)';
1917
                            }
1918
                            //Gets the xx.flv value from the string flashvars="width=320&height=240&autostart=false&file=xxx.flv&repeat=false"
1919
                            if (isset($explode_variables[$name])) {
1920
                                $value_modified = str_replace('&amp;', '&', $value);
1921
                                $value_array = explode('&', $value_modified);
1922
                                foreach ($value_array as $item) {
1923
                                    $itemParts = explode('=', $item);
1924
                                    $key = $itemParts[0];
1925
                                    $item_value = !empty($itemParts[1]) ? $itemParts[1] : '';
1926
                                    if ($key == $explode_variables[$name]) {
1927
                                        $attributes[strtolower($name)][] = $item_value;
1928
                                    }
1929
                                }
1930
                            }
1931
                            $attributes[strtolower($name)][] = $value;
1932
                        }
1933
                    }
1934
                }
1935
            }
1936
        }
1937
1938
        return $attributes;
1939
    }
1940
1941
    /**
1942
     * Replace urls inside content html from a copy course.
1943
     *
1944
     * @param string $content_html
1945
     * @param string $origin_course_code
1946
     * @param string $destination_course_directory
1947
     * @param string $origin_course_path_from_zip
1948
     * @param string $origin_course_info_path
1949
     *
1950
     * @return string new content html with replaced urls or return false if content is not a string
1951
     */
1952
    public static function replaceUrlWithNewCourseCode(
1953
        $content_html,
1954
        $origin_course_code,
1955
        $destination_course_directory,
1956
        $origin_course_path_from_zip = null,
1957
        $origin_course_info_path = null
1958
    ) {
1959
        if (empty($content_html)) {
1960
            return false;
1961
        }
1962
1963
        $orig_source_html = self::get_resources_from_source_html($content_html);
1964
        $orig_course_info = api_get_course_info($origin_course_code);
1965
1966
        $destination_course_code = CourseManager::getCourseCodeFromDirectory($destination_course_directory);
1967
        $destination_course_info = api_get_course_info($destination_course_code);
1968
1969
        if (!empty($orig_source_html)) {
1970
            foreach ($orig_source_html as $source) {
1971
1972
                $real_orig_url = $source[0];
1973
                $scope_url = $source[1];
1974
                $type_url = $source[2];
1975
1976
                if ('local' === $scope_url) {
1977
                    $document_file = strstr($real_orig_url, 'document');
1978
1979
                    if (false !== $document_file) {
1980
                        $new_url = self::generateNewUrlForCourseResource($destination_course_info, $document_file);
1981
                        $content_html = str_replace($real_orig_url, $new_url, $content_html);
1982
                    }
1983
                }
1984
            }
1985
        }
1986
1987
        return $content_html;
1988
    }
1989
1990
    /**
1991
     * Generates a new URL for a resource within the context of the target course.
1992
     *
1993
     * This function constructs a URL to access a given resource, such as a document
1994
     * or image, which has been copied into the target course. It's essential for
1995
     * updating resource links in course content to point to the correct location
1996
     * after resources have been duplicated or moved between courses.
1997
     */
1998
    public static function generateNewUrlForCourseResource(array $destination_course_info, string $document_file): string
1999
    {
2000
        $courseCode = $destination_course_info['code'];
2001
        $courseWebPath = api_get_path(WEB_COURSE_PATH) . $courseCode . "/document/";
2002
2003
        $document_file = ltrim($document_file, '/');
2004
2005
        $newUrl = $courseWebPath . $document_file;
2006
2007
        return $newUrl;
2008
    }
2009
2010
2011
    /**
2012
     * Obtains the text inside the file with the right parser.
2013
     */
2014
    public static function get_text_content($doc_path, $doc_mime)
2015
    {
2016
        // TODO: review w$ compatibility
2017
        // Use usual exec output lines array to store stdout instead of a temp file
2018
        // because we need to store it at RAM anyway before index on ChamiloIndexer object
2019
        $ret_val = null;
2020
        switch ($doc_mime) {
2021
            case 'text/plain':
2022
                $handle = fopen($doc_path, 'r');
2023
                $output = [fread($handle, filesize($doc_path))];
2024
                fclose($handle);
2025
                break;
2026
            case 'application/pdf':
2027
                exec("pdftotext $doc_path -", $output, $ret_val);
2028
                break;
2029
            case 'application/postscript':
2030
                $temp_file = tempnam(sys_get_temp_dir(), 'chamilo');
2031
                exec("ps2pdf $doc_path $temp_file", $output, $ret_val);
2032
                if (0 !== $ret_val) { // shell fail, probably 127 (command not found)
2033
                    return false;
2034
                }
2035
                exec("pdftotext $temp_file -", $output, $ret_val);
2036
                unlink($temp_file);
2037
                break;
2038
            case 'application/msword':
2039
                exec("catdoc $doc_path", $output, $ret_val);
2040
                break;
2041
            case 'text/html':
2042
                exec("html2text $doc_path", $output, $ret_val);
2043
                break;
2044
            case 'text/rtf':
2045
                // Note: correct handling of code pages in unrtf
2046
                // on debian lenny unrtf v0.19.2 can not, but unrtf v0.20.5 can
2047
                exec("unrtf --text $doc_path", $output, $ret_val);
2048
                if (127 == $ret_val) { // command not found
2049
                    return false;
2050
                }
2051
                // Avoid index unrtf comments
2052
                if (is_array($output) && count($output) > 1) {
2053
                    $parsed_output = [];
2054
                    foreach ($output as &$line) {
2055
                        if (!preg_match('/^###/', $line, $matches)) {
2056
                            if (!empty($line)) {
2057
                                $parsed_output[] = $line;
2058
                            }
2059
                        }
2060
                    }
2061
                    $output = $parsed_output;
2062
                }
2063
                break;
2064
            case 'application/vnd.ms-powerpoint':
2065
                exec("catppt $doc_path", $output, $ret_val);
2066
                break;
2067
            case 'application/vnd.ms-excel':
2068
                exec("xls2csv -c\" \" $doc_path", $output, $ret_val);
2069
                break;
2070
        }
2071
2072
        $content = '';
2073
        if (!is_null($ret_val)) {
2074
            if (0 !== $ret_val) { // shell fail, probably 127 (command not found)
2075
                return false;
2076
            }
2077
        }
2078
        if (isset($output)) {
2079
            foreach ($output as &$line) {
2080
                $content .= $line."\n";
2081
            }
2082
2083
            return $content;
2084
        } else {
2085
            return false;
2086
        }
2087
    }
2088
2089
    /**
2090
     * Display the document quota in a simple way.
2091
     *
2092
     *  Here we count 1 Kilobyte = 1024 Bytes, 1 Megabyte = 1048576 Bytes
2093
     */
2094
    public static function displaySimpleQuota($course_quota, $already_consumed_space)
2095
    {
2096
        $course_quota_m = round($course_quota / 1048576);
2097
        $already_consumed_space_m = round($already_consumed_space / 1048576, 2);
2098
        $percentage = $already_consumed_space / $course_quota * 100;
2099
        $percentage = round($percentage, 1);
2100
        $message = get_lang('You are currently using %s MB (%s) of your %s MB.');
2101
        $message = sprintf($message, $already_consumed_space_m, $percentage.'%', $course_quota_m.' ');
2102
2103
        return Display::div($message, ['id' => 'document_quota', 'class' => 'card-quota']);
2104
    }
2105
2106
    /**
2107
     * Checks if there is enough place to add a file on a directory
2108
     * on the base of a maximum directory size allowed.
2109
     *
2110
     * @author Bert Vanderkimpen
2111
     *
2112
     * @param int $file_size     size of the file in byte
2113
     * @param int $max_dir_space maximum size
2114
     *
2115
     * @return bool true if there is enough space, false otherwise
2116
     */
2117
    public static function enough_space($file_size, $max_dir_space)
2118
    {
2119
        if ($max_dir_space) {
2120
            $max_dir_space = $max_dir_space * 1024 * 1024;
2121
            $courseEntity = api_get_course_entity();
2122
            $repo = Container::getDocumentRepository();
2123
            $total = $repo->getFolderSize($courseEntity->getResourceNode(), $courseEntity);
2124
2125
            if (($file_size + $total) > $max_dir_space) {
2126
                return false;
2127
            }
2128
        }
2129
2130
        return true;
2131
    }
2132
2133
    /**
2134
     * @param array $params count, url, extension
2135
     *
2136
     * @return string
2137
     */
2138
    public static function generateAudioJavascript($params = [])
2139
    {
2140
        $js = '
2141
            $(\'audio.audio_preview\').mediaelementplayer({
2142
                features: [\'playpause\'],
2143
                audioWidth: 30,
2144
                audioHeight: 30,
2145
                success: function(mediaElement, originalNode, instance) {
2146
                }
2147
            });';
2148
2149
        return $js;
2150
    }
2151
2152
    /**
2153
     * Shows a play icon next to the document title in the document list.
2154
     *
2155
     * @param string $documentWebPath
2156
     * @param array  $documentInfo
2157
     *
2158
     * @return string
2159
     */
2160
    public static function generateAudioPreview($documentWebPath, $documentInfo)
2161
    {
2162
        $filePath = $documentWebPath.$documentInfo['path'];
2163
        $extension = $documentInfo['file_extension'];
2164
2165
        return '<span class="preview">
2166
                    <audio class="audio_preview skip" src="'.$filePath.'" type="audio/'.$extension.'"></audio>
2167
                </span>';
2168
    }
2169
2170
    /**
2171
     * @param string $file
2172
     * @param string $extension
2173
     *
2174
     * @return string
2175
     */
2176
    public static function generateMediaPreview($file, $extension)
2177
    {
2178
        $id = api_get_unique_id();
2179
        switch ($extension) {
2180
            case 'wav':
2181
            case 'ogg':
2182
            case 'mp3':
2183
                $html = '<div style="margin: 0; position: absolute; top: 50%; left: 35%;">';
2184
                $html .= '<audio id="'.$id.'" controls="controls" src="'.$file.'" type="audio/mp3" ></audio></div>';
2185
                break;
2186
            default:
2187
                $html = '<video id="'.$id.'" controls>';
2188
                $html .= '<source src="'.$file.'" >';
2189
                $html .= '</video>';
2190
                break;
2191
        }
2192
2193
        return $html;
2194
    }
2195
2196
    /**
2197
     * @param bool   $lp_id
2198
     * @param string $target
2199
     * @param int    $session_id
2200
     * @param bool   $add_move_button
2201
     * @param string $filter_by_folder
2202
     * @param string $overwrite_url
2203
     * @param bool   $showInvisibleFiles
2204
     * @param bool   $showOnlyFolders
2205
     * @param int    $folderId
2206
     * @param bool   $addCloseButton
2207
     * @param bool   $addAudioPreview
2208
     * @param array  $filterByExtension
2209
     *
2210
     * @return string
2211
     */
2212
    public static function get_document_preview(
2213
        Course $course,
2214
        $lp_id = false,
2215
        $target = '',
2216
        $session_id = 0,
2217
        $add_move_button = false,
2218
        $filter_by_folder = null,
2219
        $overwrite_url = '',
2220
        $showInvisibleFiles = false,
2221
        $showOnlyFolders = false,
2222
        $folderId = false,
2223
        $addCloseButton = true,
2224
        $addAudioPreview = false,
2225
        $filterByExtension = []
2226
    ) {
2227
        $repo = Container::getDocumentRepository();
2228
        $nodeRepository = $repo->getResourceNodeRepository();
2229
        $move = get_lang('Move');
2230
        $icon = '<i class="mdi-cursor-move mdi ch-tool-icon" style="font-size: 16px; width: 16px; height: 16px;" aria-hidden="true" title="'.htmlentities(get_lang('Move')).'"></i>';
2231
        $folderIcon = Display::getMdiIcon(ObjectIcon::CHAPTER, 'ch-tool-icon', null, ICON_SIZE_SMALL);
2232
2233
        $options = [
2234
            'decorate' => true,
2235
            'rootOpen' => '<ul id="doc_list" class="list-group lp_resource">',
2236
            'rootClose' => '</ul>',
2237
            //'childOpen' => '<li class="doc_resource lp_resource_element ">',
2238
            'childOpen' => function ($child) {;
2239
                $id = $child['id'];
2240
                $disableDrag = '';
2241
                if (!$child['resourceFiles']) {
2242
                    $disableDrag = ' disable_drag ';
2243
                }
2244
2245
                return '<li
2246
                    id="'.$id.'"
2247
                    data-id="'.$id.'"
2248
                    class=" '.$disableDrag.' list-group-item nested-'.$child['level'].'"
2249
                >';
2250
            },
2251
            'childClose' => '</li>',
2252
            'nodeDecorator' => function ($node) use ($icon, $folderIcon) {
2253
                $disableDrag = '';
2254
                if (!$node['resourceFiles']) {
2255
                    $disableDrag = ' disable_drag ';
2256
                }
2257
2258
                $link = '<div class="flex flex-row gap-1 h-4 item_data '.$disableDrag.' ">';
2259
                $file = $node['resourceFiles'] ? current($node['resourceFiles']) : null;
2260
                $extension = '';
2261
                if ($file) {
2262
                    $extension = pathinfo($file['title'], PATHINFO_EXTENSION);
2263
                }
2264
2265
                $folder = $folderIcon;
2266
2267
                if ($node['resourceFiles']) {
2268
                    $link .= '<a class="moved ui-sortable-handle" href="#">';
2269
                    $link .= $icon;
2270
                    $link .= '</a>';
2271
                    $folder = '';
2272
                }
2273
2274
                $link .= '<a
2275
                    data_id="'.$node['id'].'"
2276
                    data_type="document"
2277
                    class="moved ui-sortable-handle link_with_id"
2278
                >';
2279
                $link .= $folder.'&nbsp;';
2280
                $link .= '</a>';
2281
                $link .= cut(addslashes($node['title']), 30);
2282
                $link .= '</div>';
2283
2284
                return $link;
2285
            },
2286
        ];
2287
2288
        $type = $repo->getResourceType();
2289
        $em = Database::getManager();
2290
        $qb = $em
2291
            ->createQueryBuilder()
2292
            ->select('node')
2293
            ->from(ResourceNode::class, 'node')
2294
            ->innerJoin('node.resourceType', 'type')
2295
            ->innerJoin('node.resourceLinks', 'links')
2296
            ->innerJoin('node.resourceFiles', 'files')
2297
            ->addSelect('files')
2298
            ->where('type = :type')
2299
            ->andWhere('links.course = :course')
2300
            ->setParameters(['type' => $type, 'course' => $course])
2301
            ->orderBy('node.parent', 'ASC')
2302
        ;
2303
2304
        $sessionId = api_get_session_id();
2305
        if (empty($sessionId)) {
2306
            $qb->andWhere('links.session IS NULL');
2307
        } else {
2308
            $qb
2309
                ->andWhere('links.session = :session')
2310
                ->setParameter('session', $sessionId)
2311
            ;
2312
        }
2313
2314
        if (!empty($filterByExtension)) {
2315
            $orX = $qb->expr()->orX();
2316
            foreach ($filterByExtension as $extension) {
2317
                $orX->add($qb->expr()->like('file.originalName', ':'.$extension));
2318
                $qb->setParameter($extension, '%'.$extension);
2319
            }
2320
            $qb->andWhere($orX);
2321
        }
2322
        $query = $qb->getQuery();
2323
2324
        return $nodeRepository->buildTree($query->getArrayResult(), $options);
2325
    }
2326
2327
    /**
2328
     * Index a given document.
2329
     *
2330
     * @param   int     Document ID inside its corresponding course
2331
     * @param   string  Course code
2332
     * @param   int     Session ID (not used yet)
2333
     * @param   string  Language of document's content (defaults to course language)
2334
     * @param   array   Array of specific fields (['code'=>'value',...])
2335
     * @param   string  What to do if the file already exists (default or overwrite)
2336
     * @param   bool    When set to true, this runs the indexer without actually saving anything to any database
2337
     *
2338
     * @return bool Returns true on presumed success, false on failure
2339
     */
2340
    public static function index_document(
2341
        $docid,
2342
        $course_code,
2343
        $session_id = 0,
2344
        $lang = 'english',
2345
        $specific_fields_values = [],
2346
        $if_exists = '',
2347
        $simulation = false
2348
    ) {
2349
        if ('true' !== api_get_setting('search_enabled')) {
2350
            return false;
2351
        }
2352
        if (empty($docid) or $docid != intval($docid)) {
2353
            return false;
2354
        }
2355
        if (empty($session_id)) {
2356
            $session_id = api_get_session_id();
2357
        }
2358
        $course_info = api_get_course_info($course_code);
2359
        $course_dir = $course_info['path'].'/document';
2360
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
2361
        $base_work_dir = $sys_course_path.$course_dir;
2362
2363
        $course_id = $course_info['real_id'];
2364
        $table_document = Database::get_course_table(TABLE_DOCUMENT);
2365
2366
        $qry = "SELECT path, title FROM $table_document WHERE c_id = $course_id AND id = '$docid' LIMIT 1";
2367
        $result = Database::query($qry);
2368
        if (1 == Database::num_rows($result)) {
2369
            $row = Database::fetch_array($result);
2370
            $doc_path = api_get_path(SYS_COURSE_PATH).$course_dir.$row['path'];
2371
            //TODO: mime_content_type is deprecated, fileinfo php extension is enabled by default as of PHP 5.3.0
2372
            // now versions of PHP on Debian testing(5.2.6-5) and Ubuntu(5.2.6-2ubuntu) are lower, so wait for a while
2373
            $doc_mime = mime_content_type($doc_path);
2374
            $allowed_mime_types = self::file_get_mime_type(true);
2375
2376
            // mime_content_type does not detect correctly some formats that
2377
            // are going to be supported for index, so an extensions array is used for the moment
2378
            if (empty($doc_mime)) {
2379
                $allowed_extensions = [
2380
                    'doc',
2381
                    'docx',
2382
                    'ppt',
2383
                    'pptx',
2384
                    'pps',
2385
                    'ppsx',
2386
                    'xls',
2387
                    'xlsx',
2388
                    'odt',
2389
                    'odp',
2390
                    'ods',
2391
                    'pdf',
2392
                    'txt',
2393
                    'rtf',
2394
                    'msg',
2395
                    'csv',
2396
                    'html',
2397
                    'htm',
2398
                ];
2399
                $extensions = preg_split("/[\/\\.]/", $doc_path);
2400
                $doc_ext = strtolower($extensions[count($extensions) - 1]);
2401
                if (in_array($doc_ext, $allowed_extensions)) {
2402
                    switch ($doc_ext) {
2403
                        case 'ppt':
2404
                        case 'pps':
2405
                            $doc_mime = 'application/vnd.ms-powerpoint';
2406
                            break;
2407
                        case 'xls':
2408
                            $doc_mime = 'application/vnd.ms-excel';
2409
                            break;
2410
                    }
2411
                }
2412
            }
2413
2414
            //@todo move this nightmare in a search controller or something like that!!! J.M
2415
2416
            if (in_array($doc_mime, $allowed_mime_types)) {
2417
                $file_title = $row['title'];
2418
                $file_content = self::get_text_content($doc_path, $doc_mime);
2419
                $course_code = Database::escape_string($course_code);
2420
                $ic_slide = new IndexableChunk();
2421
                $ic_slide->addValue('title', $file_title);
2422
                $ic_slide->addCourseId($course_code);
2423
                $ic_slide->addToolId(TOOL_DOCUMENT);
2424
                $xapian_data = [
2425
                    SE_COURSE_ID => $course_code,
2426
                    SE_TOOL_ID => TOOL_DOCUMENT,
2427
                    SE_DATA => ['doc_id' => $docid],
2428
                    SE_USER => api_get_user_id(),
2429
                ];
2430
2431
                $ic_slide->xapian_data = serialize($xapian_data);
2432
                $di = new ChamiloIndexer();
2433
                $return = $di->connectDb(null, null, $lang);
2434
2435
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2436
                $specific_fields = get_specific_field_list();
2437
2438
                // process different depending on what to do if file exists
2439
                /**
2440
                 * @TODO Find a way to really verify if the file had been
2441
                 * overwriten. Now all work is done at
2442
                 * handle_uploaded_document() and it's difficult to verify it
2443
                 */
2444
                if (!empty($if_exists) && 'overwrite' == $if_exists) {
2445
                    // Overwrite the file on search engine
2446
                    // Actually, it consists on a delete of terms from db,
2447
                    // insert new ones, create a new search engine document,
2448
                    // and remove the old one
2449
                    // Get search_did
2450
                    $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2451
                    $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
2452
                    $sql = sprintf($sql, $tbl_se_ref, $course_code, TOOL_DOCUMENT, $docid);
2453
2454
                    $res = Database::query($sql);
2455
2456
                    if (Database::num_rows($res) > 0) {
2457
                        $se_ref = Database::fetch_array($res);
2458
                        if (!$simulation) {
2459
                            $di->remove_document($se_ref['search_did']);
2460
                        }
2461
                        $all_specific_terms = '';
2462
                        foreach ($specific_fields as $specific_field) {
2463
                            if (!$simulation) {
2464
                                delete_all_specific_field_value($course_code, $specific_field['id'], TOOL_DOCUMENT, $docid);
2465
                            }
2466
                            // Update search engine
2467
                            if (isset($specific_fields_values[$specific_field['code']])) {
2468
                                $sterms = trim($specific_fields_values[$specific_field['code']]);
2469
                            } else { //if the specific field is not defined, force an empty one
2470
                                $sterms = '';
2471
                            }
2472
                            $all_specific_terms .= ' '.$sterms;
2473
                            $sterms = explode(',', $sterms);
2474
                            foreach ($sterms as $sterm) {
2475
                                $sterm = trim($sterm);
2476
                                if (!empty($sterm)) {
2477
                                    $ic_slide->addTerm($sterm, $specific_field['code']);
2478
                                    // updated the last param here from $value to $sterm without being sure - see commit15464
2479
                                    if (!$simulation) {
2480
                                        add_specific_field_value(
2481
                                            $specific_field['id'],
2482
                                            $course_code,
2483
                                            TOOL_DOCUMENT,
2484
                                            $docid,
2485
                                            $sterm
2486
                                        );
2487
                                    }
2488
                                }
2489
                            }
2490
                        }
2491
                        // Add terms also to content to make terms findable by probabilistic search
2492
                        $file_content = $all_specific_terms.' '.$file_content;
2493
2494
                        if (!$simulation) {
2495
                            $ic_slide->addValue('content', $file_content);
2496
                            $di->addChunk($ic_slide);
2497
                            // Index and return a new search engine document id
2498
                            $did = $di->index();
2499
2500
                            if ($did) {
2501
                                // update the search_did on db
2502
                                $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2503
                                $sql = 'UPDATE %s SET search_did=%d WHERE id=%d LIMIT 1';
2504
                                $sql = sprintf($sql, $tbl_se_ref, (int) $did, (int) $se_ref['id']);
2505
                                Database::query($sql);
2506
                            }
2507
                        }
2508
                    }
2509
                } else {
2510
                    // Add all terms
2511
                    $all_specific_terms = '';
2512
                    foreach ($specific_fields as $specific_field) {
2513
                        if (isset($specific_fields_values[$specific_field['code']])) {
2514
                            $sterms = trim($specific_fields_values[$specific_field['code']]);
2515
                        } else { //if the specific field is not defined, force an empty one
2516
                            $sterms = '';
2517
                        }
2518
                        $all_specific_terms .= ' '.$sterms;
2519
                        if (!empty($sterms)) {
2520
                            $sterms = explode(',', $sterms);
2521
                            foreach ($sterms as $sterm) {
2522
                                if (!$simulation) {
2523
                                    $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2524
                                    add_specific_field_value(
2525
                                        $specific_field['id'],
2526
                                        $course_code,
2527
                                        TOOL_DOCUMENT,
2528
                                        $docid,
2529
                                        $sterm
2530
                                    );
2531
                                }
2532
                            }
2533
                        }
2534
                    }
2535
                    // Add terms also to content to make terms findable by probabilistic search
2536
                    $file_content = $all_specific_terms.' '.$file_content;
2537
                    if (!$simulation) {
2538
                        $ic_slide->addValue('content', $file_content);
2539
                        $di->addChunk($ic_slide);
2540
                        // Index and return search engine document id
2541
                        $did = $di->index();
2542
                        if ($did) {
2543
                            // Save it to db
2544
                            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2545
                            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2546
                            VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2547
                            $sql = sprintf($sql, $tbl_se_ref, $course_code, TOOL_DOCUMENT, $docid, $did);
2548
                            Database::query($sql);
2549
                        } else {
2550
                            return false;
2551
                        }
2552
                    }
2553
                }
2554
            } else {
2555
                return false;
2556
            }
2557
        }
2558
2559
        return true;
2560
    }
2561
2562
    /**
2563
     * @param string $path
2564
     * @param bool   $is_certificate_mode
2565
     *
2566
     * @return bool
2567
     */
2568
    public static function is_folder_to_avoid($path, $is_certificate_mode = false)
2569
    {
2570
        $foldersToAvoid = [
2571
            '/HotPotatoes_files',
2572
            '/certificates',
2573
        ];
2574
        $systemFolder = api_get_course_setting('show_system_folders');
2575
2576
        if (1 == $systemFolder) {
2577
            $foldersToAvoid = [];
2578
        }
2579
2580
        if ('css' == basename($path)) {
2581
            return true;
2582
        }
2583
2584
        if (false == $is_certificate_mode) {
2585
            //Certificate results
2586
            if (strstr($path, 'certificates')) {
2587
                return true;
2588
            }
2589
        }
2590
2591
        // Admin setting for Hide/Show the folders of all users
2592
        if ('false' == api_get_setting('show_users_folders')) {
2593
            $foldersToAvoid[] = '/shared_folder';
2594
2595
            if (strstr($path, 'shared_folder_session_')) {
2596
                return true;
2597
            }
2598
        }
2599
2600
        // Admin setting for Hide/Show Default folders to all users
2601
        if ('false' == api_get_setting('show_default_folders')) {
2602
            $foldersToAvoid[] = '/images';
2603
            $foldersToAvoid[] = '/flash';
2604
            $foldersToAvoid[] = '/audio';
2605
            $foldersToAvoid[] = '/video';
2606
        }
2607
2608
        // Admin setting for Hide/Show chat history folder
2609
        if ('false' == api_get_setting('show_chat_folder')) {
2610
            $foldersToAvoid[] = '/chat_files';
2611
        }
2612
2613
        if (is_array($foldersToAvoid)) {
2614
            return in_array($path, $foldersToAvoid);
2615
        } else {
2616
            return false;
2617
        }
2618
    }
2619
2620
    /**
2621
     * @return array
2622
     */
2623
    public static function get_system_folders()
2624
    {
2625
        return [
2626
            '/certificates',
2627
            '/HotPotatoes_files',
2628
            '/chat_files',
2629
            '/images',
2630
            '/flash',
2631
            '/audio',
2632
            '/video',
2633
            '/shared_folder',
2634
            '/learning_path',
2635
        ];
2636
    }
2637
2638
    /**
2639
     * @return array
2640
     */
2641
    public static function getProtectedFolderFromStudent()
2642
    {
2643
        return [
2644
            '/certificates',
2645
            '/HotPotatoes_files',
2646
            '/chat_files',
2647
            '/shared_folder',
2648
            '/learning_path',
2649
        ];
2650
    }
2651
2652
    /**
2653
     * @param string $courseCode
2654
     *
2655
     * @return string 'visible' or 'invisible' string
2656
     */
2657
    public static function getDocumentDefaultVisibility($courseCode)
2658
    {
2659
        $settings = api_get_setting('tool_visible_by_default_at_creation');
2660
        $defaultVisibility = 'visible';
2661
2662
        if (isset($settings['documents'])) {
2663
            $portalDefaultVisibility = 'invisible';
2664
            if ('true' == $settings['documents']) {
2665
                $portalDefaultVisibility = 'visible';
2666
            }
2667
2668
            $defaultVisibility = $portalDefaultVisibility;
2669
        }
2670
2671
        if ('true' === api_get_setting('documents_default_visibility_defined_in_course')) {
2672
            $courseVisibility = api_get_course_setting('documents_default_visibility', $courseCode);
2673
            if (!empty($courseVisibility) && in_array($courseVisibility, ['visible', 'invisible'])) {
2674
                $defaultVisibility = $courseVisibility;
2675
            }
2676
        }
2677
2678
        return $defaultVisibility;
2679
    }
2680
2681
    /**
2682
     * @param array $_course
2683
     *
2684
     * @return CDocument
2685
     */
2686
    public static function createDefaultAudioFolder($_course)
2687
    {
2688
        if (!isset($_course['path'])) {
2689
            return false;
2690
        }
2691
2692
        return self::addDocument($_course, '/audio', 'folder', 0, 'Audio');
2693
    }
2694
2695
    /**
2696
     * Generate a default certificate for a courses.
2697
     *
2698
     * @todo move to certificate lib
2699
     *
2700
     * @global string $css CSS directory
2701
     * @global string $img_dir image directory
2702
     * @global string $default_course_dir Course directory
2703
     * @global string $js JS directory
2704
     *
2705
     * @param array $courseData     The course info
2706
     * @param bool  $fromBaseCourse
2707
     * @param int   $sessionId
2708
     */
2709
    public static function generateDefaultCertificate(
2710
        $courseData,
2711
        $fromBaseCourse = false,
2712
        $sessionId = 0
2713
    ) {
2714
        if (empty($courseData)) {
2715
            return false;
2716
        }
2717
2718
        global $css, $img_dir, $default_course_dir, $js;
2719
        $codePath = api_get_path(REL_CODE_PATH);
2720
        $dir = '/certificates';
2721
        $comment = null;
2722
        $title = get_lang('Default certificate');
2723
        $fileName = api_replace_dangerous_char($title);
2724
        $fileType = 'certificate';
2725
        $templateContent = file_get_contents(api_get_path(SYS_CODE_PATH).'gradebook/certificate_template/template.html');
2726
2727
        $search = ['{CSS}', '{IMG_DIR}', '{REL_CODE_PATH}', '{COURSE_DIR}'];
2728
        $replace = [$css.$js, $img_dir, $codePath, $default_course_dir];
2729
2730
        $fileContent = str_replace($search, $replace, $templateContent);
2731
        $saveFilePath = "$dir/$fileName.html";
2732
2733
        if ($fromBaseCourse) {
2734
            $defaultCertificateId = self::get_default_certificate_id($courseData['real_id'], 0);
2735
            if (!empty($defaultCertificateId)) {
2736
                // We have a certificate from the course base
2737
                $documentData = self::get_document_data_by_id(
2738
                    $defaultCertificateId,
2739
                    $courseData['code'],
2740
                    false,
2741
                    0
2742
                );
2743
2744
                if (isset($documentData['absolute_path'])) {
2745
                    $fileContent = file_get_contents($documentData['absolute_path']);
2746
                }
2747
            }
2748
        }
2749
2750
        $document = self::addDocument(
2751
            $courseData,
2752
            $saveFilePath,
2753
            $fileType,
2754
            0,
2755
            $title,
2756
            $comment,
2757
            0, //$readonly = 0,
2758
            true, //$save_visibility = true,
2759
            null, //$group_id = null,
2760
            $sessionId,
2761
            0,
2762
            false,
2763
            $fileContent
2764
        );
2765
2766
        $defaultCertificateId = self::get_default_certificate_id($courseData['real_id'], $sessionId);
2767
2768
        if (!isset($defaultCertificateId)) {
2769
            self::attach_gradebook_certificate(
2770
                $courseData['real_id'],
2771
                $document->getIid(),
2772
                $sessionId
2773
            );
2774
        }
2775
    }
2776
2777
    /**
2778
     * Get folder/file suffix.
2779
     *
2780
     * @param array $courseInfo
2781
     * @param int   $sessionId
2782
     * @param int   $groupId
2783
     *
2784
     * @return string
2785
     */
2786
    public static function getDocumentSuffix($courseInfo, $sessionId, $groupId)
2787
    {
2788
        // If no session or group, then no suffix.
2789
        if (empty($sessionId) && empty($groupId)) {
2790
            return '';
2791
        }
2792
2793
        return '__'.(int) $sessionId.'__'.(int) $groupId;
2794
    }
2795
2796
    /**
2797
     * Fix a document name adding session id and group id
2798
     * Turns picture.jpg -> picture__1__2.jpg
2799
     * Where 1 = session id and 2 group id
2800
     * Of session id and group id are empty then the function returns:
2801
     * picture.jpg ->  picture.jpg.
2802
     *
2803
     * @param string $name       folder or file name
2804
     * @param string $type       'folder' or 'file'
2805
     * @param array  $courseInfo
2806
     * @param int    $sessionId
2807
     * @param int    $groupId
2808
     *
2809
     * @return string
2810
     */
2811
    public static function fixDocumentName($name, $type, $courseInfo, $sessionId, $groupId)
2812
    {
2813
        $suffix = self::getDocumentSuffix($courseInfo, $sessionId, $groupId);
2814
2815
        switch ($type) {
2816
            case 'folder':
2817
                $name = $name.$suffix;
2818
                break;
2819
            case 'file':
2820
                $name = self::addSuffixToFileName($name, $suffix);
2821
                break;
2822
        }
2823
2824
        return $name;
2825
    }
2826
2827
    /**
2828
     * Add a suffix to a file Example:
2829
     * /folder/picture.jpg => to /folder/picture_this.jpg
2830
     * where "_this" is the suffix.
2831
     *
2832
     * @param string $name
2833
     * @param string $suffix
2834
     *
2835
     * @return string
2836
     */
2837
    public static function addSuffixToFileName($name, $suffix)
2838
    {
2839
        $extension = pathinfo($name, PATHINFO_EXTENSION);
2840
        $fileName = pathinfo($name, PATHINFO_FILENAME);
2841
        $dir = pathinfo($name, PATHINFO_DIRNAME);
2842
2843
        if ('.' == $dir) {
2844
            $dir = null;
2845
        }
2846
2847
        if (!empty($dir) && '/' != $dir) {
2848
            $dir = $dir.'/';
2849
        }
2850
2851
        $name = $dir.$fileName.$suffix.'.'.$extension;
2852
2853
        return $name;
2854
    }
2855
2856
    /**
2857
     * Check if folder exist in the course base or in the session course.
2858
     *
2859
     * @param string $folder     Example: /folder/folder2
2860
     * @param array  $courseInfo
2861
     * @param int    $sessionId
2862
     * @param int    $groupId    group.id
2863
     *
2864
     * @return bool
2865
     */
2866
    public static function folderExists(
2867
        $folder,
2868
        $courseInfo,
2869
        $sessionId,
2870
        $groupId
2871
    ) {
2872
        $courseId = $courseInfo['real_id'];
2873
2874
        if (empty($courseId)) {
2875
            return false;
2876
        }
2877
2878
        $sessionId = (int) $sessionId;
2879
        $folderWithSuffix = self::fixDocumentName(
2880
            $folder,
2881
            'folder',
2882
            $courseInfo,
2883
            $sessionId,
2884
            $groupId
2885
        );
2886
2887
        $folder = Database::escape_string($folder);
2888
        $folderWithSuffix = Database::escape_string($folderWithSuffix);
2889
2890
        // Check if pathname already exists inside document table
2891
        $tbl_document = Database::get_course_table(TABLE_DOCUMENT);
2892
        $sql = "SELECT iid, path FROM $tbl_document
2893
                WHERE
2894
                    filetype = 'folder' AND
2895
                    c_id = $courseId AND
2896
                    (path = '$folder' OR path = '$folderWithSuffix') AND
2897
                    (session_id = 0 OR session_id IS NULL OR session_id = $sessionId)
2898
        ";
2899
2900
        $rs = Database::query($sql);
2901
        if (Database::num_rows($rs)) {
2902
            return true;
2903
        }
2904
2905
        return false;
2906
    }
2907
2908
    /**
2909
     * Check if file exist in the course base or in the session course.
2910
     *
2911
     * @param string $fileName   Example: /folder/picture.jpg
2912
     * @param array  $courseInfo
2913
     * @param int    $sessionId
2914
     * @param int    $groupId
2915
     *
2916
     * @return bool
2917
     */
2918
    public static function documentExists(
2919
        $fileName,
2920
        $courseInfo,
2921
        $sessionId,
2922
        $groupId
2923
    ) {
2924
        $courseId = $courseInfo['real_id'];
2925
2926
        if (empty($courseId)) {
2927
            return false;
2928
        }
2929
2930
        $sessionId = (int) $sessionId;
2931
        $fileNameEscape = Database::escape_string($fileName);
2932
2933
        $fileNameWithSuffix = self::fixDocumentName(
2934
            $fileName,
2935
            'file',
2936
            $courseInfo,
2937
            $sessionId,
2938
            $groupId
2939
        );
2940
2941
        $fileNameWithSuffix = Database::escape_string($fileNameWithSuffix);
2942
2943
        // Check if pathname already exists inside document table
2944
        $table = Database::get_course_table(TABLE_DOCUMENT);
2945
        $sql = "SELECT iid, title FROM $table
2946
                WHERE
2947
                    filetype = 'file' AND
2948
                    c_id = $courseId AND
2949
                    (
2950
                        title = '".$fileNameEscape."' OR
2951
                        title = '$fileNameWithSuffix'
2952
                    ) AND
2953
                    (session_id = 0 OR session_id = $sessionId)
2954
        ";
2955
        $rs = Database::query($sql);
2956
        if (Database::num_rows($rs)) {
2957
            return true;
2958
        }
2959
2960
        return false;
2961
    }
2962
2963
    /**
2964
     * @param string $path
2965
     * @param string $name
2966
     * @param array  $courseInfo
2967
     * @param int    $sessionId
2968
     * @param int    $groupId
2969
     *
2970
     * @return string
2971
     */
2972
    public static function getUniqueFileName($path, $name, $courseInfo, $sessionId, $groupId)
2973
    {
2974
        $counter = 1;
2975
        $filePath = $path.$name;
2976
        $uniqueName = $name;
2977
        $baseName = pathinfo($name, PATHINFO_FILENAME);
2978
        $extension = pathinfo($name, PATHINFO_EXTENSION);
2979
2980
        return uniqid($baseName.'-', true).'.'.$extension;
2981
2982
        while ($documentExists = self::documentExists(
0 ignored issues
show
Unused Code introduced by
WhileNode is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
2983
            $filePath,
2984
            $courseInfo,
2985
            $sessionId,
2986
            $groupId
2987
        )) {
2988
            $uniqueName = self::addSuffixToFileName($name, '_'.$counter);
2989
            $filePath = $path.$uniqueName;
2990
            $counter++;
2991
        }
2992
2993
        return $uniqueName;
2994
    }
2995
2996
    /**
2997
     * Builds the form that enables the user to
2998
     * select a directory to browse/upload in.
2999
     *
3000
     * @param array    An array containing the folders we want to be able to select
3001
     * @param string    The current folder (path inside of the "document" directory, including the prefix "/")
3002
     * @param string    Group directory, if empty, prevents documents to be uploaded
3003
     * (because group documents cannot be uploaded in root)
3004
     * @param bool    Whether to change the renderer (this will add a template <span>
3005
     * to the QuickForm object displaying the form)
3006
     *
3007
     * @return string html form
3008
     */
3009
    public static function build_directory_selector(
3010
        $folders,
3011
        $document_id,
3012
        $group_dir = '',
3013
        $change_renderer = false,
3014
        &$form = null,
3015
        $selectName = 'id'
3016
    ) {
3017
        $doc_table = Database::get_course_table(TABLE_DOCUMENT);
3018
        $course_id = api_get_course_int_id();
3019
        $folder_titles = [];
3020
3021
        if (is_array($folders)) {
3022
            $escaped_folders = [];
3023
            foreach ($folders as $key => &$val) {
3024
                $escaped_folders[$key] = Database::escape_string($val);
3025
            }
3026
            $folder_sql = implode("','", $escaped_folders);
3027
3028
            $sql = "SELECT DISTINCT docs.title, n.path
3029
                    FROM resource_node AS n
3030
                    INNER JOIN $doc_table AS docs
3031
                    ON (docs.resource_node_id = n.id)
3032
                    INNER JOIN resource_link l
3033
                    ON (l.resource_node_id = n.id)
3034
                    WHERE
3035
                        l.c_id = $course_id AND
3036
                        docs.filetype = 'folder' AND
3037
                        n.path IN ('".$folder_sql."') AND
3038
                        l.deleted_at IS NULL
3039
                         ";
3040
3041
            /*$sql = "SELECT path, title
3042
                    FROM $doc_table
3043
                    WHERE
3044
                        filetype = 'folder' AND
3045
                        c_id = $course_id AND
3046
                        path IN ('".$folder_sql."') ";*/
3047
            $res = Database::query($sql);
3048
            $folder_titles = [];
3049
            while ($obj = Database::fetch_object($res)) {
3050
                $folder_titles[$obj->path] = $obj->title;
3051
            }
3052
        }
3053
3054
        $attributes = [];
3055
        if (empty($form)) {
3056
            $form = new FormValidator('selector', 'GET', api_get_self().'?'.api_get_cidreq());
3057
            $attributes = ['onchange' => 'javascript: document.selector.submit();'];
3058
        }
3059
        $form->addElement('hidden', 'cidReq', api_get_course_id());
3060
        $form->addElement('hidden', 'cid', api_get_course_int_id());
3061
        $form->addElement('hidden', 'sid', api_get_session_id());
3062
        $form->addElement('hidden', 'gid', api_get_group_id());
3063
3064
        $parent_select = $form->addSelect(
3065
            $selectName,
3066
            get_lang('Current folder'),
3067
            [],
3068
            $attributes
3069
        );
3070
3071
        // Group documents cannot be uploaded in the root
3072
        if (empty($group_dir)) {
3073
            $parent_select->addOption(get_lang('Documents'), '/');
3074
3075
            if (is_array($folders)) {
3076
                foreach ($folders as $folder_id => &$folder) {
3077
                    if (!isset($folder_titles[$folder])) {
3078
                        continue;
3079
                    }
3080
                    $selected = ($document_id == $folder_id) ? ' selected="selected"' : '';
3081
                    $path_parts = explode('/', $folder);
3082
                    $folder_titles[$folder] = cut($folder_titles[$folder], 80);
3083
                    $counter = count($path_parts) - 2;
3084
                    if ($counter > 0) {
3085
                        $label = str_repeat('&nbsp;&nbsp;&nbsp;', $counter).' &mdash; '.$folder_titles[$folder];
3086
                    } else {
3087
                        $label = ' &mdash; '.$folder_titles[$folder];
3088
                    }
3089
                    $parent_select->addOption($label, $folder_id);
3090
                    if ('' != $selected) {
3091
                        $parent_select->setSelected($folder_id);
3092
                    }
3093
                }
3094
            }
3095
        } else {
3096
            if (!empty($folders)) {
3097
                foreach ($folders as $folder_id => &$folder) {
3098
                    $selected = ($document_id == $folder_id) ? ' selected="selected"' : '';
3099
                    $label = $folder_titles[$folder];
3100
                    if ($folder == $group_dir) {
3101
                        $label = get_lang('Documents');
3102
                    } else {
3103
                        $path_parts = explode('/', str_replace($group_dir, '', $folder));
3104
                        $label = cut($label, 80);
3105
                        $label = str_repeat('&nbsp;&nbsp;&nbsp;', count($path_parts) - 2).' &mdash; '.$label;
3106
                    }
3107
                    $parent_select->addOption($label, $folder_id);
3108
                    if ('' != $selected) {
3109
                        $parent_select->setSelected($folder_id);
3110
                    }
3111
                }
3112
            }
3113
        }
3114
3115
        return $form->toHtml();
3116
    }
3117
3118
    /**
3119
     * Builds an img html tag for the file type.
3120
     *
3121
     * @param string $type            (file/folder)
3122
     * @param string $path
3123
     * @param bool   $isAllowedToEdit
3124
     *
3125
     * @return string img html tag
3126
     */
3127
    public static function build_document_icon_tag($type, $path, $isAllowedToEdit = null)
3128
    {
3129
        $basename = basename($path);
3130
        $sessionId = api_get_session_id();
3131
        if (is_null($isAllowedToEdit)) {
3132
            $isAllowedToEdit = api_is_allowed_to_edit(null, true);
3133
        }
3134
        $user_image = false;
3135
        if ('file' == $type) {
3136
            $icon = choose_image($basename);
3137
            $basename = substr(strrchr($basename, '.'), 1);
3138
        } elseif ('link' == $type) {
3139
            $icon = 'clouddoc.png';
3140
            $basename = get_lang('Cloud file link');
3141
        } else {
3142
            if ('/shared_folder' == $path) {
3143
                $icon = 'folder_users.png';
3144
                if ($isAllowedToEdit) {
3145
                    $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3146
The users folder contains a folder for each user who has accessed it through the documents tool, or when any file has been sent in the course through the online editor. If neither circumstances has occurred, then no user folder will have been created. In the case of groups, files that are sent through the editor will be added in the folder of each group, which is only accessible by students from this group.');
3147
                } else {
3148
                    $basename = get_lang('Folders of users');
3149
                }
3150
            } elseif (strstr($basename, 'sf_user_')) {
3151
                $userInfo = api_get_user_info(substr($basename, 8));
3152
                $icon = $userInfo['avatar_small'];
3153
                $basename = get_lang('User folder').' '.$userInfo['complete_name'];
3154
                $user_image = true;
3155
            } elseif (strstr($path, 'shared_folder_session_')) {
3156
                $sessionName = api_get_session_name($sessionId);
3157
                if ($isAllowedToEdit) {
3158
                    $basename = '***('.$sessionName.')*** '.get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3159
The users folder contains a folder for each user who has accessed it through the documents tool, or when any file has been sent in the course through the online editor. If neither circumstances has occurred, then no user folder will have been created. In the case of groups, files that are sent through the editor will be added in the folder of each group, which is only accessible by students from this group.');
3160
                } else {
3161
                    $basename = get_lang('Folders of users').' ('.$sessionName.')';
3162
                }
3163
                $icon = 'folder_users.png';
3164
            } else {
3165
                $icon = 'folder_document.png';
3166
3167
                if ('/audio' == $path) {
3168
                    $icon = 'folder_audio.png';
3169
                    if ($isAllowedToEdit) {
3170
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3171
This folder contains the default archives. You can clear files or add new ones, but if a file is hidden when it is inserted in a web document, the students will not be able to see it in this document. When inserting a file in a web document, first make sure it is visible. The folders can remain hidden.');
3172
                    } else {
3173
                        $basename = get_lang('Audio');
3174
                    }
3175
                } elseif ('/flash' == $path) {
3176
                    $icon = 'folder_flash.png';
3177
                    if ($isAllowedToEdit) {
3178
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3179
This folder contains the default archives. You can clear files or add new ones, but if a file is hidden when it is inserted in a web document, the students will not be able to see it in this document. When inserting a file in a web document, first make sure it is visible. The folders can remain hidden.');
3180
                    } else {
3181
                        $basename = get_lang('Flash');
3182
                    }
3183
                } elseif ('/images' == $path) {
3184
                    $icon = 'folder_images.png';
3185
                    if ($isAllowedToEdit) {
3186
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3187
This folder contains the default archives. You can clear files or add new ones, but if a file is hidden when it is inserted in a web document, the students will not be able to see it in this document. When inserting a file in a web document, first make sure it is visible. The folders can remain hidden.');
3188
                    } else {
3189
                        $basename = get_lang('Images');
3190
                    }
3191
                } elseif ('/video' == $path) {
3192
                    $icon = 'folder_video.png';
3193
                    if ($isAllowedToEdit) {
3194
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3195
This folder contains the default archives. You can clear files or add new ones, but if a file is hidden when it is inserted in a web document, the students will not be able to see it in this document. When inserting a file in a web document, first make sure it is visible. The folders can remain hidden.');
3196
                    } else {
3197
                        $basename = get_lang('Video');
3198
                    }
3199
                } elseif ('/images/gallery' == $path) {
3200
                    $icon = 'folder_gallery.png';
3201
                    if ($isAllowedToEdit) {
3202
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3203
This folder contains the default archives. You can clear files or add new ones, but if a file is hidden when it is inserted in a web document, the students will not be able to see it in this document. When inserting a file in a web document, first make sure it is visible. The folders can remain hidden.');
3204
                    } else {
3205
                        $basename = get_lang('Gallery');
3206
                    }
3207
                } elseif ('/chat_files' == $path) {
3208
                    $icon = 'folder_chat.png';
3209
                    if ($isAllowedToEdit) {
3210
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3211
This folder contains all sessions that have been opened in the chat. Although the chat sessions can often be trivial, others can be really interesting and worthy of being incorporated as an additional work document. To do this without changing the visibility of this folder, make the file visible and link it from where you deem appropriate. It is not recommended to make this folder visible to all.');
3212
                    } else {
3213
                        $basename = get_lang('Chat conversations history');
3214
                    }
3215
                } elseif ('/learning_path' == $path) {
3216
                    $icon = 'folder_learningpath.png';
3217
                    if ($isAllowedToEdit) {
3218
                        $basename = get_lang('HelpFolderLearning paths');
3219
                    } else {
3220
                        $basename = get_lang('Learning paths');
3221
                    }
3222
                }
3223
            }
3224
        }
3225
3226
        if ($user_image) {
3227
            return Display::img($icon, $basename, [], false);
3228
        }
3229
3230
        return Display::return_icon($icon, $basename);
3231
    }
3232
3233
    public static function isBasicCourseFolder($path, $sessionId)
3234
    {
3235
        $cleanPath = Security::remove_XSS($path);
3236
        $basicCourseFolder = '/basic-course-documents__'.$sessionId.'__0';
3237
3238
        return $cleanPath == $basicCourseFolder;
3239
    }
3240
3241
    /**
3242
     * Adds a new document to the database.
3243
     *
3244
     * @param array  $courseInfo
3245
     * @param string $path
3246
     * @param string $fileType
3247
     * @param int    $fileSize
3248
     * @param string $title
3249
     * @param string $comment
3250
     * @param int    $readonly
3251
     * @param int    $visibility       see ResourceLink constants
3252
     * @param int    $groupId          group.id
3253
     * @param int    $sessionId        Session ID, if any
3254
     * @param int    $userId           creator user id
3255
     * @param bool   $sendNotification
3256
     * @param string $content
3257
     * @param int    $parentId
3258
     * @param string $realPath
3259
     *
3260
     * @return CDocument|false
3261
     */
3262
    public static function addDocument(
3263
        $courseInfo,
3264
        $path,
3265
        $fileType,
3266
        $fileSize,
3267
        $title,
3268
        $comment = null,
3269
        $readonly = 0,
3270
        $visibility = null,
3271
        $groupId = 0,
3272
        $sessionId = 0,
3273
        $userId = 0,
3274
        $sendNotification = true,
3275
        $content = '',
3276
        $parentId = 0,
3277
        $realPath = ''
3278
    ) {
3279
        $userId = empty($userId) ? api_get_user_id() : $userId;
3280
        if (empty($userId)) {
3281
            return false;
3282
        }
3283
3284
        $userEntity = api_get_user_entity($userId);
3285
        if (null === $userEntity) {
3286
            return false;
3287
        }
3288
3289
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
3290
        if (null === $courseEntity) {
3291
            return false;
3292
        }
3293
3294
        $sessionId = empty($sessionId) ? api_get_session_id() : $sessionId;
3295
        $session = api_get_session_entity($sessionId);
3296
        $group = api_get_group_entity($groupId);
3297
        $readonly = (int) $readonly;
3298
        $documentRepo = Container::getDocumentRepository();
3299
3300
        /** @var \Chamilo\CoreBundle\Entity\AbstractResource $parentResource */
3301
        $parentResource = $courseEntity;
3302
        if (!empty($parentId)) {
3303
            $parent = $documentRepo->find($parentId);
3304
            if ($parent) {
3305
                $parentResource = $parent;
3306
            }
3307
        }
3308
3309
        $document = $documentRepo->findCourseResourceByTitle(
3310
            $title,
3311
            $parentResource->getResourceNode(),
3312
            $courseEntity,
3313
            $session,
3314
            $group
3315
        );
3316
3317
        // Document already exists
3318
        if (null !== $document) {
3319
            return $document;
3320
        }
3321
3322
        // is updated using the title
3323
        $document = (new CDocument())
3324
            ->setFiletype($fileType)
3325
            ->setTitle($title)
3326
            ->setComment($comment)
3327
            ->setReadonly(1 === $readonly)
3328
            ->setCreator(api_get_user_entity())
3329
            ->setParent($parentResource)
3330
            ->addCourseLink($courseEntity, $session, $group)
3331
        ;
3332
3333
        $em = Database::getManager();
3334
        $em->persist($document);
3335
        $em->flush();
3336
3337
        $repo = Container::getDocumentRepository();
3338
        if (!empty($content)) {
3339
            $repo->addFileFromString($document, $title, 'text/html', $content, true);
3340
        } else {
3341
            if (!empty($realPath) && !is_dir($realPath) && file_exists($realPath)) {
3342
                $repo->addFileFromPath($document, $title, $realPath);
3343
            }
3344
        }
3345
3346
        if ($document) {
3347
            $allowNotification = ('true' === api_get_setting('document.send_notification_when_document_added'));
3348
            if ($sendNotification && $allowNotification) {
3349
                $courseTitle = $courseEntity->getTitle();
3350
                if (!empty($sessionId)) {
3351
                    $sessionInfo = api_get_session_info($sessionId);
3352
                    $courseTitle .= ' ( '.$sessionInfo['name'].') ';
3353
                }
3354
3355
                $url = api_get_path(WEB_CODE_PATH).
3356
                    'document/showinframes.php?cid='.$courseEntity->getId().'&sid='.$sessionId.'&id='.$document->getIid();
3357
                $link = Display::url(basename($title), $url, ['target' => '_blank']);
3358
                $userInfo = api_get_user_info($userId);
3359
                $message = sprintf(
3360
                    get_lang('A new document %s has been added to the document tool in your course %s by %s.'),
3361
                    $link,
3362
                    $courseTitle,
3363
                    $userInfo['complete_name']
3364
                );
3365
                $subject = sprintf(get_lang('New document added to course %s'), $courseTitle);
3366
                MessageManager::sendMessageToAllUsersInCourse($subject, $message, $courseEntity, $sessionId);
3367
            }
3368
3369
            return $document;
3370
        }
3371
3372
        return false;
3373
    }
3374
}
3375