Passed
Push — master ( 82afe7...83c1fc )
by Yannick
13:03 queued 04:26
created

DocumentManager::get_document_preview()   C

Complexity

Conditions 13
Paths 48

Size

Total Lines 146
Code Lines 93

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 93
c 0
b 0
f 0
nc 48
nop 15
dl 0
loc 146
rs 5.446

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\Enums\ObjectIcon;
9
use Chamilo\CoreBundle\Framework\Container;
10
use Chamilo\CourseBundle\Entity\CDocument;
11
12
/**
13
 *  Class DocumentManager
14
 *  This is the document library for Chamilo.
15
 *  It is / will be used to provide a service layer to all document-using tools.
16
 *  and eliminate code duplication fro group documents, scorm documents, main documents.
17
 *  Include/require it in your code to use its functionality.
18
 */
19
class DocumentManager
20
{
21
    /**
22
     * @param string $course_code
23
     *
24
     * @return int the document folder quota for the current course in megabytes
25
     *             or the default quota
26
     */
27
    public static function get_course_quota($course_code = null)
28
    {
29
        if (empty($course_code)) {
30
            $course_info = api_get_course_info();
31
        } else {
32
            $course_info = api_get_course_info($course_code);
33
        }
34
35
        $course_quota = null;
36
        if (empty($course_info)) {
37
            return DEFAULT_DOCUMENT_QUOTA;
38
        } else {
39
            $course_quota = $course_info['disk_quota'];
40
        }
41
        if (is_null($course_quota) || empty($course_quota)) {
42
            // Course table entry for quota was null, then use default value
43
            $course_quota = DEFAULT_DOCUMENT_QUOTA;
44
        }
45
46
        return $course_quota;
47
    }
48
49
    /**
50
     * Get the content type of a file by checking the extension
51
     * We could use mime_content_type() with php-versions > 4.3,
52
     * but this doesn't work as it should on Windows installations.
53
     *
54
     * @param string $filename or boolean TRUE to return complete array
55
     *
56
     * @author ? first version
57
     * @author Bert Vanderkimpen
58
     *
59
     * @return string
60
     */
61
    public static function file_get_mime_type($filename)
62
    {
63
        // All MIME types in an array (from 1.6, this is the authorative source)
64
        // Please, keep this alphabetical if you add something to this list!
65
        $mimeTypes = [
66
            'ai' => 'application/postscript',
67
            'aif' => 'audio/x-aiff',
68
            'aifc' => 'audio/x-aiff',
69
            'aiff' => 'audio/x-aiff',
70
            'asf' => 'video/x-ms-asf',
71
            'asc' => 'text/plain',
72
            'au' => 'audio/basic',
73
            'avi' => 'video/x-msvideo',
74
            'bcpio' => 'application/x-bcpio',
75
            'bin' => 'application/octet-stream',
76
            'bmp' => 'image/bmp',
77
            'cdf' => 'application/x-netcdf',
78
            'class' => 'application/octet-stream',
79
            'cpio' => 'application/x-cpio',
80
            'cpt' => 'application/mac-compactpro',
81
            'csh' => 'application/x-csh',
82
            'css' => 'text/css',
83
            'dcr' => 'application/x-director',
84
            'dir' => 'application/x-director',
85
            'djv' => 'image/vnd.djvu',
86
            'djvu' => 'image/vnd.djvu',
87
            'dll' => 'application/octet-stream',
88
            'dmg' => 'application/x-diskcopy',
89
            'dms' => 'application/octet-stream',
90
            'doc' => 'application/msword',
91
            'docm' => 'application/vnd.ms-word.document.macroEnabled.12',
92
            'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
93
            'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
94
            'dvi' => 'application/x-dvi',
95
            'dwg' => 'application/vnd.dwg',
96
            'dwf' => 'application/vnd.dwf',
97
            'dxf' => 'application/vnd.dxf',
98
            'dxr' => 'application/x-director',
99
            'eps' => 'application/postscript',
100
            'epub' => 'application/epub+zip',
101
            'etx' => 'text/x-setext',
102
            'exe' => 'application/octet-stream',
103
            'ez' => 'application/andrew-inset',
104
            'flv' => 'video/flv',
105
            'gif' => 'image/gif',
106
            'gtar' => 'application/x-gtar',
107
            'gz' => 'application/x-gzip',
108
            'hdf' => 'application/x-hdf',
109
            'hqx' => 'application/mac-binhex40',
110
            'htm' => 'text/html',
111
            'html' => 'text/html',
112
            'ice' => 'x-conference-xcooltalk',
113
            'ief' => 'image/ief',
114
            'iges' => 'model/iges',
115
            'igs' => 'model/iges',
116
            'jar' => 'application/java-archiver',
117
            'jpe' => 'image/jpeg',
118
            'jpeg' => 'image/jpeg',
119
            'jpg' => 'image/jpeg',
120
            'js' => 'application/x-javascript',
121
            'kar' => 'audio/midi',
122
            'lam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
123
            'latex' => 'application/x-latex',
124
            'lha' => 'application/octet-stream',
125
            'log' => 'text/plain',
126
            'lzh' => 'application/octet-stream',
127
            'm1a' => 'audio/mpeg',
128
            'm2a' => 'audio/mpeg',
129
            'm3u' => 'audio/x-mpegurl',
130
            'man' => 'application/x-troff-man',
131
            'me' => 'application/x-troff-me',
132
            'mesh' => 'model/mesh',
133
            'mid' => 'audio/midi',
134
            'midi' => 'audio/midi',
135
            'mov' => 'video/quicktime',
136
            'movie' => 'video/x-sgi-movie',
137
            'mp2' => 'audio/mpeg',
138
            'mp3' => 'audio/mpeg',
139
            'mp4' => 'video/mp4',
140
            'mpa' => 'audio/mpeg',
141
            'mpe' => 'video/mpeg',
142
            'mpeg' => 'video/mpeg',
143
            'mpg' => 'video/mpeg',
144
            'mpga' => 'audio/mpeg',
145
            'ms' => 'application/x-troff-ms',
146
            'msh' => 'model/mesh',
147
            'mxu' => 'video/vnd.mpegurl',
148
            'nc' => 'application/x-netcdf',
149
            'oda' => 'application/oda',
150
            'oga' => 'audio/ogg',
151
            'ogg' => 'application/ogg',
152
            'ogx' => 'application/ogg',
153
            'ogv' => 'video/ogg',
154
            'pbm' => 'image/x-portable-bitmap',
155
            'pct' => 'image/pict',
156
            'pdb' => 'chemical/x-pdb',
157
            'pdf' => 'application/pdf',
158
            'pgm' => 'image/x-portable-graymap',
159
            'pgn' => 'application/x-chess-pgn',
160
            'pict' => 'image/pict',
161
            'png' => 'image/png',
162
            'pnm' => 'image/x-portable-anymap',
163
            'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
164
            'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
165
            'pps' => 'application/vnd.ms-powerpoint',
166
            'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
167
            'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
168
            'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
169
            'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
170
            'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
171
            'ppm' => 'image/x-portable-pixmap',
172
            'ppt' => 'application/vnd.ms-powerpoint',
173
            'ps' => 'application/postscript',
174
            'qt' => 'video/quicktime',
175
            'ra' => 'audio/x-realaudio',
176
            'ram' => 'audio/x-pn-realaudio',
177
            'rar' => 'image/x-rar-compressed',
178
            'ras' => 'image/x-cmu-raster',
179
            'rgb' => 'image/x-rgb',
180
            'rm' => 'audio/x-pn-realaudio',
181
            'roff' => 'application/x-troff',
182
            'rpm' => 'audio/x-pn-realaudio-plugin',
183
            'rtf' => 'text/rtf',
184
            'rtx' => 'text/richtext',
185
            'sgm' => 'text/sgml',
186
            'sgml' => 'text/sgml',
187
            'sh' => 'application/x-sh',
188
            'shar' => 'application/x-shar',
189
            'silo' => 'model/mesh',
190
            'sib' => 'application/X-Sibelius-Score',
191
            'sit' => 'application/x-stuffit',
192
            'skd' => 'application/x-koan',
193
            'skm' => 'application/x-koan',
194
            'skp' => 'application/x-koan',
195
            'skt' => 'application/x-koan',
196
            'smi' => 'application/smil',
197
            'smil' => 'application/smil',
198
            'snd' => 'audio/basic',
199
            'so' => 'application/octet-stream',
200
            'spl' => 'application/x-futuresplash',
201
            'src' => 'application/x-wais-source',
202
            'sv4cpio' => 'application/x-sv4cpio',
203
            'sv4crc' => 'application/x-sv4crc',
204
            'svf' => 'application/vnd.svf',
205
            'svg' => 'image/svg+xml',
206
            //'svgz' => 'image/svg+xml',
207
            'swf' => 'application/x-shockwave-flash',
208
            'sxc' => 'application/vnd.sun.xml.calc',
209
            'sxi' => 'application/vnd.sun.xml.impress',
210
            'sxw' => 'application/vnd.sun.xml.writer',
211
            't' => 'application/x-troff',
212
            'tar' => 'application/x-tar',
213
            'tcl' => 'application/x-tcl',
214
            'tex' => 'application/x-tex',
215
            'texi' => 'application/x-texinfo',
216
            'texinfo' => 'application/x-texinfo',
217
            'tga' => 'image/x-targa',
218
            'tif' => 'image/tif',
219
            'tiff' => 'image/tiff',
220
            'tr' => 'application/x-troff',
221
            'tsv' => 'text/tab-seperated-values',
222
            'txt' => 'text/plain',
223
            'ustar' => 'application/x-ustar',
224
            'vcd' => 'application/x-cdlink',
225
            'vrml' => 'model/vrml',
226
            'wav' => 'audio/x-wav',
227
            'wbmp' => 'image/vnd.wap.wbmp',
228
            'wbxml' => 'application/vnd.wap.wbxml',
229
            'webp' => 'image/webp',
230
            'wml' => 'text/vnd.wap.wml',
231
            'wmlc' => 'application/vnd.wap.wmlc',
232
            'wmls' => 'text/vnd.wap.wmlscript',
233
            'wmlsc' => 'application/vnd.wap.wmlscriptc',
234
            'wma' => 'audio/x-ms-wma',
235
            'wmv' => 'video/x-ms-wmv',
236
            'wrl' => 'model/vrml',
237
            'xbm' => 'image/x-xbitmap',
238
            'xht' => 'application/xhtml+xml',
239
            'xhtml' => 'application/xhtml+xml',
240
            'xls' => 'application/vnd.ms-excel',
241
            'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
242
            'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
243
            'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
244
            'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
245
            'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
246
            'xml' => 'text/xml',
247
            'xpm' => 'image/x-xpixmap',
248
            'xsl' => 'text/xml',
249
            'xwd' => 'image/x-windowdump',
250
            'xyz' => 'chemical/x-xyz',
251
            'zip' => 'application/zip',
252
        ];
253
254
        if (true === $filename) {
255
            return $mimeTypes;
256
        }
257
258
        // Get the extension of the file
259
        $extension = explode('.', $filename);
260
261
        // $filename will be an array if a . was found
262
        if (is_array($extension)) {
263
            $extension = strtolower($extension[count($extension) - 1]);
264
        } else {
265
            //file without extension
266
            $extension = 'empty';
267
        }
268
269
        //if the extension is found, return the content type
270
        if (isset($mimeTypes[$extension])) {
271
            return $mimeTypes[$extension];
272
        }
273
274
        return 'application/octet-stream';
275
    }
276
277
    /**
278
     * This function smart streams a file to the client using HTTP headers.
279
     *
280
     * @param string $fullFilename The full path of the file to be sent
281
     * @param string $filename     The name of the file as shown to the client
282
     * @param string $contentType  The MIME type of the file
283
     *
284
     * @return bool false if file doesn't exist, true if stream succeeded
285
     */
286
    public static function smartReadFile($fullFilename, $filename, $contentType = 'application/octet-stream')
287
    {
288
        if (!file_exists($fullFilename)) {
289
            header("HTTP/1.1 404 Not Found");
290
291
            return false;
292
        }
293
294
        $size = filesize($fullFilename);
295
        $time = date('r', filemtime($fullFilename));
296
297
        $fm = @fopen($fullFilename, 'rb');
298
        if (!$fm) {
0 ignored issues
show
introduced by
$fm is of type false|resource, thus it always evaluated to false.
Loading history...
299
            header("HTTP/1.1 505 Internal server error");
300
301
            return false;
302
        }
303
304
        $begin = 0;
305
        $end = $size - 1;
306
307
        if (isset($_SERVER['HTTP_RANGE'])) {
308
            if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
309
                $begin = intval($matches[1]);
310
                if (!empty($matches[2])) {
311
                    $end = intval($matches[2]);
312
                }
313
            }
314
        }
315
316
        if (isset($_SERVER['HTTP_RANGE'])) {
317
            header('HTTP/1.1 206 Partial Content');
318
        } else {
319
            header('HTTP/1.1 200 OK');
320
        }
321
322
        header("Content-Type: $contentType");
323
        header('Cache-Control: public, must-revalidate, max-age=0');
324
        header('Pragma: no-cache');
325
        header('Accept-Ranges: bytes');
326
        header('Content-Length:'.(($end - $begin) + 1));
327
        if (isset($_SERVER['HTTP_RANGE'])) {
328
            header("Content-Range: bytes $begin-$end/$size");
329
        }
330
        header("Content-Disposition: inline; filename=$filename");
331
        header("Content-Transfer-Encoding: binary");
332
        header("Last-Modified: $time");
333
334
        $cur = $begin;
335
        fseek($fm, $begin, 0);
336
337
        while (!feof($fm) && $cur <= $end && (0 == connection_status())) {
338
            echo fread($fm, min(1024 * 16, ($end - $cur) + 1));
339
            $cur += 1024 * 16;
340
        }
341
    }
342
343
    /**
344
     * This function streams a file to the client.
345
     *
346
     * @param string $full_file_name
347
     * @param bool   $forced              Whether to force the browser to download the file
348
     * @param string $name
349
     * @param bool   $fixLinksHttpToHttps change file content from http to https
350
     * @param array  $extraHeaders        Additional headers to be sent
351
     *
352
     * @return false if file doesn't exist, true if stream succeeded
353
     */
