DocumentManager::setDocumentAsTemplate()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
nc 1
nop 6
dl 0
loc 21
rs 9.9332
c 0
b 0
f 0
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
        array $filterByExtension = [],
2227
        array $excludeByExtension = [],
2228
               $filterByFiletype = null,
2229
        bool $flattenRoot = false
2230
    ) {
2231
        $repo = Container::getDocumentRepository();
2232
        $nodeRepository = $repo->getResourceNodeRepository();
2233
        $move = get_lang('Move');
2234
        $icon = '<i class="mdi-cursor-move mdi ch-tool-icon" style="font-size:16px;width:16px;height:16px;" title="'.htmlentities($move).'"></i>';
2235
        $folderIcon = Display::getMdiIcon(ObjectIcon::CHAPTER, 'ch-tool-icon', null, ICON_SIZE_SMALL);
2236
        $fileIcon = '<i class="mdi-file mdi ch-tool-icon" style="font-size:16px;width:16px;height:16px;" aria-hidden="true" title="'
2237
            . htmlentities(get_lang('File'))
2238
            . '"></i>';
2239
2240
        $lpItemType = ($filterByFiletype === 'video') ? 'video' : 'document';
2241
        $options = [
2242
            'decorate'   => true,
2243
            'rootOpen'   => '<ul id="doc_list" class="list-group lp_resource">',
2244
            'rootClose'  => '</ul>',
2245
            'childOpen'  => function ($child) {
2246
                return '<li id="'.$child['id'].'" data-id="'.$child['id'].'" class="list-group-item nested-'.$child['level'].'">';
2247
            },
2248
            'childClose' => '</li>',
2249
            'nodeDecorator' => function ($node) use ($icon, $fileIcon, $lpItemType) {
2250
                $link  = '<div class="flex flex-row gap-1 h-4 item_data">';
2251
                $link .= '<a class="moved ui-sortable-handle" href="#">'.$icon.'</a>';
2252
                $link .= '<a data_id="'.$node['id'].'" data_type="'.$lpItemType.'" class="moved ui-sortable-handle link_with_id">'.$fileIcon.'&nbsp;</a>';
2253
                $link .= cut(addslashes($node['title']), 150);
2254
                $link .= '</div>';
2255
2256
                return $link;
2257
            },
2258
        ];
2259
2260
        $type = $repo->getResourceType();
2261
        $em = Database::getManager();
2262
        $qb = $em
2263
            ->createQueryBuilder()
2264
            ->select('node, files')
2265
            ->from(ResourceNode::class, 'node')
2266
            ->innerJoin('node.resourceType', 'type')
2267
            ->innerJoin('node.resourceLinks', 'links')
2268
            ->innerJoin('node.resourceFiles', 'files')
2269
            ->innerJoin(
2270
                CDocument::class,
2271
                'doc',
2272
                'WITH',
2273
                'doc.resourceNode = node'
2274
            )
2275
            ->addSelect('files')
2276
            ->where('type = :type')
2277
            ->andWhere('links.course = :course')
2278
            ->setParameters([
2279
                'type' => $type,
2280
                'course' => $course,
2281
            ])
2282
            ->orderBy('node.parent', 'ASC');
2283
2284
        $sessionId = api_get_session_id();
2285
        if (empty($sessionId)) {
2286
            $qb->andWhere('links.session IS NULL');
2287
        } else {
2288
            $qb
2289
                ->andWhere('links.session = :session')
2290
                ->setParameter('session', $sessionId);
2291
        }
2292
2293
        if ($filterByFiletype !== null) {
2294
            if (is_array($filterByFiletype)) {
2295
                $qb->andWhere('doc.filetype IN (:filetypes)');
2296
                $qb->setParameter('filetypes', $filterByFiletype);
2297
            } else {
2298
                $qb->andWhere('doc.filetype = :filetype');
2299
                $qb->setParameter('filetype', $filterByFiletype);
2300
            }
2301
        }
2302
2303
        if (!empty($filterByExtension)) {
2304
            $orX = $qb->expr()->orX();
2305
            foreach ($filterByExtension as $extension) {
2306
                $paramName = 'ext_' . $extension;
2307
                $orX->add(
2308
                    $qb->expr()->like(
2309
                        'LOWER(files.originalName)',
2310
                        ':' . $paramName
2311
                    )
2312
                );
2313
                $qb->setParameter($paramName, '%.' . strtolower($extension));
2314
            }
2315
            $qb->andWhere($orX);
2316
        }
2317
2318
        if (!empty($excludeByExtension)) {
2319
            foreach ($excludeByExtension as $extension) {
2320
                $qb->andWhere(
2321
                    $qb->expr()->notLike(
2322
                        'LOWER(files.originalName)',
2323
                        ':exclude_' . $extension
2324
                    )
2325
                );
2326
                $qb->setParameter('exclude_' . $extension, '%.' . strtolower($extension));
2327
            }
2328
        }
2329
2330
        $items = $qb->getQuery()->getArrayResult();
2331
2332
        if ($flattenRoot) {
2333
            foreach ($items as &$item) {
2334
                $item['level'] = 0;
2335
            }
2336
            unset($item);
2337
        }
2338
2339
        return $nodeRepository->buildTree($items, $options);
2340
    }
