Passed
Push — master ( 5a5c46...59256e )
by Julito
08:25
created

DocumentManager::get_system_folders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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