354
    public static function file_send_for_download(
355
        $full_file_name,
356
        $forced = false,
357
        $name = '',
358
        $fixLinksHttpToHttps = false,
359
        $extraHeaders = []
360
    ) {
361
        session_write_close(); //we do not need write access to session anymore
362
        if (!is_file($full_file_name)) {
363
            return false;
364
        }
365
        $filename = '' == $name ? basename($full_file_name) : api_replace_dangerous_char($name);
366
        $len = filesize($full_file_name);
367
        // Fixing error when file name contains a ","
368
        $filename = str_replace(',', '', $filename);
369
        $sendFileHeaders = ('true' === api_get_setting('document.enable_x_sendfile_headers'));
370
371
        // Allows chrome to make videos and audios seekable
372
        header('Accept-Ranges: bytes');
373
        if (!empty($extraHeaders)) {
374
            foreach ($extraHeaders as $name => $value) {
375
                //TODO: add restrictions to allowed headers?
376
                header($name.': '.$value);
377
            }
378
        }
379
380
        if ($forced) {
381
            // Force the browser to save the file instead of opening it
382
            if (isset($sendFileHeaders) &&
383
                !empty($sendFileHeaders)) {
384
                header("X-Sendfile: $filename");
385
            }
386
387
            header('Content-type: application/octet-stream');
388
            header('Content-length: '.$len);
389
            if (preg_match("/MSIE 5.5/", $_SERVER['HTTP_USER_AGENT'])) {
390
                header('Content-Disposition: filename= '.$filename);
391
            } else {
392
                header('Content-Disposition: attachment; filename= '.$filename);
393
            }
394
            if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
395
                header('Pragma: ');
396
                header('Cache-Control: ');
397
                header('Cache-Control: public'); // IE cannot download from sessions without a cache
398
            }
399
            header('Content-Description: '.$filename);
400
            header('Content-Transfer-Encoding: binary');
401
402
            if (function_exists('ob_end_clean') && ob_get_length()) {
403
                // Use ob_end_clean() to avoid weird buffering situations
404
                // where file is sent broken/incomplete for download
405
                ob_end_clean();
406
            }
407
408
            $res = fopen($full_file_name, 'r');
409
            fpassthru($res);
410
411
            return true;
412
        } else {
413
            // no forced download, just let the browser decide what to do according to the mimetype
414
            $lpFixedEncoding = 'true' === api_get_setting('lp.lp_fixed_encoding');
415
416
            // Commented to let courses content to be cached in order to improve performance:
417
            //header('Expires: Wed, 01 Jan 1990 00:00:00 GMT');
418
            //header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
419
420
            // Commented to avoid double caching declaration when playing with IE and HTTPS
421
            //header('Cache-Control: no-cache, must-revalidate');
422
            //header('Pragma: no-cache');
423
424
            $contentType = self::file_get_mime_type($filename);
425
426
            switch ($contentType) {
427
                case 'text/html':
428
                    if (isset($lpFixedEncoding) && 'true' === $lpFixedEncoding) {
429
                        $contentType .= '; charset=UTF-8';
430
                    } else {
431
                        $encoding = @api_detect_encoding_html(file_get_contents($full_file_name));
432
                        if (!empty($encoding)) {
433
                            $contentType .= '; charset='.$encoding;
434
                        }
435
                    }
436
                    break;
437
                case 'text/plain':
438
                    if (isset($lpFixedEncoding) && 'true' === $lpFixedEncoding) {
439
                        $contentType .= '; charset=UTF-8';
440
                    } else {
441
                        $encoding = @api_detect_encoding(strip_tags(file_get_contents($full_file_name)));
442
                        if (!empty($encoding)) {
443
                            $contentType .= '; charset='.$encoding;
444
                        }
445
                    }
446
                    break;
447
                case 'video/mp4':
448
                case 'audio/mpeg':
449
                case 'audio/mp4':
450
                case 'audio/ogg':
451
                case 'audio/webm':
452
                case 'audio/wav':
453
                case 'video/ogg':
454
                case 'video/webm':
455
                    self::smartReadFile($full_file_name, $filename, $contentType);
456
                    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...
457
                case 'application/vnd.dwg':
458
                case 'application/vnd.dwf':
459
                    header('Content-type: application/octet-stream');
460
                    break;
461
            }
462
463
            header('Content-type: '.$contentType);
464
            header('Content-Length: '.$len);
465
            $userAgent = strtolower($_SERVER['HTTP_USER_AGENT']);
466
467
            if (strpos($userAgent, 'msie')) {
468
                header('Content-Disposition: ; filename= '.$filename);
469
            } else {
470
                //header('Content-Disposition: inline');
471
                header('Content-Disposition: inline;');
472
            }
473
474
            if ($fixLinksHttpToHttps) {
475
                $content = file_get_contents($full_file_name);
476
                $content = str_replace(
477
                    ['http%3A%2F%2F', 'http://'],
478
                    ['https%3A%2F%2F', 'https://'],
479
                    $content
480
                );
481
                echo $content;
482
            } else {
483
                if (function_exists('ob_end_clean') && ob_get_length()) {
484
                    // Use ob_end_clean() to avoid weird buffering situations
485
                    // where file is sent broken/incomplete for download
486
                    ob_end_clean();
487
                }
488
489
                readfile($full_file_name);
490
            }
491
492
            return true;
493
        }
494
    }
495
496
    /**
497
     * Session folder filters.
498
     *
499
     * @param string $path
500
     * @param int    $sessionId
501
     *
502
     * @return string|null
503
     */
504
    public static function getSessionFolderFilters($path, $sessionId)
505
    {
506
        $sessionId = (int) $sessionId;
507
        $condition = null;
508
509
        if (!empty($sessionId)) {
510
            // Chat folder filter
511
            if ('/chat_files' == $path) {
512
                $condition .= " AND (docs.session_id = '$sessionId') ";
513
            }
514
        }
515
516
        return $condition;
517
    }
518
519
    /**
520
     * Gets the paths of all folders in a course
521
     * can show all folders (except for the deleted ones) or only visible ones.
522
     *
523
     * @param array  $courseInfo
524
     * @param int    $groupIid          iid
525
     * @param bool   $can_see_invisible
526
     * @param bool   $getInvisibleList
527
     * @param string $path              current path
528
     *
529
     * @return array with paths
530
     */
531
    public static function get_all_document_folders(
532
        $courseInfo,
533
        $groupIid = 0,
534
        $can_see_invisible = false,
535
        $getInvisibleList = false,
536
        $path = ''
537
    ) {
538
        if (empty($courseInfo)) {
539
            return [];
540
        }
541
542
        $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
543
        $groupIid = (int) $groupIid;
544
        $courseId = $courseInfo['real_id'];
545
        $sessionId = api_get_session_id();
546
547
        $folders = [];
548
        $students = CourseManager::get_user_list_from_course_code(
549
            $courseInfo['code'],
550
            api_get_session_id()
551
        );
552
553
        $conditionList = [];
554
        if (!empty($students)) {
555
            foreach ($students as $studentId => $studentInfo) {
556
                $conditionList[] = '/shared_folder/sf_user_'.$studentInfo['user_id'];
557
            }
558
        }
559
560
        $groupCondition = " l.group_id = $groupIid";
561
        if (empty($groupIid)) {
562
            $groupCondition = ' (l.group_id = 0 OR l.group_id IS NULL)';
563
        }
564
565
        $show_users_condition = '';
566
        if ($can_see_invisible) {
567
            $sessionId = $sessionId ?: api_get_session_id();
568
            $condition_session = " AND (l.session_id = '$sessionId' OR (l.session_id = '0' OR l.session_id IS NULL) )";
569
            $condition_session .= self::getSessionFolderFilters($path, $sessionId);
570
571
            $sql = "SELECT DISTINCT docs.iid, n.path
572
                    FROM resource_node AS n
573
                    INNER JOIN $TABLE_DOCUMENT AS docs
574
                    ON (docs.resource_node_id = n.id)
575
                    INNER JOIN resource_link l
576
                    ON (l.resource_node_id = n.id)
577
                    WHERE
578
                        l.c_id = $courseId AND
579
                        docs.filetype = 'folder' AND
580
                        $groupCondition AND
581
                        n.path NOT LIKE '%shared_folder%' AND
582
                        l.deleted_at IS NULL
583
                        $condition_session ";
584
585
            if (0 != $groupIid) {
586
                $sql .= " AND n.path NOT LIKE '%shared_folder%' ";
587
            } else {
588
                $sql .= $show_users_condition;
589
            }
590
591
            $result = Database::query($sql);
592
            if ($result && 0 != Database::num_rows($result)) {
593
                while ($row = Database::fetch_assoc($result)) {
594
                    if (self::is_folder_to_avoid($row['path'])) {
595
                        continue;
596
                    }
597
598
                    if (false !== strpos($row['path'], '/shared_folder/')) {
599
                        if (!in_array($row['path'], $conditionList)) {
600
                            continue;
601
                        }
602
                    }
603
604
                    $folders[$row['iid']] = $row['path'];
605
                }
606
607
                if (!empty($folders)) {
608
                    natsort($folders);
609
                }
610
611
                return $folders;
612
            }
613
614
            return false;
615
        } else {
616
            // No invisible folders
617
            // Condition for the session
618
            $condition_session = api_get_session_condition(
619
                $sessionId,
620
                true,
621
                false,
622
                'docs.session_id'
623
            );
624
625
            $visibilityCondition = 'l.visibility = 1';
626
            $fileType = "docs.filetype = 'folder' AND";
627
            if ($getInvisibleList) {
628
                $visibilityCondition = 'l.visibility = 0';
629
                $fileType = '';
630
            }
631
632
            //get visible folders
633
            $sql = "SELECT DISTINCT docs.id
634
                    FROM resource_node AS n
635
                    INNER JOIN $TABLE_DOCUMENT  AS docs
636
                    ON (docs.resource_node_id = n.id)
637
                    INNER JOIN resource_link l
638
                    ON (l.resource_node_id = n.id)
639
                    WHERE
640
                        $fileType
641
                        $groupCondition AND
642
                        $visibilityCondition
643
                        $show_users_condition
644
                        $condition_session AND
645
                        l.c_id = $courseId ";
646
            $result = Database::query($sql);
647
            $visibleFolders = [];
648
            while ($row = Database::fetch_assoc($result)) {
649
                $visibleFolders[$row['id']] = $row['path'];
650
            }
651
652
            if ($getInvisibleList) {
653
                return $visibleFolders;
654
            }
655
656
            // get invisible folders
657
            $sql = "SELECT DISTINCT docs.iid, n.path
658
                    FROM resource_node AS n
659
                    INNER JOIN $TABLE_DOCUMENT  AS docs
660
                    ON (docs.resource_node_id = n.id)
661
                    INNER JOIN resource_link l
662
                    ON (l.resource_node_id = n.id)
663
                    WHERE
664
                        docs.filetype = 'folder' AND
665
                        $groupCondition AND
666
                        l.visibility IN ('".ResourceLink::VISIBILITY_PENDING."')
667
                        $condition_session AND
668
                        l.c_id = $courseId ";
669
            $result = Database::query($sql);
670
            $invisibleFolders = [];
671
            while ($row = Database::fetch_assoc($result)) {
672
                //get visible folders in the invisible ones -> they are invisible too
673
                $sql = "SELECT DISTINCT docs.iid, n.path
674
                        FROM resource_node AS n
675
                        INNER JOIN $TABLE_DOCUMENT  AS docs
676
                        ON (docs.resource_node_id = n.id)
677
                        INNER JOIN resource_link l
678
                        ON (l.resource_node_id = n.id)
679
                        WHERE
680
                            docs.filetype = 'folder' AND
681
                            $groupCondition AND
682
                            l.deleted_at IS NULL
683
                            $condition_session AND
684
                            l.c_id = $courseId ";
685
                $folder_in_invisible_result = Database::query($sql);
686
                while ($folders_in_invisible_folder = Database::fetch_assoc($folder_in_invisible_result)) {
687
                    $invisibleFolders[$folders_in_invisible_folder['id']] = $folders_in_invisible_folder['path'];
688
                }
689
            }
690
691
            // If both results are arrays -> //calculate the difference between the 2 arrays -> only visible folders are left :)
692
            if (is_array($visibleFolders) && is_array($invisibleFolders)) {
693
                $folders = array_diff($visibleFolders, $invisibleFolders);
694
                natsort($folders);
695
696
                return $folders;
697
            }
698
699
            if (is_array($visibleFolders)) {
700
                natsort($visibleFolders);
701
702
                return $visibleFolders;
703
            }
704
705
            // no visible folders found
706
            return false;
707
        }
708
    }
709
710
    /**
711
     * This deletes a document by changing visibility to 2, renaming it to filename_DELETED_#id
712
     * Files/folders that are inside a deleted folder get visibility 2.
713
     *
714
     * @param array  $_course
715
     * @param string $path          Path stored in the database
716
     * @param string $base_work_dir Path to the documents folder (if not defined, $documentId must be used)
717
     * @param int    $sessionId     The ID of the session, if any
718
     * @param int    $documentId    The document id, if available
719
     * @param int    $groupId       iid
720
     *
721
     * @return bool true/false
722
     *
723
     * @todo now only files/folders in a folder get visibility 2, we should rename them too.
724
     * @todo We should be able to get rid of this later when using only documentId (check further usage)
725
     */
726
    public static function delete_document(
727
        $_course,
728
        $path = null,
729
        $base_work_dir = null,
730
        $sessionId = null,
731
        $documentId = null,
732
        $groupId = 0
733
    ) {
734
        $groupId = (int) $groupId;
735
        if (empty($groupId)) {
736
            $groupId = api_get_group_id();
737
        }
738
739
        $sessionId = (int) $sessionId;
740
        if (empty($sessionId)) {
741
            $sessionId = api_get_session_id();
742
        }
743
744
        $course_id = $_course['real_id'];
745
746
        if (empty($course_id)) {
747
            return false;
748
        }
749
750
        if (empty($documentId)) {
751
            $documentId = self::get_document_id($_course, $path, $sessionId);
752
            $docInfo = self::get_document_data_by_id(
753
                $documentId,
754
                $_course['code'],
755
                false,
756
                $sessionId
757
            );
758
            $path = $docInfo['path'];
759
        } else {
760
            $docInfo = self::get_document_data_by_id(
761
                $documentId,
762
                $_course['code'],
763
                false,
764
                $sessionId
765
            );
766
            if (empty($docInfo)) {
767
                return false;
768
            }
769
            $path = $docInfo['path'];
770
        }
771
772
        $documentId = (int) $documentId;
773
774
        if (empty($path) || empty($docInfo) || empty($documentId)) {
775
            return false;
776
        }
777
778
        $repo = Container::getDocumentRepository();
779
        $document = $repo->find($docInfo['iid']);
780
        if ($document) {
781
            $repo->hardDelete($document);
782
783
            return true;
784
        }
785
786
        return false;
787
    }
788
789
    /**
790
     * Removes documents from search engine database.
791
     *
792
     * @param string $course_id   Course code
793
     * @param int    $document_id Document id to delete
794
     */
795
    public static function delete_document_from_search_engine($course_id, $document_id)
796
    {
797
        // remove from search engine if enabled
798
        if ('true' === api_get_setting('search_enabled')) {
799
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
800
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
801
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_DOCUMENT, $document_id);
802
            $res = Database::query($sql);
803
            if (Database::num_rows($res) > 0) {
804
                $row2 = Database::fetch_array($res);
805
                $di = new ChamiloIndexer();
806
                $di->remove_document($row2['search_did']);
807
            }
808
            $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
809
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_DOCUMENT, $document_id);
810
            Database::query($sql);
811
812
            // remove terms from db
813
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
814
            delete_all_values_for_item($course_id, TOOL_DOCUMENT, $document_id);
815
        }
816
    }
817
818
    /**
819
     * Gets the id of a document with a given path.
820
     *
821
     * @param array  $courseInfo
822
     * @param string $path
823
     * @param int    $sessionId
824
     *
825
     * @return int id of document / false if no doc found
826
     */
827
    public static function get_document_id($courseInfo, $path, $sessionId = 0)
828
    {
829
        $table = Database::get_course_table(TABLE_DOCUMENT);
830
        $courseId = $courseInfo['real_id'];
831
832
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
833
        $sessionCondition = api_get_session_condition($sessionId, true);
834
835
        $path = Database::escape_string($path);
836
        if (!empty($courseId) && !empty($path)) {
837
            $sql = "SELECT iid FROM $table
838
                    WHERE
839
                        c_id = $courseId AND
840
                        path LIKE BINARY '$path'
841
                        $sessionCondition
842
                    LIMIT 1";
843
844
            $result = Database::query($sql);
845
            if (Database::num_rows($result)) {
846
                $row = Database::fetch_array($result);
847
848
                return (int) $row['iid'];
849
            }
850
        }
851
852
        return false;
853
    }
854
855
    /**
856
     * Gets the document data with a given id.
857
     *
858
     * @param int    $id            Document Id (id field in c_document table)
859
     * @param string $course_code   Course code
860
     * @param bool   $load_parents  load folder parents
861
     * @param int    $session_id    The session ID,
862
     *                              0 if requires context *out of* session, and null to use global context
863
     * @param bool   $ignoreDeleted
864
     *
865
     * @deprecated  use $repo->find()
866
     *
867
     * @return array document content
868
     */