2341
2342
    /**
2343
     * Index a given document.
2344
     *
2345
     * @param   int     Document ID inside its corresponding course
2346
     * @param   string  Course code
2347
     * @param   int     Session ID (not used yet)
2348
     * @param   string  Language of document's content (defaults to course language)
2349
     * @param   array   Array of specific fields (['code'=>'value',...])
2350
     * @param   string  What to do if the file already exists (default or overwrite)
2351
     * @param   bool    When set to true, this runs the indexer without actually saving anything to any database
2352
     *
2353
     * @return bool Returns true on presumed success, false on failure
2354
     */
2355
    public static function index_document(
2356
        $docid,
2357
        $course_code,
2358
        $session_id = 0,
2359
        $lang = 'english',
2360
        $specific_fields_values = [],
2361
        $if_exists = '',
2362
        $simulation = false
2363
    ) {
2364
        if ('true' !== api_get_setting('search_enabled')) {
2365
            return false;
2366
        }
2367
        if (empty($docid) or $docid != intval($docid)) {
2368
            return false;
2369
        }
2370
        if (empty($session_id)) {
2371
            $session_id = api_get_session_id();
2372
        }
2373
        $course_info = api_get_course_info($course_code);
2374
        $course_dir = $course_info['path'].'/document';
2375
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
2376
        $base_work_dir = $sys_course_path.$course_dir;
2377
2378
        $course_id = $course_info['real_id'];
2379
        $table_document = Database::get_course_table(TABLE_DOCUMENT);
2380
2381
        $qry = "SELECT path, title FROM $table_document WHERE c_id = $course_id AND id = '$docid' LIMIT 1";
2382
        $result = Database::query($qry);
2383
        if (1 == Database::num_rows($result)) {
2384
            $row = Database::fetch_array($result);
2385
            $doc_path = api_get_path(SYS_COURSE_PATH).$course_dir.$row['path'];
2386
            //TODO: mime_content_type is deprecated, fileinfo php extension is enabled by default as of PHP 5.3.0
2387
            // now versions of PHP on Debian testing(5.2.6-5) and Ubuntu(5.2.6-2ubuntu) are lower, so wait for a while
2388
            $doc_mime = mime_content_type($doc_path);
2389
            $allowed_mime_types = self::file_get_mime_type(true);
2390
2391
            // mime_content_type does not detect correctly some formats that
2392
            // are going to be supported for index, so an extensions array is used for the moment
2393
            if (empty($doc_mime)) {
2394
                $allowed_extensions = [
2395
                    'doc',
2396
                    'docx',
2397
                    'ppt',
2398
                    'pptx',
2399
                    'pps',
2400
                    'ppsx',
2401
                    'xls',
2402
                    'xlsx',
2403
                    'odt',
2404
                    'odp',
2405
                    'ods',
2406
                    'pdf',
2407
                    'txt',
2408
                    'rtf',
2409
                    'msg',
2410
                    'csv',
2411
                    'html',
2412
                    'htm',
2413
                ];
2414
                $extensions = preg_split("/[\/\\.]/", $doc_path);
2415
                $doc_ext = strtolower($extensions[count($extensions) - 1]);
2416
                if (in_array($doc_ext, $allowed_extensions)) {
2417
                    switch ($doc_ext) {
2418
                        case 'ppt':
2419
                        case 'pps':
2420
                            $doc_mime = 'application/vnd.ms-powerpoint';
2421
                            break;
2422
                        case 'xls':
2423
                            $doc_mime = 'application/vnd.ms-excel';
2424
                            break;
2425
                    }
2426
                }
2427
            }
2428
2429
            //@todo move this nightmare in a search controller or something like that!!! J.M
2430
2431
            if (in_array($doc_mime, $allowed_mime_types)) {
2432
                $file_title = $row['title'];
2433
                $file_content = self::get_text_content($doc_path, $doc_mime);
2434
                $course_code = Database::escape_string($course_code);
2435
                $ic_slide = new IndexableChunk();
2436
                $ic_slide->addValue('title', $file_title);
2437
                $ic_slide->addCourseId($course_code);
2438
                $ic_slide->addToolId(TOOL_DOCUMENT);
2439
                $xapian_data = [
2440
                    SE_COURSE_ID => $course_code,
2441
                    SE_TOOL_ID => TOOL_DOCUMENT,
2442
                    SE_DATA => ['doc_id' => $docid],
2443
                    SE_USER => api_get_user_id(),
2444
                ];
2445
2446
                $ic_slide->xapian_data = serialize($xapian_data);
2447
                $di = new ChamiloIndexer();
2448
                $return = $di->connectDb(null, null, $lang);
2449
2450
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
2451
                $specific_fields = get_specific_field_list();
2452
2453
                // process different depending on what to do if file exists
2454
                /**
2455
                 * @TODO Find a way to really verify if the file had been
2456
                 * overwriten. Now all work is done at
2457
                 * handle_uploaded_document() and it's difficult to verify it
2458
                 */
2459
                if (!empty($if_exists) && 'overwrite' == $if_exists) {
2460
                    // Overwrite the file on search engine
2461
                    // Actually, it consists on a delete of terms from db,
2462
                    // insert new ones, create a new search engine document,
2463
                    // and remove the old one
2464
                    // Get search_did
2465
                    $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2466
                    $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
2467
                    $sql = sprintf($sql, $tbl_se_ref, $course_code, TOOL_DOCUMENT, $docid);
2468
2469
                    $res = Database::query($sql);
2470
2471
                    if (Database::num_rows($res) > 0) {
2472
                        $se_ref = Database::fetch_array($res);
2473
                        if (!$simulation) {
2474
                            $di->remove_document($se_ref['search_did']);
2475
                        }
2476
                        $all_specific_terms = '';
2477
                        foreach ($specific_fields as $specific_field) {
2478
                            if (!$simulation) {
2479
                                delete_all_specific_field_value($course_code, $specific_field['id'], TOOL_DOCUMENT, $docid);
2480
                            }
2481
                            // Update search engine
2482
                            if (isset($specific_fields_values[$specific_field['code']])) {
2483
                                $sterms = trim($specific_fields_values[$specific_field['code']]);
2484
                            } else { //if the specific field is not defined, force an empty one
2485
                                $sterms = '';
2486
                            }
2487
                            $all_specific_terms .= ' '.$sterms;
2488
                            $sterms = explode(',', $sterms);
2489
                            foreach ($sterms as $sterm) {
2490
                                $sterm = trim($sterm);
2491
                                if (!empty($sterm)) {
2492
                                    $ic_slide->addTerm($sterm, $specific_field['code']);
2493
                                    // updated the last param here from $value to $sterm without being sure - see commit15464
2494
                                    if (!$simulation) {
2495
                                        add_specific_field_value(
2496
                                            $specific_field['id'],
2497
                                            $course_code,
2498
                                            TOOL_DOCUMENT,
2499
                                            $docid,
2500
                                            $sterm
2501
                                        );
2502
                                    }
2503
                                }
2504
                            }
2505
                        }
2506
                        // Add terms also to content to make terms findable by probabilistic search
2507
                        $file_content = $all_specific_terms.' '.$file_content;
2508
2509
                        if (!$simulation) {
2510
                            $ic_slide->addValue('content', $file_content);
2511
                            $di->addChunk($ic_slide);
2512
                            // Index and return a new search engine document id
2513
                            $did = $di->index();
2514
2515
                            if ($did) {
2516
                                // update the search_did on db
2517
                                $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2518
                                $sql = 'UPDATE %s SET search_did=%d WHERE id=%d LIMIT 1';
2519
                                $sql = sprintf($sql, $tbl_se_ref, (int) $did, (int) $se_ref['id']);
2520
                                Database::query($sql);
2521
                            }
2522
                        }
2523
                    }
2524
                } else {
2525
                    // Add all terms
2526
                    $all_specific_terms = '';
2527
                    foreach ($specific_fields as $specific_field) {
2528
                        if (isset($specific_fields_values[$specific_field['code']])) {
2529
                            $sterms = trim($specific_fields_values[$specific_field['code']]);
2530
                        } else { //if the specific field is not defined, force an empty one
2531
                            $sterms = '';
2532
                        }
2533
                        $all_specific_terms .= ' '.$sterms;
2534
                        if (!empty($sterms)) {
2535
                            $sterms = explode(',', $sterms);
2536
                            foreach ($sterms as $sterm) {
2537
                                if (!$simulation) {
2538
                                    $ic_slide->addTerm(trim($sterm), $specific_field['code']);
2539
                                    add_specific_field_value(
2540
                                        $specific_field['id'],
2541
                                        $course_code,
2542
                                        TOOL_DOCUMENT,
2543
                                        $docid,
2544
                                        $sterm
2545
                                    );
2546
                                }
2547
                            }
2548
                        }
2549
                    }
2550
                    // Add terms also to content to make terms findable by probabilistic search
2551
                    $file_content = $all_specific_terms.' '.$file_content;
2552
                    if (!$simulation) {
2553
                        $ic_slide->addValue('content', $file_content);
2554
                        $di->addChunk($ic_slide);
2555
                        // Index and return search engine document id
2556
                        $did = $di->index();
2557
                        if ($did) {
2558
                            // Save it to db
2559
                            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
2560
                            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
2561
                            VALUES (NULL , \'%s\', \'%s\', %s, %s)';
2562
                            $sql = sprintf($sql, $tbl_se_ref, $course_code, TOOL_DOCUMENT, $docid, $did);
2563
                            Database::query($sql);
2564
                        } else {
2565
                            return false;
2566
                        }
2567
                    }
2568
                }
2569
            } else {
2570
                return false;
2571
            }
2572
        }
2573
2574
        return true;
2575
    }
2576
2577
    /**
2578
     * @param string $path
2579
     * @param bool   $is_certificate_mode
2580
     *
2581
     * @return bool
2582
     */
2583
    public static function is_folder_to_avoid($path, $is_certificate_mode = false)
2584
    {
2585
        $foldersToAvoid = [
2586
            '/HotPotatoes_files',
2587
            '/certificates',
2588
        ];
2589
        $systemFolder = api_get_course_setting('show_system_folders');
2590
2591
        if (1 == $systemFolder) {
2592
            $foldersToAvoid = [];
2593
        }
2594
2595
        if ('css' == basename($path)) {
2596
            return true;
2597
        }
2598
2599
        if (false == $is_certificate_mode) {
2600
            //Certificate results
2601
            if (strstr($path, 'certificates')) {
2602
                return true;
2603
            }
2604
        }
2605
2606
        // Admin setting for Hide/Show the folders of all users
2607
        if ('false' == api_get_setting('show_users_folders')) {
2608
            $foldersToAvoid[] = '/shared_folder';
2609
2610
            if (strstr($path, 'shared_folder_session_')) {
2611
                return true;
2612
            }
2613
        }
2614
2615
        // Admin setting for Hide/Show Default folders to all users
2616
        if ('false' == api_get_setting('show_default_folders')) {
2617
            $foldersToAvoid[] = '/images';
2618
            $foldersToAvoid[] = '/flash';
2619
            $foldersToAvoid[] = '/audio';
2620
            $foldersToAvoid[] = '/video';
2621
        }
2622
2623
        // Admin setting for Hide/Show chat history folder
2624
        if ('false' == api_get_setting('show_chat_folder')) {
2625
            $foldersToAvoid[] = '/chat_files';
2626
        }
2627
2628
        if (is_array($foldersToAvoid)) {
2629
            return in_array($path, $foldersToAvoid);
2630
        } else {
2631
            return false;
2632
        }
2633
    }
2634
2635
    /**
2636
     * @return array
2637
     */
2638
    public static function get_system_folders()
2639
    {
2640
        return [
2641
            '/certificates',
2642
            '/HotPotatoes_files',
2643
            '/chat_files',
2644
            '/images',
2645
            '/flash',
2646
            '/audio',
2647
            '/video',
2648
            '/shared_folder',
2649
            '/learning_path',
2650
        ];
2651
    }
2652
2653
    /**
2654
     * @return array
2655
     */
2656
    public static function getProtectedFolderFromStudent()
2657
    {
2658
        return [
2659
            '/certificates',
2660
            '/HotPotatoes_files',
2661
            '/chat_files',
2662
            '/shared_folder',
2663
            '/learning_path',
2664
        ];
2665
    }
2666
2667
    /**
2668
     * @param string $courseCode
2669
     *
2670
     * @return string 'visible' or 'invisible' string
2671
     */
2672
    public static function getDocumentDefaultVisibility($courseCode)
2673
    {
2674
        $settings = api_get_setting('tool_visible_by_default_at_creation');
2675
        $defaultVisibility = 'visible';
2676
2677
        if (isset($settings['documents'])) {
2678
            $portalDefaultVisibility = 'invisible';
2679
            if ('true' == $settings['documents']) {
2680
                $portalDefaultVisibility = 'visible';
2681
            }
2682
2683
            $defaultVisibility = $portalDefaultVisibility;
2684
        }
2685
2686
        if ('true' === api_get_setting('documents_default_visibility_defined_in_course')) {
2687
            $courseVisibility = api_get_course_setting('documents_default_visibility', $courseCode);
2688
            if (!empty($courseVisibility) && in_array($courseVisibility, ['visible', 'invisible'])) {
2689
                $defaultVisibility = $courseVisibility;
2690
            }
2691
        }
2692
2693
        return $defaultVisibility;
2694
    }
2695
2696
    /**
2697
     * @param array $_course
2698
     *
2699
     * @return CDocument
2700
     */
2701
    public static function createDefaultAudioFolder($_course)
2702
    {
2703
        if (!isset($_course['path'])) {
2704
            return false;
2705
        }
2706
2707
        return self::addDocument($_course, '/audio', 'folder', 0, 'Audio');
2708
    }
2709
2710
    /**
2711
     * Generate a default certificate for a courses.
2712
     *
2713
     * @todo move to certificate lib
2714
     *
2715
     * @global string $css CSS directory
2716
     * @global string $img_dir image directory
2717
     * @global string $default_course_dir Course directory
2718
     * @global string $js JS directory
2719
     *
2720
     * @param array $courseData     The course info
2721
     * @param bool  $fromBaseCourse
2722
     * @param int   $sessionId
2723
     */
2724
    public static function generateDefaultCertificate(
2725
        $courseData,
2726
        $fromBaseCourse = false,
2727
        $sessionId = 0
2728
    ) {
2729
        if (empty($courseData)) {
2730
            return false;
2731
        }
2732
2733
        global $css, $img_dir, $default_course_dir, $js;
2734
        $codePath = api_get_path(REL_CODE_PATH);
2735
        $dir = '/certificates';
2736
        $comment = null;
2737
        $title = get_lang('Default certificate');
2738
        $fileName = api_replace_dangerous_char($title);
2739
        $fileType = 'certificate';
2740
        $templateContent = file_get_contents(api_get_path(SYS_CODE_PATH).'gradebook/certificate_template/template.html');
2741
2742
        $search = ['{CSS}', '{IMG_DIR}', '{REL_CODE_PATH}', '{COURSE_DIR}'];
2743
        $replace = [$css.$js, $img_dir, $codePath, $default_course_dir];
2744
2745
        $fileContent = str_replace($search, $replace, $templateContent);
2746
        $saveFilePath = "$dir/$fileName.html";
2747
2748
        if ($fromBaseCourse) {
2749
            $defaultCertificateId = self::get_default_certificate_id($courseData['real_id'], 0);
2750
            if (!empty($defaultCertificateId)) {
2751
                // We have a certificate from the course base
2752
                $documentData = self::get_document_data_by_id(
2753
                    $defaultCertificateId,
2754
                    $courseData['code'],
2755
                    false,
2756
                    0
2757
                );
2758
2759
                if (isset($documentData['absolute_path'])) {
2760
                    $fileContent = file_get_contents($documentData['absolute_path']);
2761
                }
2762
            }
2763
        }
2764
2765
        $document = self::addDocument(
2766
            $courseData,
2767
            $saveFilePath,
2768
            $fileType,
2769
            0,
2770
            $title,
2771
            $comment,
2772
            0, //$readonly = 0,
2773
            true, //$save_visibility = true,
2774
            null, //$group_id = null,
2775
            $sessionId,
2776
            0,
2777
            false,
2778
            $fileContent
2779
        );
2780
2781
        $defaultCertificateId = self::get_default_certificate_id($courseData['real_id'], $sessionId);
2782
2783
        if (!isset($defaultCertificateId)) {
2784
            self::attach_gradebook_certificate(
2785
                $courseData['real_id'],
2786
                $document->getIid(),
2787
                $sessionId
2788
            );
2789
        }
2790
    }
2791
2792
    /**
2793
     * Get folder/file suffix.
2794
     *
2795
     * @param array $courseInfo
2796
     * @param int   $sessionId
2797
     * @param int   $groupId
2798
     *
2799
     * @return string
2800
     */
2801
    public static function getDocumentSuffix($courseInfo, $sessionId, $groupId)
2802
    {
2803
        // If no session or group, then no suffix.
2804
        if (empty($sessionId) && empty($groupId)) {
2805
            return '';
2806
        }
2807
2808
        return '__'.(int) $sessionId.'__'.(int) $groupId;
2809
    }
2810
2811
    /**
2812
     * Fix a document name adding session id and group id
2813
     * Turns picture.jpg -> picture__1__2.jpg
2814
     * Where 1 = session id and 2 group id
2815
     * Of session id and group id are empty then the function returns:
2816
     * picture.jpg ->  picture.jpg.
2817
     *
2818
     * @param string $name       folder or file name
2819
     * @param string $type       'folder' or 'file'
2820
     * @param array  $courseInfo
2821
     * @param int    $sessionId
2822
     * @param int    $groupId
2823
     *
2824
     * @return string
2825
     */
2826
    public static function fixDocumentName($name, $type, $courseInfo, $sessionId, $groupId)
2827
    {
2828
        $suffix = self::getDocumentSuffix($courseInfo, $sessionId, $groupId);
2829
2830
        switch ($type) {
2831
            case 'folder':
2832
                $name = $name.$suffix;
2833
                break;
2834
            case 'file':
2835
                $name = self::addSuffixToFileName($name, $suffix);
2836
                break;
2837
        }
2838
2839
        return $name;
2840
    }
2841
2842
    /**
2843
     * Add a suffix to a file Example:
2844
     * /folder/picture.jpg => to /folder/picture_this.jpg
2845
     * where "_this" is the suffix.
2846
     *
2847
     * @param string $name
2848
     * @param string $suffix
2849
     *
2850
     * @return string
2851
     */
2852
    public static function addSuffixToFileName($name, $suffix)
2853
    {
2854
        $extension = pathinfo($name, PATHINFO_EXTENSION);
2855
        $fileName = pathinfo($name, PATHINFO_FILENAME);
2856
        $dir = pathinfo($name, PATHINFO_DIRNAME);
2857
2858
        if ('.' == $dir) {
2859
            $dir = null;
2860
        }
2861
2862
        if (!empty($dir) && '/' != $dir) {
2863
            $dir = $dir.'/';
2864
        }
2865
2866
        $name = $dir.$fileName.$suffix.'.'.$extension;
2867
2868
        return $name;
2869
    }
2870
2871
    /**
2872
     * Check if folder exist in the course base or in the session course.
2873
     *
2874
     * @param string $folder     Example: /folder/folder2
2875
     * @param array  $courseInfo
2876
     * @param int    $sessionId
2877
     * @param int    $groupId    group.id
2878
     *
2879
     * @return bool
2880
     */
2881
    public static function folderExists(
2882
        $folder,
2883
        $courseInfo,
2884
        $sessionId,
2885
        $groupId
2886
    ) {
2887
        $courseId = $courseInfo['real_id'];
2888
2889
        if (empty($courseId)) {
2890
            return false;
2891
        }
2892
2893
        $sessionId = (int) $sessionId;
2894
        $folderWithSuffix = self::fixDocumentName(
2895
            $folder,
2896
            'folder',
2897
            $courseInfo,
2898
            $sessionId,
2899
            $groupId
2900
        );
2901
2902
        $folder = Database::escape_string($folder);
2903
        $folderWithSuffix = Database::escape_string($folderWithSuffix);
2904
2905
        // Check if pathname already exists inside document table
2906
        $tbl_document = Database::get_course_table(TABLE_DOCUMENT);
2907
        $sql = "SELECT iid, path FROM $tbl_document
2908
                WHERE
2909
                    filetype = 'folder' AND
2910
                    c_id = $courseId AND
2911
                    (path = '$folder' OR path = '$folderWithSuffix') AND
2912
                    (session_id = 0 OR session_id IS NULL OR session_id = $sessionId)
2913
        ";
2914
2915
        $rs = Database::query($sql);
2916
        if (Database::num_rows($rs)) {
2917
            return true;
2918
        }
2919
2920
        return false;
2921
    }
2922
2923
    /**
2924
     * Check if file exist in the course base or in the session course.
2925
     *
2926
     * @param string $fileName   Example: /folder/picture.jpg
2927
     * @param array  $courseInfo
2928
     * @param int    $sessionId
2929
     * @param int    $groupId
2930
     *
2931
     * @return bool
2932
     */
2933
    public static function documentExists(
2934
        $fileName,
2935
        $courseInfo,
2936
        $sessionId,
2937
        $groupId
2938
    ) {
2939
        $courseId = $courseInfo['real_id'];
2940
2941
        if (empty($courseId)) {
2942
            return false;
2943
        }
2944
2945
        $sessionId = (int) $sessionId;
2946
        $fileNameEscape = Database::escape_string($fileName);
2947
2948
        $fileNameWithSuffix = self::fixDocumentName(
2949
            $fileName,
2950
            'file',
2951
            $courseInfo,
2952
            $sessionId,
2953
            $groupId
2954
        );
2955
2956
        $fileNameWithSuffix = Database::escape_string($fileNameWithSuffix);
2957
2958
        // Check if pathname already exists inside document table
2959
        $table = Database::get_course_table(TABLE_DOCUMENT);
2960
        $sql = "SELECT iid, title FROM $table
2961
                WHERE
2962
                    filetype = 'file' AND
2963
                    c_id = $courseId AND
2964
                    (
2965
                        title = '".$fileNameEscape."' OR
2966
                        title = '$fileNameWithSuffix'
2967
                    ) AND
2968
                    (session_id = 0 OR session_id = $sessionId)
2969
        ";
2970
        $rs = Database::query($sql);
2971
        if (Database::num_rows($rs)) {
2972
            return true;
2973
        }
2974
2975
        return false;
2976
    }
2977
2978
    /**
2979
     * @param string $path
2980
     * @param string $name
2981
     * @param array  $courseInfo
2982
     * @param int    $sessionId
2983
     * @param int    $groupId
2984
     *
2985
     * @return string
2986
     */
2987
    public static function getUniqueFileName($path, $name, $courseInfo, $sessionId, $groupId)
2988
    {
2989
        $counter = 1;
2990
        $filePath = $path.$name;
2991
        $uniqueName = $name;
2992
        $baseName = pathinfo($name, PATHINFO_FILENAME);
2993
        $extension = pathinfo($name, PATHINFO_EXTENSION);
2994
2995
        return uniqid($baseName.'-', true).'.'.$extension;
2996
2997
        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...
2998
            $filePath,
2999
            $courseInfo,
3000
            $sessionId,
3001
            $groupId
3002
        )) {
3003
            $uniqueName = self::addSuffixToFileName($name, '_'.$counter);
3004
            $filePath = $path.$uniqueName;
3005
            $counter++;
3006
        }
