Passed
Push — master ( 68f627...939207 )
by
unknown
12:17 queued 03:34
created

DocumentManager::get_document_data_by_id()   F

Complexity

Conditions 19
Paths 435

Size

Total Lines 101
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 64
c 0
b 0
f 0
nc 435
nop 5
dl 0
loc 101
rs 1.1347

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

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

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