869
    public static function get_document_data_by_id(
870
        int    $id,
871
        string $course_code,
872
        bool   $load_parents = false,
873
        int    $session_id = null,
874
        bool $ignoreDeleted = false
875
    ): bool|array {
876
        $course_info = api_get_course_info($course_code);
877
        if (empty($course_info)) {
878
            return false;
879
        }
880
881
        $course_id = $course_info['real_id'];
882
        $session_id = empty($session_id) ? api_get_session_id() : (int) $session_id;
883
        $groupId = api_get_group_id();
884
885
        $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
886
        $id = (int) $id;
887
888
        $sql = "SELECT * FROM $TABLE_DOCUMENT WHERE iid = $id";
889
        $result = Database::query($sql);
890
        if ($result && 1 == Database::num_rows($result)) {
891
            $row = Database::fetch_assoc($result);
892
893
            // Adjust paths for URLs based on new system
894
            $row['url'] = api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id;
895
            $row['document_url'] = api_get_path(WEB_CODE_PATH).'document/document.php?id='.$id;
896
            // Consider storing a relative path or identifier in the database to construct paths
897
            $row['basename'] = $id;  // You may store and use titles or another unique identifier
898
899
            // Handling the parent ID should be adjusted if the path isn't available
900
            $row['parent_id'] = $row['parent_resource_node_id'] ?? '0';  // Adjust according to your schema
901
902
            $parents = [];
903
            if ($load_parents) {
904
                // Modify this logic to work with parent IDs stored directly in the database
905
                $current_id = $row['parent_id'];
906
                while ($current_id != '0') {
907
                    $parent_data = self::get_document_data_by_id($current_id, $course_code, false, $session_id);
908
                    if ($parent_data) {
909
                        $parents[] = $parent_data;
910
                        $current_id = $parent_data['parent_id'] ?? '0';
911
                    } else {
912
                        break;
913
                    }
914
                }
915
            }
916
            $row['parents'] = array_reverse($parents);
917
918
            return $row;
919
        }
920
921
        return false;
922
    }
923
924
    /**
925
     * Allow to set a specific document as a new template for CKeditor
926
     * for a particular user in a particular course.
927
     *
928
     * @param string $title
929
     * @param string $description
930
     * @param int    $document_id_for_template the document id
931
     * @param int    $courseId
932
     * @param int    $user_id
933
     * @param string $image
934
     *
935
     * @return bool
936
     */
937
    public static function setDocumentAsTemplate(
938
        $title,
939
        $description,
940
        $document_id_for_template,
941
        $courseId,
942
        $user_id,
943
        $image
944
    ) {
945
        // Database table definition
946
        $table_template = Database::get_main_table(TABLE_MAIN_TEMPLATES);
947
        $params = [
948
            'title' => $title,
949
            'description' => $description,
950
            'c_id' => $courseId,
951
            'user_id' => $user_id,
952
            'ref_doc' => $document_id_for_template,
953
            'image' => $image,
954
        ];
955
        Database::insert($table_template, $params);
956
957
        return true;
958
    }
959
960
    /**
961
     * Check document visibility.
962
     *
963
     * @param string $doc_path the relative complete path of the document
964
     * @param array  $course   the _course array info of the document's course
965
     * @param int
966
     * @param string
967
     *
968
     * @return bool
969
     */
970
    public static function is_visible(
971
        $doc_path,
972
        $course,
973
        $session_id = 0,
974
        $file_type = 'file'
975
    ) {
976
        $docTable = Database::get_course_table(TABLE_DOCUMENT);
977
978
        $course_id = $course['real_id'];
979
        // note the extra / at the end of doc_path to match every path in
980
        // the document table that is part of the document path
981
        $session_id = (int) $session_id;
982
        $condition = " AND d.session_id IN  ('$session_id', '0') ";
983
        // The " d.filetype='file' " let the user see a file even if the folder is hidden see #2198
984
985
        /*
986
          When using hotpotatoes files, a new html files are generated
987
          in the hotpotatoes folder to display the test.
988
          The genuine html file is copied to math4.htm(user_id).t.html
989
          Images files are not copied, and keep same name.
990
          To check the html file visibility, we don't have to check file math4.htm(user_id).t.html but file math4.htm
991
          In this case, we have to remove (user_id).t.html to check the visibility of the file
992
          For images, we just check the path of the image file.
993
994
          Exemple of hotpotatoes folder :
995
          A.jpg
996
          maths4-consigne.jpg
997
          maths4.htm
998
          maths4.htm1.t.html
999
          maths4.htm52.t.html
1000
          maths4.htm654.t.html
1001
          omega.jpg
1002
          theta.jpg
1003
         */
1004
1005
        if (strpos($doc_path, 'HotPotatoes_files') && preg_match("/\.t\.html$/", $doc_path)) {
1006
            $doc_path = substr($doc_path, 0, strlen($doc_path) - 7 - strlen(api_get_user_id()));
1007
        }
1008
1009
        if (!in_array($file_type, ['file', 'folder'])) {
1010
            $file_type = 'file';
1011
        }
1012
        $doc_path = Database::escape_string($doc_path).'/';
1013
1014
        $sql = "SELECT iid
1015
                FROM $docTable d
1016
        		WHERE
1017
        		    d.c_id  = $course_id AND
1018
        		    $condition AND
1019
        			filetype = '$file_type' AND
1020
        			locate(concat(path,'/'), '$doc_path')=1
1021
                ";
1022
1023
        $result = Database::query($sql);
1024
        $is_visible = false;
1025
        if (Database::num_rows($result) > 0) {
1026
            $row = Database::fetch_assoc($result);
1027
1028
            $em = Database::getManager();
1029
1030
            $repo = $em->getRepository(CDocument::class);
1031
            /** @var CDocument $document */
1032
            $document = $repo->find($row['iid']);
1033
            if (ResourceLink::VISIBILITY_PUBLISHED === $document->getVisibility()) {
1034
                $is_visible = api_is_allowed_in_course() || api_is_platform_admin();
1035
            }
1036
        }
1037
1038
        /* improved protection of documents viewable directly through the url:
1039
            incorporates the same protections of the course at the url of
1040
            documents:
1041
            access allowed for the whole world Open, access allowed for
1042
            users registered on the platform Private access, document accessible
1043
            only to course members (see the Users list), Completely closed;
1044
            the document is only accessible to the course admin and
1045
            teaching assistants.*/
1046
        //return $_SESSION ['is_allowed_in_course'] || api_is_platform_admin();
1047
        return $is_visible;
1048
    }
1049
1050
    /**
1051
     * Allow attach a certificate to a course.
1052
     *
1053
     * @todo move to certificate.lib.php
1054
     *
1055
     * @param int $courseId
1056
     * @param int $document_id
1057
     * @param int $sessionId
1058
     */
1059
    public static function attach_gradebook_certificate($courseId, $document_id, $sessionId = 0)
1060
    {
1061
        $tbl_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1062
        $sessionId = intval($sessionId);
1063
        $courseId = (int) $courseId;
1064
        if (empty($sessionId)) {
1065
            $sessionId = api_get_session_id();
1066
        }
1067
1068
        if (empty($sessionId)) {
1069
            $sql_session = 'AND (session_id = 0 OR isnull(session_id)) ';
1070
        } elseif ($sessionId > 0) {
1071
            $sql_session = 'AND session_id='.$sessionId;
1072
        } else {
1073
            $sql_session = '';
1074
        }
1075
        $sql = 'UPDATE '.$tbl_category.' SET document_id="'.intval($document_id).'"
1076
                WHERE c_id ="'.$courseId.'" '.$sql_session;
1077
        Database::query($sql);
1078
    }
1079
1080
    /**
1081
     * get the document id of default certificate.
1082
     *
1083
     * @todo move to certificate.lib.php
1084
     *
1085
     * @param int $courseId
1086
     * @param int $session_id
1087
     *
1088
     * @return int The default certificate id
1089
     */
1090
    public static function get_default_certificate_id($courseId, $session_id = 0)
1091
    {
1092
        $tbl_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1093
        $session_id = (int) $session_id;
1094
        $courseId = (int) $courseId;
1095
        if (empty($session_id)) {
1096
            $session_id = api_get_session_id();
1097
        }
1098
1099
        if (empty($session_id)) {
1100
            $sql_session = 'AND (session_id = 0 OR isnull(session_id)) ';
1101
        } elseif ($session_id > 0) {
1102
            $sql_session = 'AND session_id='.$session_id;
1103
        } else {
1104
            $sql_session = '';
1105
        }
1106
1107
        $sql = 'SELECT document_id FROM '.$tbl_category.'
1108
                WHERE c_id ="'.$courseId.'" '.$sql_session;
1109
1110
        $rs = Database::query($sql);
1111
        $num = Database::num_rows($rs);
1112
        if (0 == $num) {
1113
            return null;
1114
        }
1115
        $row = Database::fetch_array($rs);
1116
1117
        return $row['document_id'];
1118
    }
1119
1120
    /**
1121
     * Allow replace user info in file html.
1122
     *
1123
     * @param int   $user_id
1124
     * @param array $courseInfo
1125
     * @param int   $sessionId
1126
     * @param bool  $is_preview
1127
     *
1128
     * @return array
1129
     */
1130
    public static function replace_user_info_into_html(
1131
        $user_id,
1132
        $courseInfo,
1133
        $sessionId,
1134
        $is_preview = false
1135
    ) {
1136
        $user_id = (int) $user_id;
1137
        $course_id = $courseInfo['real_id'];
1138
        $document_id = self::get_default_certificate_id($course_id, $sessionId);
1139
1140
        $my_content_html = null;
1141
        if ($document_id) {
1142
            $repo = Container::getDocumentRepository();
1143
            $doc = $repo->find($document_id);
1144
            $new_content = '';
1145
            $all_user_info = [];
1146
            if ($doc) {
1147
                try {
1148
                    // Validate if the document content is not empty
1149
                    $my_content_html = $repo->getResourceFileContent($doc);
1150
                    if (empty($my_content_html)) {
1151
                        throw new Exception("The document content is empty.");
1152
                    }
1153
1154
                    // Retrieve user information for the certificate
1155
                    $all_user_info = self::get_all_info_to_certificate(
1156
                        $user_id,
1157
                        $course_id,
1158
                        $is_preview
1159
                    );
1160
1161
                    // Ensure user info array is properly structured
1162
                    if (!isset($all_user_info[0]) || !isset($all_user_info[1])) {
1163
                        throw new Exception("Error retrieving user information for the certificate.");
1164
                    }
1165
1166
                    $info_to_be_replaced_in_content_html = $all_user_info[0];
1167
                    $info_to_replace_in_content_html = $all_user_info[1];
1168
1169
                    // Replace placeholders in the certificate template with user info
1170
                    $new_content = str_replace(
1171
                        $info_to_be_replaced_in_content_html,
1172
                        $info_to_replace_in_content_html,
1173
                        $my_content_html
1174
                    );
1175
                } catch (Exception $e) {
1176
                    error_log("Error in replace_user_info_into_html: " . $e->getMessage());
1177
                    return [
1178
                        'content' => '',
1179
                        'variables' => [],
1180
                    ];
1181
                }
1182
            }
1183
1184
            return [
1185
                'content' => $new_content,
1186
                'variables' => $all_user_info,
1187
            ];
1188
        }
1189
1190
        return [];
1191
    }
1192
1193
    /**
1194
     * Return all content to replace and all content to be replace.
1195
     *
1196
     * @param int  $user_id
1197
     * @param bool $is_preview
1198
     *
1199
     * @return array
1200
     */
1201
    public static function get_all_info_to_certificate($user_id, $courseId, $sessionId, $is_preview = false)
1202
    {
1203
        $info_list = [];
1204
        $user_id = (int) $user_id;
1205
        $sessionId = (int) $sessionId;
1206
        $course_info = api_get_course_info_by_id($courseId);
1207
        $courseCode = $course_info['code'];
1208
1209
        // Portal info
1210
        $organization_name = api_get_setting('Institution');
1211
        $portal_name = api_get_setting('siteName');
1212
1213
        // Extra user data information
1214
        $extra_user_info_data = UserManager::get_extra_user_data(
1215
            $user_id,
1216
            false,
1217
            false,
1218
            false,
1219
            true
1220
        );
1221
1222
        // get extra fields
1223
        $extraField = new ExtraField('user');
1224
        $extraFields = $extraField->get_all(['filter = ? AND visible_to_self = ?' => [1, 1]]);
1225
1226
        // Student information
1227
        $user_info = api_get_user_info($user_id);
1228
        $first_name = $user_info['firstname'];
1229
        $last_name = $user_info['lastname'];
1230
        $username = $user_info['username'];
1231
        $official_code = $user_info['official_code'];
1232
1233
        // Teacher information
1234
        $info_teacher_id = UserManager::get_user_id_of_course_admin_or_session_admin($course_info);
1235
        $teacher_info = api_get_user_info($info_teacher_id);
1236
        $teacher_first_name = $teacher_info['firstname'];
1237
        $teacher_last_name = $teacher_info['lastname'];
1238
1239
        // info gradebook certificate
1240
        $info_grade_certificate = UserManager::get_info_gradebook_certificate($course_info, $sessionId, $user_id);
1241
        $date_long_certificate = '';
1242
        $date_certificate = '';
1243
        $url = '';
1244
        if ($info_grade_certificate) {
1245
            $date_certificate = $info_grade_certificate['created_at'];
1246
            if (!empty($info_grade_certificate['path_certificate'])) {
1247
                $hash = pathinfo($info_grade_certificate['path_certificate'], PATHINFO_FILENAME);
1248
                $url = api_get_path(WEB_PATH) . 'certificates/' . $hash . '.html';
1249
            }
1250
        }
1251
        $date_no_time = api_convert_and_format_date(api_get_utc_datetime(), DATE_FORMAT_LONG_NO_DAY);
1252
        if (!empty($date_certificate)) {
1253
            $date_long_certificate = api_convert_and_format_date($date_certificate);
1254
            $date_no_time = api_convert_and_format_date($date_certificate, DATE_FORMAT_LONG_NO_DAY);
1255
        }
1256
1257
        if ($is_preview) {
1258
            $date_long_certificate = api_convert_and_format_date(api_get_utc_datetime());
1259
            $date_no_time = api_convert_and_format_date(api_get_utc_datetime(), DATE_FORMAT_LONG_NO_DAY);
1260
        }
1261
1262
        $externalStyleFile = api_get_path(SYS_CSS_PATH).'themes/'.api_get_visual_theme().'/certificate.css';
1263
        $externalStyle = '';
1264
        if (is_file($externalStyleFile)) {
1265
            $externalStyle = file_get_contents($externalStyleFile);
1266
        }
1267
        $timeInCourse = Tracking::get_time_spent_on_the_course($user_id, $course_info['real_id'], $sessionId);
1268
        $timeInCourse = api_time_to_hms($timeInCourse, ':', false, true);
1269
1270
        $timeInCourseInAllSessions = 0;
1271
        $sessions = SessionManager::get_session_by_course($course_info['real_id']);
1272
1273
        if (!empty($sessions)) {
1274
            foreach ($sessions as $session) {
1275
                $timeInCourseInAllSessions += Tracking::get_time_spent_on_the_course($user_id, $course_info['real_id'], $session['id']);
1276
            }
1277
        }
1278
        $timeInCourseInAllSessions = api_time_to_hms($timeInCourseInAllSessions, ':', false, true);
1279
1280
        $first = Tracking::get_first_connection_date_on_the_course($user_id, $course_info['real_id'], $sessionId, false);
1281
        $first = substr($first, 0, 10);
1282
        $last = Tracking::get_last_connection_date_on_the_course($user_id, $course_info, $sessionId, false);
1283
        $last = substr($last, 0, 10);
1284
1285
        if ($first === $last) {
1286
            $startDateAndEndDate = get_lang('From').' '.$first;
1287
        } else {
1288
            $startDateAndEndDate = sprintf(
1289
                get_lang('From %s to %s'),
1290
                $first,
1291
                $last
1292
            );
1293
        }
1294
        $courseDescription = new CourseDescription();
1295
        $description = $courseDescription->get_data_by_description_type(2, $course_info['real_id'], $sessionId);
1296
        $courseObjectives = '';
1297
        if ($description) {
1298
            $courseObjectives = $description['description_content'];
1299
        }
1300
1301
        // Replace content
1302
        $info_to_replace_in_content_html = [
1303
            $first_name,
1304
            $last_name,
1305
            $username,
1306
            $organization_name,
1307
            $portal_name,
1308
            $teacher_first_name,
1309
            $teacher_last_name,
1310
            $official_code,
1311
            $date_long_certificate,
1312
            $date_no_time,
1313
            $course_info['code'],
1314
            $course_info['name'],
1315
            isset($info_grade_certificate['grade']) ? $info_grade_certificate['grade'] : '',
1316
            $url,
1317
            '<a href="'.$url.'" target="_blank">'.get_lang('Online link to certificate').'</a>',
1318
            '((certificate_barcode))',
1319
            $externalStyle,
1320
            $timeInCourse,
1321
            $timeInCourseInAllSessions,
1322
            $startDateAndEndDate,
1323
            $courseObjectives,
1324
        ];
1325
1326
        $tags = [
1327
            '((user_firstname))',
1328
            '((user_lastname))',
1329
            '((user_username))',
1330
            '((gradebook_institution))',
1331
            '((gradebook_sitename))',
1332
            '((teacher_firstname))',
1333
            '((teacher_lastname))',
1334
            '((official_code))',
1335
            '((date_certificate))',
1336
            '((date_certificate_no_time))',
1337
            '((course_code))',
1338
            '((course_title))',
1339
            '((gradebook_grade))',
1340
            '((certificate_link))',
1341
            '((certificate_link_html))',
1342
            '((certificate_barcode))',
1343
            '((external_style))',
1344
            '((time_in_course))',
1345
            '((time_in_course_in_all_sessions))',
1346
            '((start_date_and_end_date))',
1347
            '((course_objectives))',
1348
        ];
1349
1350
        if (!empty($extraFields)) {
1351
            foreach ($extraFields as $extraField) {
1352
                $valueExtra = isset($extra_user_info_data[$extraField['variable']]) ? $extra_user_info_data[$extraField['variable']] : '';
1353
                $tags[] = '(('.strtolower($extraField['variable']).'))';
1354
                $info_to_replace_in_content_html[] = $valueExtra;
1355
            }
1356
        }
1357
1358
        $info_list[] = $tags;
1359
        $info_list[] = $info_to_replace_in_content_html;
1360
1361
        return $info_list;
1362
    }