3007
3008
        return $uniqueName;
3009
    }
3010
3011
    /**
3012
     * Builds the form that enables the user to
3013
     * select a directory to browse/upload in.
3014
     *
3015
     * @param array    An array containing the folders we want to be able to select
3016
     * @param string    The current folder (path inside of the "document" directory, including the prefix "/")
3017
     * @param string    Group directory, if empty, prevents documents to be uploaded
3018
     * (because group documents cannot be uploaded in root)
3019
     * @param bool    Whether to change the renderer (this will add a template <span>
3020
     * to the QuickForm object displaying the form)
3021
     *
3022
     * @return string html form
3023
     */
3024
    public static function build_directory_selector(
3025
        $folders,
3026
        $document_id,
3027
        $group_dir = '',
3028
        $change_renderer = false,
3029
        &$form = null,
3030
        $selectName = 'id'
3031
    ) {
3032
        $doc_table = Database::get_course_table(TABLE_DOCUMENT);
3033
        $course_id = api_get_course_int_id();
3034
        $folder_titles = [];
3035
3036
        if (is_array($folders)) {
3037
            $escaped_folders = [];
3038
            foreach ($folders as $key => &$val) {
3039
                $escaped_folders[$key] = Database::escape_string($val);
3040
            }
3041
            $folder_sql = implode("','", $escaped_folders);
3042
3043
            $sql = "SELECT DISTINCT docs.title, n.path
3044
                    FROM resource_node AS n
3045
                    INNER JOIN $doc_table AS docs
3046
                    ON (docs.resource_node_id = n.id)
3047
                    INNER JOIN resource_link l
3048
                    ON (l.resource_node_id = n.id)
3049
                    WHERE
3050
                        l.c_id = $course_id AND
3051
                        docs.filetype = 'folder' AND
3052
                        n.path IN ('".$folder_sql."') AND
3053
                        l.deleted_at IS NULL
3054
                         ";
3055
3056
            /*$sql = "SELECT path, title
3057
                    FROM $doc_table
3058
                    WHERE
3059
                        filetype = 'folder' AND
3060
                        c_id = $course_id AND
3061
                        path IN ('".$folder_sql."') ";*/
3062
            $res = Database::query($sql);
3063
            $folder_titles = [];
3064
            while ($obj = Database::fetch_object($res)) {
3065
                $folder_titles[$obj->path] = $obj->title;
3066
            }
3067
        }
3068
3069
        $attributes = [];
3070
        if (empty($form)) {
3071
            $form = new FormValidator('selector', 'GET', api_get_self().'?'.api_get_cidreq());
3072
            $attributes = ['onchange' => 'javascript: document.selector.submit();'];
3073
        }
3074
        $form->addElement('hidden', 'cidReq', api_get_course_id());
3075
        $form->addElement('hidden', 'cid', api_get_course_int_id());
3076
        $form->addElement('hidden', 'sid', api_get_session_id());
3077
        $form->addElement('hidden', 'gid', api_get_group_id());
3078
3079
        $parent_select = $form->addSelect(
3080
            $selectName,
3081
            get_lang('Current folder'),
3082
            [],
3083
            $attributes
3084
        );
3085
3086
        // Group documents cannot be uploaded in the root
3087
        if (empty($group_dir)) {
3088
            $parent_select->addOption(get_lang('Documents'), '/');
3089
3090
            if (is_array($folders)) {
3091
                foreach ($folders as $folder_id => &$folder) {
3092
                    if (!isset($folder_titles[$folder])) {
3093
                        continue;
3094
                    }
3095
                    $selected = ($document_id == $folder_id) ? ' selected="selected"' : '';
3096
                    $path_parts = explode('/', $folder);
3097
                    $folder_titles[$folder] = cut($folder_titles[$folder], 80);
3098
                    $counter = count($path_parts) - 2;
3099
                    if ($counter > 0) {
3100
                        $label = str_repeat('&nbsp;&nbsp;&nbsp;', $counter).' &mdash; '.$folder_titles[$folder];
3101
                    } else {
3102
                        $label = ' &mdash; '.$folder_titles[$folder];
3103
                    }
3104
                    $parent_select->addOption($label, $folder_id);
3105
                    if ('' != $selected) {
3106
                        $parent_select->setSelected($folder_id);
3107
                    }
3108
                }
3109
            }
3110
        } else {
3111
            if (!empty($folders)) {
3112
                foreach ($folders as $folder_id => &$folder) {
3113
                    $selected = ($document_id == $folder_id) ? ' selected="selected"' : '';
3114
                    $label = $folder_titles[$folder];
3115
                    if ($folder == $group_dir) {
3116
                        $label = get_lang('Documents');
3117
                    } else {
3118
                        $path_parts = explode('/', str_replace($group_dir, '', $folder));
3119
                        $label = cut($label, 80);
3120
                        $label = str_repeat('&nbsp;&nbsp;&nbsp;', count($path_parts) - 2).' &mdash; '.$label;
3121
                    }
3122
                    $parent_select->addOption($label, $folder_id);
3123
                    if ('' != $selected) {
3124
                        $parent_select->setSelected($folder_id);
3125
                    }
3126
                }
3127
            }
3128
        }
3129
3130
        return $form->toHtml();
3131
    }
