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

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

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