1363
1364
    /**
1365
     * Remove default certificate.
1366
     *
1367
     * @param int $course_id              The course code
1368
     * @param int $default_certificate_id The document id of the default certificate
1369
     */
1370
    public static function remove_attach_certificate($course_id, $default_certificate_id)
1371
    {
1372
        if (empty($default_certificate_id)) {
1373
            return false;
1374
        }
1375
1376
        $default_certificate = self::get_default_certificate_id($course_id);
1377
        if ((int) $default_certificate == (int) $default_certificate_id) {
1378
            $tbl_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1379
            $session_id = api_get_session_id();
1380
            if (0 == $session_id || is_null($session_id)) {
1381
                $sql_session = 'AND (session_id='.intval($session_id).' OR isnull(session_id)) ';
1382
            } elseif ($session_id > 0) {
1383
                $sql_session = 'AND session_id='.intval($session_id);
1384
            } else {
1385
                $sql_session = '';
1386
            }
1387
1388
            $sql = 'UPDATE '.$tbl_category.' SET document_id = null
1389
                    WHERE
1390
                        c_id = "'.Database::escape_string($course_id).'" AND
1391
                        document_id="'.$default_certificate_id.'" '.$sql_session;
1392
            Database::query($sql);
1393
        }
1394
    }
1395
1396
    /**
1397
     * Create directory certificate.
1398
     *
1399
     * @param array $courseInfo
1400
     */
1401
    public static function create_directory_certificate_in_course($courseInfo)
1402
    {
1403
        if (!empty($courseInfo)) {
1404
            $dir_name = '/certificates';
1405
            $post_dir_name = get_lang('Certificates');
1406
            $id = self::get_document_id_of_directory_certificate();
1407
            if (empty($id)) {
1408
                create_unexisting_directory(
1409
                    $courseInfo,
1410
                    api_get_user_id(),
1411
                    api_get_session_id(),
1412
                    0,
1413
                    0,
1414
                    '',
1415
                    $dir_name,
1416
                    $post_dir_name,
1417
                    null,
1418
                    false,
1419
                    false
1420
                );
1421
            }
1422
        }
1423
    }
1424
1425
    /**
1426
     * Get the document id of the directory certificate.
1427
     *
1428
     * @return int The document id of the directory certificate
1429
     *
1430
     * @todo move to certificate.lib.php
1431
     */
1432
    public static function get_document_id_of_directory_certificate()
1433
    {
1434
        $tbl_document = Database::get_course_table(TABLE_DOCUMENT);
1435
        $course_id = api_get_course_int_id();
1436
        $sql = "SELECT id FROM $tbl_document
1437
                WHERE c_id = $course_id AND path='/certificates' ";
1438
        $rs = Database::query($sql);
1439
        $row = Database::fetch_array($rs);
1440
1441
        return $row['id'];
1442
    }
1443
1444
    /**
1445
     * Check if a directory given is for certificate.
1446
     *
1447
     * @todo move to certificate.lib.php
1448
     *
1449
     * @param string $dir path of directory
1450
     *
1451
     * @return bool true if is a certificate or false otherwise
1452
     */
1453
    public static function is_certificate_mode($dir)
1454
    {
1455
        // I'm in the certification module?
1456
        $is_certificate_mode = false;
1457
        $is_certificate_array = explode('/', $dir);
1458
        array_shift($is_certificate_array);
1459
        if (isset($is_certificate_array[0]) && 'certificates' == $is_certificate_array[0]) {
1460
            $is_certificate_mode = true;
1461
        }
1462
1463
        return $is_certificate_mode || (isset($_GET['certificate']) && 'true' === $_GET['certificate']);
1464
    }
1465
1466
    /**
1467
     * Gets the list of included resources as a list of absolute or relative paths from a html file or string html
1468
     * This allows for a better SCORM export or replace urls inside content html from copy course
1469
     * The list will generally include pictures, flash objects, java applets, or any other
1470
     * stuff included in the source of the current item. The current item is expected
1471
     * to be an HTML file or string html. If it is not, then the function will return and empty list.
1472
     *
1473
     * @param    string  source html (content or path)
1474
     * @param    bool    is file or string html
1475
     * @param    string    type (one of the app tools) - optional (otherwise takes the current item's type)
1476
     * @param    int        level of recursivity we're in
1477
     *
1478
     * @return array List of file paths. An additional field containing 'local' or 'remote' helps determine
1479
     *               if the file should be copied into the zip or just linked
1480
     */
1481
    public static function get_resources_from_source_html(
1482
        $source_html,
1483
        $is_file = false,
1484
        $type = null,
1485
        $recursivity = 1
1486
    ) {
1487
        $max = 5;
1488
        $attributes = [];
1489
        $wanted_attributes = [
1490
            'src',
1491
            'url',
1492
            '@import',
1493
            'href',
1494
            'value',
1495
            'flashvars',
1496
            'poster',
1497
        ];
1498
        $explode_attributes = ['flashvars' => 'file'];
1499
        $abs_path = '';
1500
1501
        if ($recursivity > $max) {
1502
            return [];
1503
        }
1504
1505
        if (!isset($type)) {
1506
            $type = TOOL_DOCUMENT;
1507
        }
1508
1509
        if (!$is_file) {
1510
            $attributes = self::parse_HTML_attributes(
1511
                $source_html,
1512
                $wanted_attributes,
1513
                $explode_attributes
1514
            );
1515
        } else {
1516
            if (is_file($source_html)) {
1517
                $abs_path = $source_html;
1518
                //for now, read the whole file in one go (that's gonna be a problem when the file is too big)
1519
                $info = pathinfo($abs_path);
1520
                $ext = $info['extension'];
1521
                switch (strtolower($ext)) {
1522
                    case 'html':
1523
                    case 'htm':
1524
                    case 'shtml':
1525
                    case 'css':
1526
                        $file_content = file_get_contents($abs_path);
1527
                        // get an array of attributes from the HTML source
1528
                        $attributes = self::parse_HTML_attributes(
1529
                            $file_content,
1530
                            $wanted_attributes,
1531
                            $explode_attributes
1532
                        );
1533
                        break;
1534
                    default:
1535
                        break;
1536
                }
1537
            } else {
1538
                return [];
1539
            }
1540
        }
1541
1542
        $files_list = [];
1543
        switch ($type) {
1544
            case TOOL_DOCUMENT:
1545
            case TOOL_QUIZ:
1546
            case 'sco':
1547
                foreach ($wanted_attributes as $attr) {
1548
                    if (isset($attributes[$attr])) {
1549
                        //find which kind of path these are (local or remote)
1550
                        $sources = $attributes[$attr];
1551
                        foreach ($sources as $source) {
1552
                            //skip what is obviously not a resource
1553
                            if (strpos($source, '+this.')) {
1554
                                continue; //javascript code - will still work unaltered
1555
                            }
1556
                            if (false === strpos($source, '.')) {
1557
                                continue; //no dot, should not be an external file anyway
1558
                            }
1559
                            if (strpos($source, 'mailto:')) {
1560
                                continue; //mailto link
1561
                            }
1562
                            if (strpos($source, ';') && !strpos($source, '&amp;')) {
1563
                                continue; //avoid code - that should help
1564
                            }
1565
1566
                            if ('value' == $attr) {
1567
                                if (strpos($source, 'mp3file')) {
1568
                                    $files_list[] = [
1569
                                        substr($source, 0, strpos($source, '.swf') + 4),
1570
                                        'local',
1571
                                        'abs',
1572
                                    ];
1573
                                    $mp3file = substr($source, strpos($source, 'mp3file=') + 8);
1574
                                    if ('/' == substr($mp3file, 0, 1)) {
1575
                                        $files_list[] = [$mp3file, 'local', 'abs'];
1576
                                    } else {
1577
                                        $files_list[] = [$mp3file, 'local', 'rel'];
1578
                                    }
1579
                                } elseif (0 === strpos($source, 'flv=')) {
1580
                                    $source = substr($source, 4);
1581
                                    if (strpos($source, '&') > 0) {
1582
                                        $source = substr($source, 0, strpos($source, '&'));
1583
                                    }
1584
                                    if (strpos($source, '://') > 0) {
1585
                                        if (false !== strpos($source, api_get_path(WEB_PATH))) {
1586
                                            //we found the current portal url
1587
                                            $files_list[] = [$source, 'local', 'url'];
1588
                                        } else {
1589
                                            //we didn't find any trace of current portal
1590
                                            $files_list[] = [$source, 'remote', 'url'];
1591
                                        }
1592
                                    } else {
1593
                                        $files_list[] = [$source, 'local', 'abs'];
1594
                                    }
1595
                                    /* skipping anything else to avoid two entries
1596
                                    (while the others can have sub-files in their url, flv's can't)*/
1597
                                    continue;
1598
                                }
1599
                            }
1600
                            if (strpos($source, '://') > 0) {
1601
                                //cut at '?' in a URL with params
1602
                                if (strpos($source, '?') > 0) {
1603
                                    $second_part = substr($source, strpos($source, '?'));
1604
                                    if (strpos($second_part, '://') > 0) {
1605
                                        //if the second part of the url contains a url too, treat the second one before cutting
1606
                                        $pos1 = strpos($second_part, '=');
1607
                                        $pos2 = strpos($second_part, '&');
1608
                                        $second_part = substr($second_part, $pos1 + 1, $pos2 - ($pos1 + 1));
1609
                                        if (false !== strpos($second_part, api_get_path(WEB_PATH))) {
1610
                                            //we found the current portal url
1611
                                            $files_list[] = [$second_part, 'local', 'url'];
1612
                                            $in_files_list[] = self::get_resources_from_source_html(
1613
                                                $second_part,
1614
                                                true,
1615
                                                TOOL_DOCUMENT,
1616
                                                $recursivity + 1
1617
                                            );
1618
                                            if (count($in_files_list) > 0) {
1619
                                                $files_list = array_merge($files_list, $in_files_list);
1620
                                            }
1621
                                        } else {
1622
                                            //we didn't find any trace of current portal
1623
                                            $files_list[] = [$second_part, 'remote', 'url'];
1624
                                        }
1625
                                    } elseif (strpos($second_part, '=') > 0) {
1626
                                        if ('/' === substr($second_part, 0, 1)) {
1627
                                            //link starts with a /, making it absolute (relative to DocumentRoot)
1628
                                            $files_list[] = [$second_part, 'local', 'abs'];
1629
                                            $in_files_list[] = self::get_resources_from_source_html(
1630
                                                $second_part,
1631
                                                true,
1632
                                                TOOL_DOCUMENT,
1633
                                                $recursivity + 1
1634
                                            );
1635
                                            if (count($in_files_list) > 0) {
1636
                                                $files_list = array_merge($files_list, $in_files_list);
1637
                                            }
1638
                                        } elseif (0 === strstr($second_part, '..')) {
1639
                                            //link is relative but going back in the hierarchy
1640
                                            $files_list[] = [$second_part, 'local', 'rel'];
1641
                                            //$dir = api_get_path(SYS_CODE_PATH);//dirname($abs_path);
1642
                                            //$new_abs_path = realpath($dir.'/'.$second_part);
1643
                                            $dir = '';
1644
                                            if (!empty($abs_path)) {
1645
                                                $dir = dirname($abs_path).'/';
1646
                                            }
1647
                                            $new_abs_path = realpath($dir.$second_part);
1648
                                            $in_files_list[] = self::get_resources_from_source_html(
1649
                                                $new_abs_path,
1650
                                                true,
1651
                                                TOOL_DOCUMENT,
1652
                                                $recursivity + 1
1653
                                            );
1654
                                            if (count($in_files_list) > 0) {
1655
                                                $files_list = array_merge($files_list, $in_files_list);
1656
                                            }
1657
                                        } else {
1658
                                            //no starting '/', making it relative to current document's path
1659
                                            if ('./' == substr($second_part, 0, 2)) {
1660
                                                $second_part = substr($second_part, 2);
1661
                                            }
1662
                                            $files_list[] = [$second_part, 'local', 'rel'];
1663
                                            $dir = '';
1664
                                            if (!empty($abs_path)) {
1665
                                                $dir = dirname($abs_path).'/';
1666
                                            }
1667
                                            $new_abs_path = realpath($dir.$second_part);
1668
                                            $in_files_list[] = self::get_resources_from_source_html(
1669
                                                $new_abs_path,
1670
                                                true,
1671
                                                TOOL_DOCUMENT,
1672
                                                $recursivity + 1
1673
                                            );
1674
                                            if (count($in_files_list) > 0) {
1675
                                                $files_list = array_merge($files_list, $in_files_list);
1676
                                            }
1677
                                        }
1678
                                    }
1679
                                    //leave that second part behind now
1680
                                    $source = substr($source, 0, strpos($source, '?'));
1681
                                    if (strpos($source, '://') > 0) {
1682
                                        if (false !== strpos($source, api_get_path(WEB_PATH))) {
1683
                                            //we found the current portal url
1684
                                            $files_list[] = [$source, 'local', 'url'];
1685
                                            $in_files_list[] = self::get_resources_from_source_html(
1686
                                                $source,
1687
                                                true,
1688
                                                TOOL_DOCUMENT,
1689
                                                $recursivity + 1
1690
                                            );
1691
                                            if (count($in_files_list) > 0) {
1692
                                                $files_list = array_merge($files_list, $in_files_list);
1693
                                            }
1694
                                        } else {
1695
                                            //we didn't find any trace of current portal
1696
                                            $files_list[] = [$source, 'remote', 'url'];
1697
                                        }
1698
                                    } else {
1699
                                        //no protocol found, make link local
1700
                                        if ('/' === substr($source, 0, 1)) {
1701
                                            //link starts with a /, making it absolute (relative to DocumentRoot)
1702
                                            $files_list[] = [$source, 'local', 'abs'];
1703
                                            $in_files_list[] = self::get_resources_from_source_html(
1704
                                                $source,
1705
                                                true,
1706
                                                TOOL_DOCUMENT,
1707
                                                $recursivity + 1
1708
                                            );
1709
                                            if (count($in_files_list) > 0) {
1710
                                                $files_list = array_merge($files_list, $in_files_list);
1711
                                            }
1712
                                        } elseif (0 === strstr($source, '..')) {
1713
                                            //link is relative but going back in the hierarchy
1714
                                            $files_list[] = [$source, 'local', 'rel'];
1715
                                            $dir = '';
1716
                                            if (!empty($abs_path)) {
1717
                                                $dir = dirname($abs_path).'/';
1718
                                            }
1719
                                            $new_abs_path = realpath($dir.$source);
1720
                                            $in_files_list[] = self::get_resources_from_source_html(
1721
                                                $new_abs_path,
1722
                                                true,
1723
                                                TOOL_DOCUMENT,
1724
                                                $recursivity + 1
1725
                                            );
1726
                                            if (count($in_files_list) > 0) {
1727
                                                $files_list = array_merge($files_list, $in_files_list);
1728
                                            }
1729
                                        } else {
1730
                                            //no starting '/', making it relative to current document's path
1731
                                            if ('./' == substr($source, 0, 2)) {
1732
                                                $source = substr($source, 2);
1733
                                            }
1734
                                            $files_list[] = [$source, 'local', 'rel'];
1735
                                            $dir = '';
1736
                                            if (!empty($abs_path)) {
1737
                                                $dir = dirname($abs_path).'/';
1738
                                            }
1739
                                            $new_abs_path = realpath($dir.$source);
1740
                                            $in_files_list[] = self::get_resources_from_source_html(
1741
                                                $new_abs_path,
1742
                                                true,
1743
                                                TOOL_DOCUMENT,
1744
                                                $recursivity + 1
1745
                                            );
1746
                                            if (count($in_files_list) > 0) {
1747
                                                $files_list = array_merge($files_list, $in_files_list);
1748
                                            }
1749
                                        }
1750
                                    }
1751
                                }
1752
                                //found some protocol there
1753
                                if (false !== strpos($source, api_get_path(WEB_PATH))) {
1754
                                    //we found the current portal url
1755
                                    $files_list[] = [$source, 'local', 'url'];
1756
                                    $in_files_list[] = self::get_resources_from_source_html(
1757
                                        $source,
1758
                                        true,
1759
                                        TOOL_DOCUMENT,
1760
                                        $recursivity + 1
1761
                                    );
1762
                                    if (count($in_files_list) > 0) {
1763
                                        $files_list = array_merge($files_list, $in_files_list);
1764
                                    }
1765
                                } else {
1766
                                    //we didn't find any trace of current portal
1767
                                    $files_list[] = [$source, 'remote', 'url'];
1768
                                }
1769
                            } else {
1770
                                //no protocol found, make link local
1771
                                if ('/' === substr($source, 0, 1)) {
1772
                                    //link starts with a /, making it absolute (relative to DocumentRoot)
1773
                                    $files_list[] = [$source, 'local', 'abs'];
1774
                                    $in_files_list[] = self::get_resources_from_source_html(
1775
                                        $source,
1776
                                        true,
1777
                                        TOOL_DOCUMENT,
1778
                                        $recursivity + 1
1779
                                    );
1780
                                    if (count($in_files_list) > 0) {
1781
                                        $files_list = array_merge($files_list, $in_files_list);
1782
                                    }
1783
                                } elseif (0 === strpos($source, '..')) {
1784
                                    //link is relative but going back in the hierarchy
1785
                                    $files_list[] = [$source, 'local', 'rel'];
1786
                                    $dir = '';
1787
                                    if (!empty($abs_path)) {
1788
                                        $dir = dirname($abs_path).'/';
1789
                                    }
1790
                                    $new_abs_path = realpath($dir.$source);
1791
                                    $in_files_list[] = self::get_resources_from_source_html(
1792
                                        $new_abs_path,
1793
                                        true,
1794
                                        TOOL_DOCUMENT,
1795
                                        $recursivity + 1
1796
                                    );
1797
                                    if (count($in_files_list) > 0) {
1798
                                        $files_list = array_merge($files_list, $in_files_list);
1799
                                    }
1800
                                } else {
1801
                                    //no starting '/', making it relative to current document's path
1802
                                    if ('./' == substr($source, 0, 2)) {
1803
                                        $source = substr($source, 2);
1804
                                    }
1805
                                    $files_list[] = [$source, 'local', 'rel'];
1806
                                    $dir = '';
1807
                                    if (!empty($abs_path)) {
1808
                                        $dir = dirname($abs_path).'/';
1809
                                    }
1810
                                    $new_abs_path = realpath($dir.$source);
1811
                                    $in_files_list[] = self::get_resources_from_source_html(
1812
                                        $new_abs_path,
1813
                                        true,
1814
                                        TOOL_DOCUMENT,
1815
                                        $recursivity + 1
1816
                                    );
1817
                                    if (count($in_files_list) > 0) {
1818
                                        $files_list = array_merge($files_list, $in_files_list);
1819
                                    }
1820
                                }
1821
                            }
1822
                        }
1823
                    }
1824
                }
1825
                break;
1826
            default: //ignore
1827
                break;
1828
        }
1829
1830
        $checked_files_list = [];
1831
        $checked_array_list = [];
1832
1833
        if (count($files_list) > 0) {
1834
            foreach ($files_list as $idx => $file) {
1835
                if (!empty($file[0])) {
1836
                    if (!in_array($file[0], $checked_files_list)) {
1837
                        $checked_files_list[] = $files_list[$idx][0];
1838
                        $checked_array_list[] = $files_list[$idx];
1839
                    }
1840
                }
1841
            }
1842
        }
1843
1844
        return $checked_array_list;
1845
    }