3132
3133
    /**
3134
     * Builds an img html tag for the file type.
3135
     *
3136
     * @param string $type            (file/folder)
3137
     * @param string $path
3138
     * @param bool   $isAllowedToEdit
3139
     *
3140
     * @return string img html tag
3141
     */
3142
    public static function build_document_icon_tag($type, $path, $isAllowedToEdit = null)
3143
    {
3144
        $basename = basename($path);
3145
        $sessionId = api_get_session_id();
3146
        if (is_null($isAllowedToEdit)) {
3147
            $isAllowedToEdit = api_is_allowed_to_edit(null, true);
3148
        }
3149
        $user_image = false;
3150
        if ('file' == $type) {
3151
            $icon = choose_image($basename);
3152
            $basename = substr(strrchr($basename, '.'), 1);
3153
        } elseif ('link' == $type) {
3154
            $icon = 'clouddoc.png';
3155
            $basename = get_lang('Cloud file link');
3156
        } else {
3157
            if ('/shared_folder' == $path) {
3158
                $icon = 'folder_users.png';
3159
                if ($isAllowedToEdit) {
3160
                    $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3161
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.');
3162
                } else {
3163
                    $basename = get_lang('Folders of users');
3164
                }
3165
            } elseif (strstr($basename, 'sf_user_')) {
3166
                $userInfo = api_get_user_info(substr($basename, 8));
3167
                $icon = $userInfo['avatar_small'];
3168
                $basename = get_lang('User folder').' '.$userInfo['complete_name'];
3169
                $user_image = true;
3170
            } elseif (strstr($path, 'shared_folder_session_')) {
3171
                $sessionName = api_get_session_name($sessionId);
3172
                if ($isAllowedToEdit) {
3173
                    $basename = '***('.$sessionName.')*** '.get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3174
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.');
3175
                } else {
3176
                    $basename = get_lang('Folders of users').' ('.$sessionName.')';
3177
                }
3178
                $icon = 'folder_users.png';
3179
            } else {
3180
                $icon = 'folder_document.png';
3181
3182
                if ('/audio' == $path) {
3183
                    $icon = 'folder_audio.png';
3184
                    if ($isAllowedToEdit) {
3185
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3186
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.');
3187
                    } else {
3188
                        $basename = get_lang('Audio');
3189
                    }
3190
                } elseif ('/flash' == $path) {
3191
                    $icon = 'folder_flash.png';
3192
                    if ($isAllowedToEdit) {
3193
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3194
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.');
3195
                    } else {
3196
                        $basename = get_lang('Flash');
3197
                    }
3198
                } elseif ('/images' == $path) {
3199
                    $icon = 'folder_images.png';
3200
                    if ($isAllowedToEdit) {
3201
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3202
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.');
3203
                    } else {
3204
                        $basename = get_lang('Images');
3205
                    }
3206
                } elseif ('/video' == $path) {
3207
                    $icon = 'folder_video.png';
3208
                    if ($isAllowedToEdit) {
3209
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3210
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.');
3211
                    } else {
3212
                        $basename = get_lang('Video');
3213
                    }
3214
                } elseif ('/images/gallery' == $path) {
3215
                    $icon = 'folder_gallery.png';
3216
                    if ($isAllowedToEdit) {
3217
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3218
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.');
3219
                    } else {
3220
                        $basename = get_lang('Gallery');
3221
                    }
3222
                } elseif ('/chat_files' == $path) {
3223
                    $icon = 'folder_chat.png';
3224
                    if ($isAllowedToEdit) {
3225
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3226
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.');
3227
                    } else {
3228
                        $basename = get_lang('Chat conversations history');
3229
                    }
3230
                } elseif ('/learning_path' == $path) {
3231
                    $icon = 'folder_learningpath.png';
3232
                    if ($isAllowedToEdit) {
3233
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:\nThis folder contains documents which are created by the Learning Path tool. Inside this folder you can edit the HTML file which are generated by importing content from the Learning Path, as the ones imported by Chamilo Rapid, for example. We recommend hiding this folder to your students.');
3234
                    } else {
3235
                        $basename = get_lang('Learning paths');
3236
                    }
3237
                }
3238
            }
3239
        }
3240
3241
        if ($user_image) {
3242
            return Display::img($icon, $basename, [], false);
3243
        }
3244
3245
        return Display::return_icon($icon, $basename);
3246
    }
