DocumentManager::build_directory_selector()   C
last analyzed

Complexity

Conditions 17
Paths 16

Size

Total Lines 107
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 60
nc 16
nop 6
dl 0
loc 107
rs 5.2166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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