1846
1847
    /**
1848
     * Parses the HTML attributes given as string.
1849
     *
1850
     * @param string HTML attribute string
1851
     * @param array List of attributes that we want to get back
1852
     * @param array
1853
     *
1854
     * @return array An associative array of attributes
1855
     *
1856
     * @author Based on a function from the HTML_Common2 PEAR module     *
1857
     */
1858
    public static function parse_HTML_attributes($attrString, $wanted = [], $explode_variables = [])
1859
    {
1860
        $attributes = [];
1861
        $regs = [];
1862
        $reduced = false;
1863
        if (count($wanted) > 0) {
1864
            $reduced = true;
1865
        }
1866
        try {
1867
            //Find all occurences of something that looks like a URL
1868
            // The structure of this regexp is:
1869
            // (find protocol) then
1870
            // (optionally find some kind of space 1 or more times) then
1871
            // find (either an equal sign or a bracket) followed by an optional space
1872
            // followed by some text without quotes (between quotes itself or not)
1873
            // then possible closing brackets if we were in the opening bracket case
1874
            // OR something like @import()
1875
            $res = preg_match_all(
1876
                '/(((([A-Za-z_:])([A-Za-z0-9_:\.-]*))'.
1877
                // '/(((([A-Za-z_:])([A-Za-z0-9_:\.-]|[^\x00-\x7F])*)' . -> seems to be taking too much
1878
                // '/(((([A-Za-z_:])([^\x00-\x7F])*)' . -> takes only last letter of parameter name
1879
                '([ \n\t\r]+)?('.
1880
                // '(=([ \n\t\r]+)?("[^"]+"|\'[^\']+\'|[^ \n\t\r]+))' . -> doesn't restrict close enough to the url itself
1881
                '(=([ \n\t\r]+)?("[^"\)]+"|\'[^\'\)]+\'|[^ \n\t\r\)]+))'.
1882
                '|'.
1883
                // '(\(([ \n\t\r]+)?("[^"]+"|\'[^\']+\'|[^ \n\t\r]+)\))' . -> doesn't restrict close enough to the url itself
1884
                '(\(([ \n\t\r]+)?("[^"\)]+"|\'[^\'\)]+\'|[^ \n\t\r\)]+)\))'.
1885
                '))'.
1886
                '|'.
1887
                // '(@import([ \n\t\r]+)?("[^"]+"|\'[^\']+\'|[^ \n\t\r]+)))?/', -> takes a lot (like 100's of thousands of empty possibilities)
1888
                '(@import([ \n\t\r]+)?("[^"]+"|\'[^\']+\'|[^ \n\t\r]+)))/',
1889
                $attrString,
1890
                $regs
1891
            );
1892
        } catch (Exception $e) {
1893
            error_log('Caught exception: '.$e->getMessage(), 0);
1894
        }
1895
        if ($res) {
1896
            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...
1897
                $name = trim($regs[3][$i]);
1898
                $check = trim($regs[0][$i]);
1899
                $value = trim($regs[10][$i]);
1900
                if (empty($value) and !empty($regs[13][$i])) {
1901
                    $value = $regs[13][$i];
1902
                }
1903
                if (empty($name) && !empty($regs[16][$i])) {
1904
                    $name = '@import';
1905
                    $value = trim($regs[16][$i]);
1906
                }
1907
                if (!empty($name)) {
1908
                    if (!$reduced || in_array(strtolower($name), $wanted)) {
1909
                        if ($name == $check) {
1910
                            $attributes[strtolower($name)][] = strtolower($name);
1911
                        } else {
1912
                            if (!empty($value) && ('\'' == $value[0] || '"' == $value[0])) {
1913
                                $value = substr($value, 1, -1);
1914
                            }
1915
1916
                            if ('API.LMSGetValue(name' == $value) {
1917
                                $value = 'API.LMSGetValue(name)';
1918
                            }
1919
                            //Gets the xx.flv value from the string flashvars="width=320&height=240&autostart=false&file=xxx.flv&repeat=false"
1920
                            if (isset($explode_variables[$name])) {
1921
                                $value_modified = str_replace('&amp;', '&', $value);
1922
                                $value_array = explode('&', $value_modified);
1923
                                foreach ($value_array as $item) {
1924
                                    $itemParts = explode('=', $item);
1925
                                    $key = $itemParts[0];
1926
                                    $item_value = !empty($itemParts[1]) ? $itemParts[1] : '';
1927
                                    if ($key == $explode_variables[$name]) {
1928
                                        $attributes[strtolower($name)][] = $item_value;
1929
                                    }
1930
                                }
1931
                            }
1932
                            $attributes[strtolower($name)][] = $value;
1933
                        }
1934
                    }
1935
                }
1936
            }
1937
        }
1938
1939
        return $attributes;
1940
    }
1941
1942
    /**
1943
     * Replace urls inside content html from a copy course.
1944
     *
1945
     * @param string $content_html
1946
     * @param string $origin_course_code
1947
     * @param string $destination_course_directory
1948
     * @param string $origin_course_path_from_zip
1949
     * @param string $origin_course_info_path
1950
     *
1951
     * @return string new content html with replaced urls or return false if content is not a string
1952
     */
1953
    public static function replaceUrlWithNewCourseCode(
1954
        $content_html,
1955
        $origin_course_code,
1956
        $destination_course_directory,
1957
        $origin_course_path_from_zip = null,
1958
        $origin_course_info_path = null
1959
    ) {
1960
        if (empty($content_html)) {
1961
            return false;
1962
        }
1963
1964
        $orig_source_html = self::get_resources_from_source_html($content_html);
1965
        $orig_course_info = api_get_course_info($origin_course_code);
1966
1967
        $destination_course_code = CourseManager::getCourseCodeFromDirectory($destination_course_directory);
1968
        $destination_course_info = api_get_course_info($destination_course_code);
1969
1970
        if (!empty($orig_source_html)) {
1971
            foreach ($orig_source_html as $source) {
1972
1973
                $real_orig_url = $source[0];
1974
                $scope_url = $source[1];
1975
                $type_url = $source[2];
1976
1977
                if ('local' === $scope_url) {
1978
                    $document_file = strstr($real_orig_url, 'document');
1979
1980
                    if (false !== $document_file) {
1981
                        $new_url = self::generateNewUrlForCourseResource($destination_course_info, $document_file);
1982
                        $content_html = str_replace($real_orig_url, $new_url, $content_html);
1983
                    }
1984
                }
1985
            }
1986
        }
1987
1988
        return $content_html;
1989
    }
1990
1991
    /**
1992
     * Generates a new URL for a resource within the context of the target course.
1993
     *
1994
     * This function constructs a URL to access a given resource, such as a document
1995
     * or image, which has been copied into the target course. It's essential for
1996
     * updating resource links in course content to point to the correct location
1997
     * after resources have been duplicated or moved between courses.
1998
     */
1999
    public static function generateNewUrlForCourseResource(array $destination_course_info, string $document_file): string
2000
    {
2001
        $courseCode = $destination_course_info['code'];
2002
        $courseWebPath = api_get_path(WEB_COURSE_PATH) . $courseCode . "/document/";
2003
2004
        $document_file = ltrim($document_file, '/');
2005
2006
        $newUrl = $courseWebPath . $document_file;
2007
2008
        return $newUrl;
2009
    }
2010
2011
2012
    /**
2013
     * Obtains the text inside the file with the right parser.
2014
     */
2015
    public static function get_text_content($doc_path, $doc_mime)
2016
    {
2017
        // TODO: review w$ compatibility
2018
        // Use usual exec output lines array to store stdout instead of a temp file
2019
        // because we need to store it at RAM anyway before index on ChamiloIndexer object
2020
        $ret_val = null;
2021
        switch ($doc_mime) {
2022
            case 'text/plain':
2023
                $handle = fopen($doc_path, 'r');
2024
                $output = [fread($handle, filesize($doc_path))];
2025
                fclose($handle);
2026
                break;
2027
            case 'application/pdf':
2028
                exec("pdftotext $doc_path -", $output, $ret_val);
2029
                break;
2030
            case 'application/postscript':
2031
                $temp_file = tempnam(sys_get_temp_dir(), 'chamilo');
2032
                exec("ps2pdf $doc_path $temp_file", $output, $ret_val);
2033
                if (0 !== $ret_val) { // shell fail, probably 127 (command not found)
2034
                    return false;
2035
                }
2036
                exec("pdftotext $temp_file -", $output, $ret_val);
2037
                unlink($temp_file);
2038
                break;
2039
            case 'application/msword':
2040
                exec("catdoc $doc_path", $output, $ret_val);
2041
                break;
2042
            case 'text/html':
2043
                exec("html2text $doc_path", $output, $ret_val);
2044
                break;
2045
            case 'text/rtf':
2046
                // Note: correct handling of code pages in unrtf
2047
                // on debian lenny unrtf v0.19.2 can not, but unrtf v0.20.5 can
2048
                exec("unrtf --text $doc_path", $output, $ret_val);
2049
                if (127 == $ret_val) { // command not found
2050
                    return false;
2051
                }
2052
                // Avoid index unrtf comments
2053
                if (is_array($output) && count($output) > 1) {
2054
                    $parsed_output = [];
2055
                    foreach ($output as &$line) {
2056
                        if (!preg_match('/^###/', $line, $matches)) {
2057
                            if (!empty($line)) {
2058
                                $parsed_output[] = $line;
2059
                            }
2060
                        }
2061
                    }
2062
                    $output = $parsed_output;
2063
                }
2064
                break;
2065
            case 'application/vnd.ms-powerpoint':
2066
                exec("catppt $doc_path", $output, $ret_val);
2067
                break;
2068
            case 'application/vnd.ms-excel':
2069
                exec("xls2csv -c\" \" $doc_path", $output, $ret_val);
2070
                break;
2071
        }
2072
2073
        $content = '';
2074
        if (!is_null($ret_val)) {
2075
            if (0 !== $ret_val) { // shell fail, probably 127 (command not found)
2076
                return false;
2077
            }
2078
        }
2079
        if (isset($output)) {
2080
            foreach ($output as &$line) {
2081
                $content .= $line."\n";
2082
            }
2083
2084
            return $content;
2085
        } else {
2086
            return false;
2087
        }
2088
    }