3247
3248
    public static function isBasicCourseFolder($path, $sessionId)
3249
    {
3250
        $cleanPath = Security::remove_XSS($path);
3251
        $basicCourseFolder = '/basic-course-documents__'.$sessionId.'__0';
3252
3253
        return $cleanPath == $basicCourseFolder;
3254
    }
3255
3256
    /**
3257
     * Adds a new document to the database.
3258
     *
3259
     * @param array  $courseInfo
3260
     * @param string $path
3261
     * @param string $fileType
3262
     * @param int    $fileSize
3263
     * @param string $title
3264
     * @param string $comment
3265
     * @param int    $readonly
3266
     * @param int    $visibility       see ResourceLink constants
3267
     * @param int    $groupId          group.id
3268
     * @param int    $sessionId        Session ID, if any
3269
     * @param int    $userId           creator user id
3270
     * @param bool   $sendNotification
3271
     * @param string $content
3272
     * @param int    $parentId
3273
     * @param string $realPath
3274
     *
3275
     * @return CDocument|false
3276
     */
3277
    public static function addDocument(
3278
        $courseInfo,
3279
        $path,
3280
        $fileType,
3281
        $fileSize,
3282
        $title,
3283
        $comment = null,
3284
        $readonly = 0,
3285
        $visibility = null,
3286
        $groupId = 0,
3287
        $sessionId = 0,
3288
        $userId = 0,
3289
        $sendNotification = true,
3290
        $content = '',
3291
        $parentId = 0,
3292
        $realPath = ''
3293
    ) {
3294
        $userId = empty($userId) ? api_get_user_id() : $userId;
3295
        if (empty($userId)) {
3296
            return false;
3297
        }
3298
3299
        $userEntity = api_get_user_entity($userId);
3300
        if (null === $userEntity) {
3301
            return false;
3302
        }
3303
3304
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
3305
        if (null === $courseEntity) {
3306
            return false;
3307
        }
3308
3309
        $sessionId = empty($sessionId) ? api_get_session_id() : $sessionId;
3310
        $session = api_get_session_entity($sessionId);
3311
        $group = api_get_group_entity($groupId);
3312
        $readonly = (int) $readonly;
3313
        $documentRepo = Container::getDocumentRepository();
3314
3315
        /** @var \Chamilo\CoreBundle\Entity\AbstractResource $parentResource */
3316
        $parentResource = $courseEntity;
3317
        if (!empty($parentId)) {
3318
            $parent = $documentRepo->find($parentId);
3319
            if ($parent) {
3320
                $parentResource = $parent;
3321
            }
3322
        }
3323
3324
        $document = $documentRepo->findCourseResourceByTitle(
3325
            $title,
3326
            $parentResource->getResourceNode(),
3327
            $courseEntity,
3328
            $session,
3329
            $group
3330
        );
3331
3332
        // Document already exists
3333
        if (null !== $document) {
3334
            return $document;
3335
        }
3336
3337
        // is updated using the title
3338
        $document = (new CDocument())
3339
            ->setFiletype($fileType)
3340
            ->setTitle($title)
3341
            ->setComment($comment)
3342
            ->setReadonly(1 === $readonly)
3343
            ->setCreator(api_get_user_entity())
3344
            ->setParent($parentResource)
3345
            ->addCourseLink($courseEntity, $session, $group)
3346
        ;
3347
3348
        $em = Database::getManager();
3349
        $em->persist($document);
3350
        $em->flush();
3351
3352
        $repo = Container::getDocumentRepository();
3353
        if (!empty($content)) {
3354
            $repo->addFileFromString($document, $title, 'text/html', $content, true);
3355
        } else {
3356
            if (!empty($realPath) && !is_dir($realPath) && file_exists($realPath)) {
3357
                $repo->addFileFromPath($document, $title, $realPath);
3358
            }
3359
        }
3360
3361
        if ($document) {
3362
            $allowNotification = ('true' === api_get_setting('document.send_notification_when_document_added'));
3363
            if ($sendNotification && $allowNotification) {
3364
                $courseTitle = $courseEntity->getTitle();
3365
                if (!empty($sessionId)) {
3366
                    $sessionInfo = api_get_session_info($sessionId);
3367
                    $courseTitle .= ' ( '.$sessionInfo['name'].') ';
3368
                }
3369
3370
                $url = api_get_path(WEB_CODE_PATH).
3371
                    'document/showinframes.php?cid='.$courseEntity->getId().'&sid='.$sessionId.'&id='.$document->getIid();
3372
                $link = Display::url(basename($title), $url, ['target' => '_blank']);
3373
                $userInfo = api_get_user_info($userId);
3374
                $message = sprintf(
3375
                    get_lang('A new document %s has been added to the document tool in your course %s by %s.'),
3376
                    $link,
3377
                    $courseTitle,
3378
                    $userInfo['complete_name']
3379
                );
3380
                $subject = sprintf(get_lang('New document added to course %s'), $courseTitle);
3381
                MessageManager::sendMessageToAllUsersInCourse($subject, $message, $courseEntity, $sessionId);
3382
            }
3383
3384
            return $document;
3385
        }
3386
3387
        return false;
3388
    }
3389
}
3390