2089
2090
    /**
2091
     * Display the document quota in a simple way.
2092
     *
2093
     *  Here we count 1 Kilobyte = 1024 Bytes, 1 Megabyte = 1048576 Bytes
2094
     */
2095
    public static function displaySimpleQuota($course_quota, $already_consumed_space)
2096
    {
2097
        $course_quota_m = round($course_quota / 1048576);
2098
        $already_consumed_space_m = round($already_consumed_space / 1048576, 2);
2099
        $percentage = $already_consumed_space / $course_quota * 100;
2100
        $percentage = round($percentage, 1);
2101
        $message = get_lang('You are currently using %s MB (%s) of your %s MB.');
2102
        $message = sprintf($message, $already_consumed_space_m, $percentage.'%', $course_quota_m.' ');
2103
2104
        return Display::div($message, ['id' => 'document_quota', 'class' => 'card-quota']);
2105
    }
2106
2107
    /**
2108
     * Checks if there is enough place to add a file on a directory
2109
     * on the base of a maximum directory size allowed.
2110
     *
2111
     * @author Bert Vanderkimpen
2112
     *
2113
     * @param int $file_size     size of the file in byte
2114
     * @param int $max_dir_space maximum size
2115
     *
2116
     * @return bool true if there is enough space, false otherwise
2117
     */
2118
    public static function enough_space($file_size, $max_dir_space)
2119
    {
2120
        if ($max_dir_space) {
2121
            $max_dir_space = $max_dir_space * 1024 * 1024;
2122
            $courseEntity = api_get_course_entity();
2123
            $repo = Container::getDocumentRepository();
2124
            $total = $repo->getFolderSize($courseEntity->getResourceNode(), $courseEntity);
2125
2126
            if (($file_size + $total) > $max_dir_space) {
2127
                return false;
2128
            }
2129
        }
2130
2131
        return true;
2132
    }
2133
2134
    /**
2135
     * @param array $params count, url, extension
2136
     *
2137
     * @return string
2138
     */
2139
    public static function generateAudioJavascript($params = [])
2140
    {
2141
        $js = '
2142
            $(\'audio.audio_preview\').mediaelementplayer({
2143
                features: [\'playpause\'],
2144
                audioWidth: 30,
2145
                audioHeight: 30,
2146
                success: function(mediaElement, originalNode, instance) {
2147
                }
2148
            });';
2149
2150
        return $js;
2151
    }
2152
2153
    /**
2154
     * Shows a play icon next to the document title in the document list.
2155
     *
2156
     * @param string $documentWebPath
2157
     * @param array  $documentInfo
2158
     *
2159
     * @return string
2160
     */
2161
    public static function generateAudioPreview($documentWebPath, $documentInfo)
2162
    {
2163
        $filePath = $documentWebPath.$documentInfo['path'];
2164
        $extension = $documentInfo['file_extension'];
2165
2166
        return '<span class="preview">
2167
                    <audio class="audio_preview skip" src="'.$filePath.'" type="audio/'.$extension.'"></audio>
2168
                </span>';
2169
    }
2170
2171
    /**
2172
     * @param string $file
2173
     * @param string $extension
2174
     *
2175
     * @return string
2176
     */
2177
    public static function generateMediaPreview($file, $extension)
2178
    {
2179
        $id = api_get_unique_id();
2180
        switch ($extension) {
2181
            case 'wav':
2182
            case 'ogg':
2183
            case 'mp3':
2184
                $html = '<div style="margin: 0; position: absolute; top: 50%; left: 35%;">';
2185
                $html .= '<audio id="'.$id.'" controls="controls" src="'.$file.'" type="audio/mp3" ></audio></div>';
2186
                break;
2187
            default:
2188
                $html = '<video id="'.$id.'" controls>';
2189
                $html .= '<source src="'.$file.'" >';
2190
                $html .= '</video>';
2191
                break;
2192
        }
2193
2194
        return $html;
2195
    }
2196
2197
    /**
2198
     * @param bool   $lp_id
2199
     * @param string $target
2200
     * @param int    $session_id
2201
     * @param bool   $add_move_button
2202
     * @param string $filter_by_folder
2203
     * @param string $overwrite_url
2204
     * @param bool   $showInvisibleFiles
2205
     * @param bool   $showOnlyFolders
2206
     * @param int    $folderId
2207
     * @param bool   $addCloseButton
2208
     * @param bool   $addAudioPreview
2209
     * @param array  $filterByExtension
2210
     *
2211
     * @return string
2212
     */
2213
    public static function get_document_preview(
2214
        Course $course,
2215
               $lp_id = false,
2216
               $target = '',
2217
               $session_id = 0,
2218
               $add_move_button = false,
2219
               $filter_by_folder = null,
2220
               $overwrite_url = '',
2221
               $showInvisibleFiles = false,
2222
               $showOnlyFolders = false,
2223
               $folderId = false,
2224
               $addCloseButton = true,
2225
               $addAudioPreview = false,
2226
               $filterByExtension = [],
2227
               $excludeByExtension = [],
2228
               $filterByFiletype = null
2229
    ) {
2230
        $repo = Container::getDocumentRepository();
2231
        $nodeRepository = $repo->getResourceNodeRepository();
2232
        $move = get_lang('Move');
2233
        $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>';
2234
        $folderIcon = Display::getMdiIcon(ObjectIcon::CHAPTER, 'ch-tool-icon', null, ICON_SIZE_SMALL);
2235
2236
        $lpItemType = ($filterByFiletype === 'video') ? 'video' : 'document';
2237
        $options = [
2238
            'decorate' => true,
2239
            'rootOpen' => '<ul id="doc_list" class="list-group lp_resource">',
2240
            'rootClose' => '</ul>',
2241
            'childOpen' => function ($child) {
2242
                $id = $child['id'];
2243
                $disableDrag = '';
2244
                if (empty($child['resourceFiles'])) {
2245
                    $disableDrag = ' disable_drag ';
2246
                }
2247
2248
                return '<li
2249
                id="'.$id.'"
2250
                data-id="'.$id.'"
2251
                class="'.$disableDrag.' list-group-item nested-'.$child['level'].'"
2252
            >';
2253
            },
2254
            'childClose' => '</li>',
2255
            'nodeDecorator' => function ($node) use ($icon, $folderIcon, $lpItemType) {
2256
                $disableDrag = '';
2257
                if (empty($node['resourceFiles'])) {
2258
                    $disableDrag = ' disable_drag ';
2259
                }
2260
2261
                $link = '<div class="flex flex-row gap-1 h-4 item_data '.$disableDrag.' ">';
2262
                $file = !empty($node['resourceFiles']) ? current($node['resourceFiles']) : null;
2263
                $folder = $folderIcon;
2264
2265
                if (!empty($node['resourceFiles'])) {
2266
                    $link .= '<a class="moved ui-sortable-handle" href="#">';
2267
                    $link .= $icon;
2268
                    $link .= '</a>';
2269
                    $folder = '';
2270
                }
2271
2272
                $link .= '<a
2273
                data_id="'.$node['id'].'"
2274
                data_type="'.$lpItemType.'"
2275
                class="moved ui-sortable-handle link_with_id"
2276
            >';
2277
                $link .= $folder.'&nbsp;';
2278
                $link .= '</a>';
2279
                $link .= cut(addslashes($node['title']), 30);
2280
                $link .= '</div>';
2281
2282
                return $link;
2283
            },
2284
        ];
2285
2286
        $type = $repo->getResourceType();
2287
        $em = Database::getManager();
2288
        $qb = $em
2289
            ->createQueryBuilder()
2290
            ->select('node, files')
2291
            ->from(ResourceNode::class, 'node')
2292
            ->innerJoin('node.resourceType', 'type')
2293
            ->innerJoin('node.resourceLinks', 'links')
2294
            ->innerJoin('node.resourceFiles', 'files')
2295
            ->innerJoin(
2296
                CDocument::class,
2297
                'doc',
2298
                'WITH',
2299
                'doc.resourceNode = node'
2300
            )
2301
            ->where('type = :type')
2302
            ->andWhere('links.course = :course')
2303
            ->setParameters([
2304
                'type' => $type,
2305
                'course' => $course,
2306
            ])
2307
            ->orderBy('node.parent', 'ASC');
2308
2309
2310
        $sessionId = api_get_session_id();
2311
        if (empty($sessionId)) {
2312
            $qb->andWhere('links.session IS NULL');
2313
        } else {
2314
            $qb
2315
                ->andWhere('links.session = :session')
2316
                ->setParameter('session', $sessionId);
2317
        }
2318
2319
        if ($filterByFiletype !== null) {
2320
            if (is_array($filterByFiletype)) {
2321
                $qb->andWhere('doc.filetype IN (:filetypes)');
2322
                $qb->setParameter('filetypes', $filterByFiletype);
2323
            } else {
2324
                $qb->andWhere('doc.filetype = :filetype');
2325
                $qb->setParameter('filetype', $filterByFiletype);
2326
            }
2327
        }
2328
2329
        if (!empty($filterByExtension)) {
2330
            $orX = $qb->expr()->orX();
2331
            foreach ($filterByExtension as $extension) {
2332
                $paramName = 'ext_' . $extension;
2333
                $orX->add(
2334
                    $qb->expr()->like(
2335
                        'LOWER(files.originalName)',
2336
                        ':' . $paramName
2337
                    )
2338
                );
2339
                $qb->setParameter($paramName, '%.' . strtolower($extension));
2340
            }
2341
            $qb->andWhere($orX);
2342
        }
2343
2344
        if (!empty($excludeByExtension)) {
2345
            foreach ($excludeByExtension as $extension) {
2346
                $qb->andWhere(
2347
                    $qb->expr()->notLike(
2348
                        'LOWER(files.originalName)',
2349
                        ':exclude_' . $extension
2350
                    )
2351
                );
2352
                $qb->setParameter('exclude_' . $extension, '%.' . strtolower($extension));
2353
            }
2354
        }
2355
2356
        $query = $qb->getQuery();
2357
2358
        return $nodeRepository->buildTree($query->getArrayResult(), $options);
2359
    }
2360
2361
    /**
2362
     * Index a given document.
2363
     *
2364
     * @param   int     Document ID inside its corresponding course
2365
     * @param   string  Course code
2366
     * @param   int     Session ID (not used yet)
2367
     * @param   string  Language of document's content (defaults to course language)
2368
     * @param   array   Array of specific fields (['code'=>'value',...])
2369
     * @param   string  What to do if the file already exists (default or overwrite)
2370
     * @param   bool    When set to true, this runs the indexer without actually saving anything to any database
2371
     *
2372
     * @return bool Returns true on presumed success, false on failure
2373
     */
2374
    public static function index_document(
2375
        $docid,
2376
        $course_code,
2377
        $session_id = 0,
2378
        $lang = 'english',
2379
        $specific_fields_values = [],
2380
        $if_exists = '',
2381
        $simulation = false
2382
    ) {
2383
        if ('true' !== api_get_setting('search_enabled')) {
2384
            return false;
2385
        }
2386
        if (empty($docid) or $docid != intval($docid)) {
2387
            return false;
2388
        }
2389
        if (empty($session_id)) {
2390
            $session_id = api_get_session_id();
2391
        }
2392
        $course_info = api_get_course_info($course_code);
2393
        $course_dir = $course_info['path'].'/document';
2394
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
2395
        $base_work_dir = $sys_course_path.$course_dir;
2396
2397
        $course_id = $course_info['real_id'];
2398
        $table_document = Database::get_course_table(TABLE_DOCUMENT);
2399
2400
        $qry = "SELECT path, title FROM $table_document WHERE c_id = $course_id AND id = '$docid' LIMIT 1";
2401
        $result = Database::query($qry);
2402
        if (1 == Database::num_rows($result)) {
2403
            $row = Database::fetch_array($result);
2404
            $doc_path = api_get_path(SYS_COURSE_PATH).$course_dir.$row['path'];
2405
            //TODO: mime_content_type is deprecated, fileinfo php extension is enabled by default as of PHP 5.3.0
2406
            // now versions of PHP on Debian testing(5.2.6-5) and Ubuntu(5.2.6-2ubuntu) are lower, so wait for a while
2407
            $doc_mime = mime_content_type($doc_path);
2408
            $allowed_mime_types = self::file_get_mime_type(true);
2409
2410
            // mime_content_type does not detect correctly some formats that
2411
            // are going to be supported for index, so an extensions array is used for the moment
2412
            if (empty($doc_mime)) {
2413
                $allowed_extensions = [
2414
                    'doc',
2415
                    'docx',
2416
                    'ppt',
2417
                    'pptx',
2418
                    'pps',
2419
                    'ppsx',
2420
                    'xls',
2421
                    'xlsx',
2422
                    'odt',
2423
                    'odp',
2424
                    'ods',
2425
                    'pdf',
2426
                    'txt',
2427
                    'rtf',
2428
                    'msg',
2429
                    'csv',
2430
                    'html',
2431
                    'htm',
2432
                ];
2433
                $extensions = preg_split("/[\/\\.]/", $doc_path);
2434
                $doc_ext = strtolower($extensions[count($extensions) - 1]);
2435
                if (in_array($doc_ext, $allowed_extensions)) {
2436
                    switch ($doc_ext) {
2437
                        case 'ppt':
2438
                        case 'pps':
2439
                            $doc_mime = 'application/vnd.ms-powerpoint';
2440
                            break;
2441
                        case 'xls':
2442
                            $doc_mime = 'application/vnd.ms-excel';
2443
                            break;
2444
                    }
2445
                }
2446
            }
2447
2448
            //@todo move this nightmare in a search controller or something like that!!! J.M
2449
2450
            if (in_array($doc_mime, $allowed_mime_types)) {
2451
                $file_title = $row['title'];
2452
                $file_content = self::get_text_content($doc_path, $doc_mime);
2453
                $course_code = Database::escape_string($course_code);
2454
                $ic_slide = new IndexableChunk();
2455
                $ic_slide->addValue('title', $file_title);
2456
                $ic_slide->addCourseId($course_code);
2457
                $ic_slide->addToolId(TOOL_DOCUMENT);
2458
                $xapian_data = [
2459
                    SE_COURSE_ID => $course_code,
2460
                    SE_TOOL_ID => TOOL_DOCUMENT,
2461
                    SE_DATA => ['doc_id' => $docid],
2462
                    SE_USER => api_get_user_id(),
2463
                ];
2464
2465
                $ic_slide->xapian_data = serialize($xapian_data);
2466
                $di = new ChamiloIndexer();
2467
                $return = $di->connectDb(null, null, $lang);
2468
2469
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2470
                $specific_fields = get_specific_field_list();
2471
2472
                // process different depending on what to do if file exists
2473
                /**
2474
                 * @TODO Find a way to really verify if the file had been
2475
                 * overwriten. Now all work is done at
2476
                 * handle_uploaded_document() and it's difficult to verify it
2477
                 */
2478
                if (!empty($if_exists) && 'overwrite' == $if_exists) {
2479
                    // Overwrite the file on search engine
2480
                    // Actually, it consists on a delete of terms from db,
2481
                    // insert new ones, create a new search engine document,
2482
                    // and remove the old one
2483
                    // Get search_did
2484
                    $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2485
                    $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
2486
                    $sql = sprintf($sql, $tbl_se_ref, $course_code, TOOL_DOCUMENT, $docid);
2487
2488
                    $res = Database::query($sql);
2489
2490
                    if (Database::num_rows($res) > 0) {
2491
                        $se_ref = Database::fetch_array($res);
2492
                        if (!$simulation) {
2493
                            $di->remove_document($se_ref['search_did']);
2494
                        }
2495
                        $all_specific_terms = '';
2496
                        foreach ($specific_fields as $specific_field) {
2497
                            if (!$simulation) {
2498
                                delete_all_specific_field_value($course_code, $specific_field['id'], TOOL_DOCUMENT, $docid);
2499
                            }
2500
                            // Update search engine
2501
                            if (isset($specific_fields_values[$specific_field['code']])) {
2502
                                $sterms = trim($specific_fields_values[$specific_field['code']]);
2503
                            } else { //if the specific field is not defined, force an empty one
2504
                                $sterms = '';
2505
                            }
2506
                            $all_specific_terms .= ' '.$sterms;
2507
                            $sterms = explode(',', $sterms);
2508
                            foreach ($sterms as $sterm) {
2509
                                $sterm = trim($sterm);
2510
                                if (!empty($sterm)) {
2511
                                    $ic_slide->addTerm($sterm, $specific_field['code']);
2512
                                    // updated the last param here from $value to $sterm without being sure - see commit15464
2513
                                    if (!$simulation) {
2514
                                        add_specific_field_value(
2515
                                            $specific_field['id'],
2516
                                            $course_code,
2517
                                            TOOL_DOCUMENT,
2518
                                            $docid,
2519
                                            $sterm
2520
                                        );
2521
                                    }
2522
                                }
2523
                            }
2524
                        }
2525
                        // Add terms also to content to make terms findable by probabilistic search
2526
                        $file_content = $all_specific_terms.' '.$file_content;
2527
2528
                        if (!$simulation) {
2529
                            $ic_slide->addValue('content', $file_content);
2530
                            $di->addChunk($ic_slide);
2531
                            // Index and return a new search engine document id
2532
                            $did = $di->index();
2533
2534
                            if ($did) {
2535
                                // update the search_did on db
2536
                                $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2537
                                $sql = 'UPDATE %s SET search_did=%d WHERE id=%d LIMIT 1';
2538
                                $sql = sprintf($sql, $tbl_se_ref, (int) $did, (int) $se_ref['id']);
2539
                                Database::query($sql);
2540
                            }
2541
                        }
2542
                    }
2543
                } else {
2544
                    // Add all terms
2545
                    $all_specific_terms = '';
2546
                    foreach ($specific_fields as $specific_field) {
2547
                        if (isset($specific_fields_values[$specific_field['code']])) {
2548
                            $sterms = trim($specific_fields_values[$specific_field['code']]);
2549
                        } else { //if the specific field is not defined, force an empty one
2550
                            $sterms = '';
2551
                        }
2552
                        $all_specific_terms .= ' '.$sterms;
2553
                        if (!empty($sterms)) {
2554
                            $sterms = explode(',', $sterms);
2555
                            foreach ($sterms as $sterm) {
2556
                                if (!$simulation) {
2557
                                    $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2558
                                    add_specific_field_value(
2559
                                        $specific_field['id'],
2560
                                        $course_code,
2561
                                        TOOL_DOCUMENT,
2562
                                        $docid,
2563
                                        $sterm
2564
                                    );
2565
                                }
2566
                            }
2567
                        }
2568
                    }
2569
                    // Add terms also to content to make terms findable by probabilistic search
2570
                    $file_content = $all_specific_terms.' '.$file_content;
2571
                    if (!$simulation) {
2572
                        $ic_slide->addValue('content', $file_content);
2573
                        $di->addChunk($ic_slide);
2574
                        // Index and return search engine document id
2575
                        $did = $di->index();
2576
                        if ($did) {
2577
                            // Save it to db
2578
                            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2579
                            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2580
                            VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2581
                            $sql = sprintf($sql, $tbl_se_ref, $course_code, TOOL_DOCUMENT, $docid, $did);
2582
                            Database::query($sql);
2583
                        } else {
2584
                            return false;
2585
                        }
2586
                    }
2587
                }
2588
            } else {
2589
                return false;
2590
            }
2591
        }
2592
2593
        return true;
2594
    }
2595
2596
    /**
2597
     * @param string $path
2598
     * @param bool   $is_certificate_mode
2599
     *
2600
     * @return bool
2601
     */
2602
    public static function is_folder_to_avoid($path, $is_certificate_mode = false)
2603
    {
2604
        $foldersToAvoid = [
2605
            '/HotPotatoes_files',
2606
            '/certificates',
2607
        ];
2608
        $systemFolder = api_get_course_setting('show_system_folders');
2609
2610
        if (1 == $systemFolder) {
2611
            $foldersToAvoid = [];
2612
        }
2613
2614
        if ('css' == basename($path)) {
2615
            return true;
2616
        }
2617
2618
        if (false == $is_certificate_mode) {
2619
            //Certificate results
2620
            if (strstr($path, 'certificates')) {
2621
                return true;
2622
            }
2623
        }
2624
2625
        // Admin setting for Hide/Show the folders of all users
2626
        if ('false' == api_get_setting('show_users_folders')) {
2627
            $foldersToAvoid[] = '/shared_folder';
2628
2629
            if (strstr($path, 'shared_folder_session_')) {
2630
                return true;
2631
            }
2632
        }
2633
2634
        // Admin setting for Hide/Show Default folders to all users
2635
        if ('false' == api_get_setting('show_default_folders')) {
2636
            $foldersToAvoid[] = '/images';
2637
            $foldersToAvoid[] = '/flash';
2638
            $foldersToAvoid[] = '/audio';
2639
            $foldersToAvoid[] = '/video';
2640
        }
2641
2642
        // Admin setting for Hide/Show chat history folder
2643
        if ('false' == api_get_setting('show_chat_folder')) {
2644
            $foldersToAvoid[] = '/chat_files';
2645
        }
2646
2647
        if (is_array($foldersToAvoid)) {
2648
            return in_array($path, $foldersToAvoid);
2649
        } else {
2650
            return false;
2651
        }
2652
    }
2653
2654
    /**
2655
     * @return array
2656
     */
2657
    public static function get_system_folders()
2658
    {
2659
        return [
2660
            '/certificates',
2661
            '/HotPotatoes_files',
2662
            '/chat_files',
2663
            '/images',
2664
            '/flash',
2665
            '/audio',
2666
            '/video',
2667
            '/shared_folder',
2668
            '/learning_path',
2669
        ];
2670
    }
2671
2672
    /**
2673
     * @return array
2674
     */
2675
    public static function getProtectedFolderFromStudent()
2676
    {
2677
        return [
2678
            '/certificates',
2679
            '/HotPotatoes_files',
2680
            '/chat_files',
2681
            '/shared_folder',
2682
            '/learning_path',
2683
        ];
2684
    }
2685
2686
    /**
2687
     * @param string $courseCode
2688
     *
2689
     * @return string 'visible' or 'invisible' string
2690
     */
2691
    public static function getDocumentDefaultVisibility($courseCode)
2692
    {
2693
        $settings = api_get_setting('tool_visible_by_default_at_creation');
2694
        $defaultVisibility = 'visible';
2695
2696
        if (isset($settings['documents'])) {
2697
            $portalDefaultVisibility = 'invisible';
2698
            if ('true' == $settings['documents']) {
2699
                $portalDefaultVisibility = 'visible';
2700
            }
2701
2702
            $defaultVisibility = $portalDefaultVisibility;
2703
        }
2704
2705
        if ('true' === api_get_setting('documents_default_visibility_defined_in_course')) {
2706
            $courseVisibility = api_get_course_setting('documents_default_visibility', $courseCode);
2707
            if (!empty($courseVisibility) && in_array($courseVisibility, ['visible', 'invisible'])) {
2708
                $defaultVisibility = $courseVisibility;
2709
            }
2710
        }
2711
2712
        return $defaultVisibility;
2713
    }
2714
2715
    /**
2716
     * @param array $_course
2717
     *
2718
     * @return CDocument
2719
     */
2720
    public static function createDefaultAudioFolder($_course)
2721
    {
2722
        if (!isset($_course['path'])) {
2723
            return false;
2724
        }
2725
2726
        return self::addDocument($_course, '/audio', 'folder', 0, 'Audio');
2727
    }
2728
2729
    /**
2730
     * Generate a default certificate for a courses.
2731
     *
2732
     * @todo move to certificate lib
2733
     *
2734
     * @global string $css CSS directory
2735
     * @global string $img_dir image directory
2736
     * @global string $default_course_dir Course directory
2737
     * @global string $js JS directory
2738
     *
2739
     * @param array $courseData     The course info
2740
     * @param bool  $fromBaseCourse
2741
     * @param int   $sessionId
2742
     */
2743
    public static function generateDefaultCertificate(
2744
        $courseData,
2745
        $fromBaseCourse = false,
2746
        $sessionId = 0
2747
    ) {
2748
        if (empty($courseData)) {
2749
            return false;
2750
        }
2751
2752
        global $css, $img_dir, $default_course_dir, $js;
2753
        $codePath = api_get_path(REL_CODE_PATH);
2754
        $dir = '/certificates';
2755
        $comment = null;
2756
        $title = get_lang('Default certificate');
2757
        $fileName = api_replace_dangerous_char($title);
2758
        $fileType = 'certificate';
2759
        $templateContent = file_get_contents(api_get_path(SYS_CODE_PATH).'gradebook/certificate_template/template.html');
2760
2761
        $search = ['{CSS}', '{IMG_DIR}', '{REL_CODE_PATH}', '{COURSE_DIR}'];
2762
        $replace = [$css.$js, $img_dir, $codePath, $default_course_dir];
2763
2764
        $fileContent = str_replace($search, $replace, $templateContent);
2765
        $saveFilePath = "$dir/$fileName.html";
2766
2767
        if ($fromBaseCourse) {
2768
            $defaultCertificateId = self::get_default_certificate_id($courseData['real_id'], 0);
2769
            if (!empty($defaultCertificateId)) {
2770
                // We have a certificate from the course base
2771
                $documentData = self::get_document_data_by_id(
2772
                    $defaultCertificateId,
2773
                    $courseData['code'],
2774
                    false,
2775
                    0
2776
                );
2777
2778
                if (isset($documentData['absolute_path'])) {
2779
                    $fileContent = file_get_contents($documentData['absolute_path']);
2780
                }
2781
            }
2782
        }
2783
2784
        $document = self::addDocument(
2785
            $courseData,
2786
            $saveFilePath,
2787
            $fileType,
2788
            0,
2789
            $title,
2790
            $comment,
2791
            0, //$readonly = 0,
2792
            true, //$save_visibility = true,
2793
            null, //$group_id = null,
2794
            $sessionId,
2795
            0,
2796
            false,
2797
            $fileContent
2798
        );
2799
2800
        $defaultCertificateId = self::get_default_certificate_id($courseData['real_id'], $sessionId);
2801
2802
        if (!isset($defaultCertificateId)) {
2803
            self::attach_gradebook_certificate(
2804
                $courseData['real_id'],
2805
                $document->getIid(),
2806
                $sessionId
2807
            );
2808
        }
2809
    }
2810
2811
    /**
2812
     * Get folder/file suffix.
2813
     *
2814
     * @param array $courseInfo
2815
     * @param int   $sessionId
2816
     * @param int   $groupId
2817
     *
2818
     * @return string
2819
     */
2820
    public static function getDocumentSuffix($courseInfo, $sessionId, $groupId)
2821
    {
2822
        // If no session or group, then no suffix.
2823
        if (empty($sessionId) && empty($groupId)) {
2824
            return '';
2825
        }
2826
2827
        return '__'.(int) $sessionId.'__'.(int) $groupId;
2828
    }
2829
2830
    /**
2831
     * Fix a document name adding session id and group id
2832
     * Turns picture.jpg -> picture__1__2.jpg
2833
     * Where 1 = session id and 2 group id
2834
     * Of session id and group id are empty then the function returns:
2835
     * picture.jpg ->  picture.jpg.
2836
     *
2837
     * @param string $name       folder or file name
2838
     * @param string $type       'folder' or 'file'
2839
     * @param array  $courseInfo
2840
     * @param int    $sessionId
2841
     * @param int    $groupId
2842
     *
2843
     * @return string
2844
     */
2845
    public static function fixDocumentName($name, $type, $courseInfo, $sessionId, $groupId)
2846
    {
2847
        $suffix = self::getDocumentSuffix($courseInfo, $sessionId, $groupId);
2848
2849
        switch ($type) {
2850
            case 'folder':
2851
                $name = $name.$suffix;
2852
                break;
2853
            case 'file':
2854
                $name = self::addSuffixToFileName($name, $suffix);
2855
                break;
2856
        }
2857
2858
        return $name;
2859
    }
2860
2861
    /**
2862
     * Add a suffix to a file Example:
2863
     * /folder/picture.jpg => to /folder/picture_this.jpg
2864
     * where "_this" is the suffix.
2865
     *
2866
     * @param string $name
2867
     * @param string $suffix
2868
     *
2869
     * @return string
2870
     */
2871
    public static function addSuffixToFileName($name, $suffix)
2872
    {
2873
        $extension = pathinfo($name, PATHINFO_EXTENSION);
2874
        $fileName = pathinfo($name, PATHINFO_FILENAME);
2875
        $dir = pathinfo($name, PATHINFO_DIRNAME);
2876
2877
        if ('.' == $dir) {
2878
            $dir = null;
2879
        }
2880
2881
        if (!empty($dir) && '/' != $dir) {
2882
            $dir = $dir.'/';
2883
        }
2884
2885
        $name = $dir.$fileName.$suffix.'.'.$extension;
2886
2887
        return $name;
2888
    }
2889
2890
    /**
2891
     * Check if folder exist in the course base or in the session course.
2892
     *
2893
     * @param string $folder     Example: /folder/folder2
2894
     * @param array  $courseInfo
2895
     * @param int    $sessionId
2896
     * @param int    $groupId    group.id
2897
     *
2898
     * @return bool
2899
     */
2900
    public static function folderExists(
2901
        $folder,
2902
        $courseInfo,
2903
        $sessionId,
2904
        $groupId
2905
    ) {
2906
        $courseId = $courseInfo['real_id'];
2907
2908
        if (empty($courseId)) {
2909
            return false;
2910
        }
2911
2912
        $sessionId = (int) $sessionId;
2913
        $folderWithSuffix = self::fixDocumentName(
2914
            $folder,
2915
            'folder',
2916
            $courseInfo,
2917
            $sessionId,
2918
            $groupId
2919
        );
2920
2921
        $folder = Database::escape_string($folder);
2922
        $folderWithSuffix = Database::escape_string($folderWithSuffix);
2923
2924
        // Check if pathname already exists inside document table
2925
        $tbl_document = Database::get_course_table(TABLE_DOCUMENT);
2926
        $sql = "SELECT iid, path FROM $tbl_document
2927
                WHERE
2928
                    filetype = 'folder' AND
2929
                    c_id = $courseId AND
2930
                    (path = '$folder' OR path = '$folderWithSuffix') AND
2931
                    (session_id = 0 OR session_id IS NULL OR session_id = $sessionId)
2932
        ";
2933
2934
        $rs = Database::query($sql);
2935
        if (Database::num_rows($rs)) {
2936
            return true;
2937
        }
2938
2939
        return false;
2940
    }
2941
2942
    /**
2943
     * Check if file exist in the course base or in the session course.
2944
     *
2945
     * @param string $fileName   Example: /folder/picture.jpg
2946
     * @param array  $courseInfo
2947
     * @param int    $sessionId
2948
     * @param int    $groupId
2949
     *
2950
     * @return bool
2951
     */
2952
    public static function documentExists(
2953
        $fileName,
2954
        $courseInfo,
2955
        $sessionId,
2956
        $groupId
2957
    ) {
2958
        $courseId = $courseInfo['real_id'];
2959
2960
        if (empty($courseId)) {
2961
            return false;
2962
        }
2963
2964
        $sessionId = (int) $sessionId;
2965
        $fileNameEscape = Database::escape_string($fileName);
2966
2967
        $fileNameWithSuffix = self::fixDocumentName(
2968
            $fileName,
2969
            'file',
2970
            $courseInfo,
2971
            $sessionId,
2972
            $groupId
2973
        );
2974
2975
        $fileNameWithSuffix = Database::escape_string($fileNameWithSuffix);
2976
2977
        // Check if pathname already exists inside document table
2978
        $table = Database::get_course_table(TABLE_DOCUMENT);
2979
        $sql = "SELECT iid, title FROM $table
2980
                WHERE
2981
                    filetype = 'file' AND
2982
                    c_id = $courseId AND
2983
                    (
2984
                        title = '".$fileNameEscape."' OR
2985
                        title = '$fileNameWithSuffix'
2986
                    ) AND
2987
                    (session_id = 0 OR session_id = $sessionId)
2988
        ";
2989
        $rs = Database::query($sql);
2990
        if (Database::num_rows($rs)) {
2991
            return true;
2992
        }
2993
2994
        return false;
2995
    }
2996
2997
    /**
2998
     * @param string $path
2999
     * @param string $name
3000
     * @param array  $courseInfo
3001
     * @param int    $sessionId
3002
     * @param int    $groupId
3003
     *
3004
     * @return string
3005
     */
3006
    public static function getUniqueFileName($path, $name, $courseInfo, $sessionId, $groupId)
3007
    {
3008
        $counter = 1;
3009
        $filePath = $path.$name;
3010
        $uniqueName = $name;
3011
        $baseName = pathinfo($name, PATHINFO_FILENAME);
3012
        $extension = pathinfo($name, PATHINFO_EXTENSION);
3013
3014
        return uniqid($baseName.'-', true).'.'.$extension;
3015
3016
        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...
3017
            $filePath,
3018
            $courseInfo,
3019
            $sessionId,
3020
            $groupId
3021
        )) {
3022
            $uniqueName = self::addSuffixToFileName($name, '_'.$counter);
3023
            $filePath = $path.$uniqueName;
3024
            $counter++;
3025
        }
3026
3027
        return $uniqueName;
3028
    }
3029
3030
    /**
3031
     * Builds the form that enables the user to
3032
     * select a directory to browse/upload in.
3033
     *
3034
     * @param array    An array containing the folders we want to be able to select
3035
     * @param string    The current folder (path inside of the "document" directory, including the prefix "/")
3036
     * @param string    Group directory, if empty, prevents documents to be uploaded
3037
     * (because group documents cannot be uploaded in root)
3038
     * @param bool    Whether to change the renderer (this will add a template <span>
3039
     * to the QuickForm object displaying the form)
3040
     *
3041
     * @return string html form
3042
     */
3043
    public static function build_directory_selector(
3044
        $folders,
3045
        $document_id,
3046
        $group_dir = '',
3047
        $change_renderer = false,
3048
        &$form = null,
3049
        $selectName = 'id'
3050
    ) {
3051
        $doc_table = Database::get_course_table(TABLE_DOCUMENT);
3052
        $course_id = api_get_course_int_id();
3053
        $folder_titles = [];
3054
3055
        if (is_array($folders)) {
3056
            $escaped_folders = [];
3057
            foreach ($folders as $key => &$val) {
3058
                $escaped_folders[$key] = Database::escape_string($val);
3059
            }
3060
            $folder_sql = implode("','", $escaped_folders);
3061
3062
            $sql = "SELECT DISTINCT docs.title, n.path
3063
                    FROM resource_node AS n
3064
                    INNER JOIN $doc_table AS docs
3065
                    ON (docs.resource_node_id = n.id)
3066
                    INNER JOIN resource_link l
3067
                    ON (l.resource_node_id = n.id)
3068
                    WHERE
3069
                        l.c_id = $course_id AND
3070
                        docs.filetype = 'folder' AND
3071
                        n.path IN ('".$folder_sql."') AND
3072
                        l.deleted_at IS NULL
3073
                         ";
3074
3075
            /*$sql = "SELECT path, title
3076
                    FROM $doc_table
3077
                    WHERE
3078
                        filetype = 'folder' AND
3079
                        c_id = $course_id AND
3080
                        path IN ('".$folder_sql."') ";*/
3081
            $res = Database::query($sql);
3082
            $folder_titles = [];
3083
            while ($obj = Database::fetch_object($res)) {
3084
                $folder_titles[$obj->path] = $obj->title;
3085
            }
3086
        }
3087
3088
        $attributes = [];
3089
        if (empty($form)) {
3090
            $form = new FormValidator('selector', 'GET', api_get_self().'?'.api_get_cidreq());
3091
            $attributes = ['onchange' => 'javascript: document.selector.submit();'];
3092
        }
3093
        $form->addElement('hidden', 'cidReq', api_get_course_id());
3094
        $form->addElement('hidden', 'cid', api_get_course_int_id());
3095
        $form->addElement('hidden', 'sid', api_get_session_id());
3096
        $form->addElement('hidden', 'gid', api_get_group_id());
3097
3098
        $parent_select = $form->addSelect(
3099
            $selectName,
3100
            get_lang('Current folder'),
3101
            [],
3102
            $attributes
3103
        );
3104
3105
        // Group documents cannot be uploaded in the root
3106
        if (empty($group_dir)) {
3107
            $parent_select->addOption(get_lang('Documents'), '/');
3108
3109
            if (is_array($folders)) {
3110
                foreach ($folders as $folder_id => &$folder) {
3111
                    if (!isset($folder_titles[$folder])) {
3112
                        continue;
3113
                    }
3114
                    $selected = ($document_id == $folder_id) ? ' selected="selected"' : '';
3115
                    $path_parts = explode('/', $folder);
3116
                    $folder_titles[$folder] = cut($folder_titles[$folder], 80);
3117
                    $counter = count($path_parts) - 2;
3118
                    if ($counter > 0) {
3119
                        $label = str_repeat('&nbsp;&nbsp;&nbsp;', $counter).' &mdash; '.$folder_titles[$folder];
3120
                    } else {
3121
                        $label = ' &mdash; '.$folder_titles[$folder];
3122
                    }
3123
                    $parent_select->addOption($label, $folder_id);
3124
                    if ('' != $selected) {
3125
                        $parent_select->setSelected($folder_id);
3126
                    }
3127
                }
3128
            }
3129
        } else {
3130
            if (!empty($folders)) {
3131
                foreach ($folders as $folder_id => &$folder) {
3132
                    $selected = ($document_id == $folder_id) ? ' selected="selected"' : '';
3133
                    $label = $folder_titles[$folder];
3134
                    if ($folder == $group_dir) {
3135
                        $label = get_lang('Documents');
3136
                    } else {
3137
                        $path_parts = explode('/', str_replace($group_dir, '', $folder));
3138
                        $label = cut($label, 80);
3139
                        $label = str_repeat('&nbsp;&nbsp;&nbsp;', count($path_parts) - 2).' &mdash; '.$label;
3140
                    }
3141
                    $parent_select->addOption($label, $folder_id);
3142
                    if ('' != $selected) {
3143
                        $parent_select->setSelected($folder_id);
3144
                    }
3145
                }
3146
            }
3147
        }
3148
3149
        return $form->toHtml();
3150
    }
3151
3152
    /**
3153
     * Builds an img html tag for the file type.
3154
     *
3155
     * @param string $type            (file/folder)
3156
     * @param string $path
3157
     * @param bool   $isAllowedToEdit
3158
     *
3159
     * @return string img html tag
3160
     */
3161
    public static function build_document_icon_tag($type, $path, $isAllowedToEdit = null)
3162
    {
3163
        $basename = basename($path);
3164
        $sessionId = api_get_session_id();
3165
        if (is_null($isAllowedToEdit)) {
3166
            $isAllowedToEdit = api_is_allowed_to_edit(null, true);
3167
        }
3168
        $user_image = false;
3169
        if ('file' == $type) {
3170
            $icon = choose_image($basename);
3171
            $basename = substr(strrchr($basename, '.'), 1);
3172
        } elseif ('link' == $type) {
3173
            $icon = 'clouddoc.png';
3174
            $basename = get_lang('Cloud file link');
3175
        } else {
3176
            if ('/shared_folder' == $path) {
3177
                $icon = 'folder_users.png';
3178
                if ($isAllowedToEdit) {
3179
                    $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3180
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.');
3181
                } else {
3182
                    $basename = get_lang('Folders of users');
3183
                }
3184
            } elseif (strstr($basename, 'sf_user_')) {
3185
                $userInfo = api_get_user_info(substr($basename, 8));
3186
                $icon = $userInfo['avatar_small'];
3187
                $basename = get_lang('User folder').' '.$userInfo['complete_name'];
3188
                $user_image = true;
3189
            } elseif (strstr($path, 'shared_folder_session_')) {
3190
                $sessionName = api_get_session_name($sessionId);
3191
                if ($isAllowedToEdit) {
3192
                    $basename = '***('.$sessionName.')*** '.get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3193
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.');
3194
                } else {
3195
                    $basename = get_lang('Folders of users').' ('.$sessionName.')';
3196
                }
3197
                $icon = 'folder_users.png';
3198
            } else {
3199
                $icon = 'folder_document.png';
3200
3201
                if ('/audio' == $path) {
3202
                    $icon = 'folder_audio.png';
3203
                    if ($isAllowedToEdit) {
3204
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3205
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.');
3206
                    } else {
3207
                        $basename = get_lang('Audio');
3208
                    }
3209
                } elseif ('/flash' == $path) {
3210
                    $icon = 'folder_flash.png';
3211
                    if ($isAllowedToEdit) {
3212
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3213
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.');
3214
                    } else {
3215
                        $basename = get_lang('Flash');
3216
                    }
3217
                } elseif ('/images' == $path) {
3218
                    $icon = 'folder_images.png';
3219
                    if ($isAllowedToEdit) {
3220
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3221
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.');
3222
                    } else {
3223
                        $basename = get_lang('Images');
3224
                    }
3225
                } elseif ('/video' == $path) {
3226
                    $icon = 'folder_video.png';
3227
                    if ($isAllowedToEdit) {
3228
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3229
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.');
3230
                    } else {
3231
                        $basename = get_lang('Video');
3232
                    }
3233
                } elseif ('/images/gallery' == $path) {
3234
                    $icon = 'folder_gallery.png';
3235
                    if ($isAllowedToEdit) {
3236
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3237
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.');
3238
                    } else {
3239
                        $basename = get_lang('Gallery');
3240
                    }
3241
                } elseif ('/chat_files' == $path) {
3242
                    $icon = 'folder_chat.png';
3243
                    if ($isAllowedToEdit) {
3244
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3245
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.');
3246
                    } else {
3247
                        $basename = get_lang('Chat conversations history');
3248
                    }
3249
                } elseif ('/learning_path' == $path) {
3250
                    $icon = 'folder_learningpath.png';
3251
                    if ($isAllowedToEdit) {
3252
                        $basename = get_lang('HelpFolderLearning paths');
3253
                    } else {
3254
                        $basename = get_lang('Learning paths');
3255
                    }
3256
                }
3257
            }
3258
        }
3259
3260
        if ($user_image) {
3261
            return Display::img($icon, $basename, [], false);
3262
        }
3263
3264
        return Display::return_icon($icon, $basename);
3265
    }
3266
3267
    public static function isBasicCourseFolder($path, $sessionId)
3268
    {
3269
        $cleanPath = Security::remove_XSS($path);
3270
        $basicCourseFolder = '/basic-course-documents__'.$sessionId.'__0';
3271
3272
        return $cleanPath == $basicCourseFolder;
3273
    }
3274
3275
    /**
3276
     * Adds a new document to the database.
3277
     *
3278
     * @param array  $courseInfo
3279
     * @param string $path
3280
     * @param string $fileType
3281
     * @param int    $fileSize
3282
     * @param string $title
3283
     * @param string $comment
3284
     * @param int    $readonly
3285
     * @param int    $visibility       see ResourceLink constants
3286
     * @param int    $groupId          group.id
3287
     * @param int    $sessionId        Session ID, if any
3288
     * @param int    $userId           creator user id
3289
     * @param bool   $sendNotification
3290
     * @param string $content
3291
     * @param int    $parentId
3292
     * @param string $realPath
3293
     *
3294
     * @return CDocument|false
3295
     */
3296
    public static function addDocument(
3297
        $courseInfo,
3298
        $path,
3299
        $fileType,
3300
        $fileSize,
3301
        $title,
3302
        $comment = null,
3303
        $readonly = 0,
3304
        $visibility = null,
3305
        $groupId = 0,
3306
        $sessionId = 0,
3307
        $userId = 0,
3308
        $sendNotification = true,
3309
        $content = '',
3310
        $parentId = 0,
3311
        $realPath = ''
3312
    ) {
3313
        $userId = empty($userId) ? api_get_user_id() : $userId;
3314
        if (empty($userId)) {
3315
            return false;
3316
        }
3317
3318
        $userEntity = api_get_user_entity($userId);
3319
        if (null === $userEntity) {
3320
            return false;
3321
        }
3322
3323
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
3324
        if (null === $courseEntity) {
3325
            return false;
3326
        }
3327
3328
        $sessionId = empty($sessionId) ? api_get_session_id() : $sessionId;
3329
        $session = api_get_session_entity($sessionId);
3330
        $group = api_get_group_entity($groupId);
3331
        $readonly = (int) $readonly;
3332
        $documentRepo = Container::getDocumentRepository();
3333
3334
        /** @var \Chamilo\CoreBundle\Entity\AbstractResource $parentResource */
3335
        $parentResource = $courseEntity;
3336
        if (!empty($parentId)) {
3337
            $parent = $documentRepo->find($parentId);
3338
            if ($parent) {
3339
                $parentResource = $parent;
3340
            }
3341
        }
3342
3343
        $document = $documentRepo->findCourseResourceByTitle(
3344
            $title,
3345
            $parentResource->getResourceNode(),
3346
            $courseEntity,
3347
            $session,
3348
            $group
3349
        );
3350
3351
        // Document already exists
3352
        if (null !== $document) {
3353
            return $document;
3354
        }
3355
3356
        // is updated using the title
3357
        $document = (new CDocument())
3358
            ->setFiletype($fileType)
3359
            ->setTitle($title)
3360
            ->setComment($comment)
3361
            ->setReadonly(1 === $readonly)
3362
            ->setCreator(api_get_user_entity())
3363
            ->setParent($parentResource)
3364
            ->addCourseLink($courseEntity, $session, $group)
3365
        ;
3366
3367
        $em = Database::getManager();
3368
        $em->persist($document);
3369
        $em->flush();
3370
3371
        $repo = Container::getDocumentRepository();
3372
        if (!empty($content)) {
3373
            $repo->addFileFromString($document, $title, 'text/html', $content, true);
3374
        } else {
3375
            if (!empty($realPath) && !is_dir($realPath) && file_exists($realPath)) {
3376
                $repo->addFileFromPath($document, $title, $realPath);
3377
            }
3378
        }
3379
3380
        if ($document) {
3381
            $allowNotification = ('true' === api_get_setting('document.send_notification_when_document_added'));
3382
            if ($sendNotification && $allowNotification) {
3383
                $courseTitle = $courseEntity->getTitle();
3384
                if (!empty($sessionId)) {
3385
                    $sessionInfo = api_get_session_info($sessionId);
3386
                    $courseTitle .= ' ( '.$sessionInfo['name'].') ';
3387
                }
3388
3389
                $url = api_get_path(WEB_CODE_PATH).
3390
                    'document/showinframes.php?cid='.$courseEntity->getId().'&sid='.$sessionId.'&id='.$document->getIid();
3391
                $link = Display::url(basename($title), $url, ['target' => '_blank']);
3392
                $userInfo = api_get_user_info($userId);
3393
                $message = sprintf(
3394
                    get_lang('A new document %s has been added to the document tool in your course %s by %s.'),
3395
                    $link,
3396
                    $courseTitle,
3397
                    $userInfo['complete_name']
3398
                );
3399
                $subject = sprintf(get_lang('New document added to course %s'), $courseTitle);
3400
                MessageManager::sendMessageToAllUsersInCourse($subject, $message, $courseEntity, $sessionId);
3401
            }
3402
3403
            return $document;
3404
        }
3405
3406
        return false;
3407
    }
3408
}
3409