DocumentManager::displaySimpleQuota()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

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

Loading history...
461
                case 'application/vnd.dwg':
462
                case 'application/vnd.dwf':
463
                    header('Content-type: application/octet-stream');
464
                    break;
465
            }
466
467
            header('Content-type: '.$contentType);
468
            header('Content-Length: '.$len);
469
            $userAgent = strtolower($_SERVER['HTTP_USER_AGENT']);
470
471
            if (strpos($userAgent, 'msie')) {
472
                header('Content-Disposition: ; filename= '.$filename);
473
            } else {
474
                //header('Content-Disposition: inline');
475
                header('Content-Disposition: inline;');
476
            }
477
478
            if ($fixLinksHttpToHttps) {
479
                $content = file_get_contents($full_file_name);
480
                $content = str_replace(
481
                    ['http%3A%2F%2F', 'http://'],
482
                    ['https%3A%2F%2F', 'https://'],
483
                    $content
484
                );
485
                if ($enableMathJaxScript === true) {
486
                    $content = self::includeMathJaxScript($content);
487
                }
488
                echo $content;
489
            } else {
490
                if ('image/svg+xml' === $contentType) {
491
                    $svgContent = file_get_contents($full_file_name);
492
493
                    echo (new Sanitizer())->sanitize($svgContent);
494
495
                    return true;
496
                }
497
498
                if (isset($enableMathJaxScript) && $enableMathJaxScript === true) {
499
                    $content = file_get_contents($full_file_name);
500
                    $content = self::includeMathJaxScript($content);
501
                    echo $content;
502
                } else {
503
                    if (function_exists('ob_end_clean') && ob_get_length()) {
504
                        // Use ob_end_clean() to avoid weird buffering situations
505
                        // where file is sent broken/incomplete for download
506
                        ob_end_clean();
507
                    }
508
                    readfile($full_file_name);
509
                }
510
            }
511
512
            return true;
513
        }
514
    }
515
516
    /**
517
     * Session folder filters.
518
     *
519
     * @param string $path
520
     * @param int    $sessionId
521
     *
522
     * @return string|null
523
     */
524
    public static function getSessionFolderFilters($path, $sessionId)
525
    {
526
        $sessionId = (int) $sessionId;
527
        $condition = null;
528
529
        if (!empty($sessionId)) {
530
            // Chat folder filter
531
            if ($path == '/chat_files') {
532
                $condition .= " AND (docs.session_id = '$sessionId') ";
533
            }
534
            // share_folder filter
535
            $condition .= " AND docs.path != '/shared_folder' ";
536
        }
537
538
        return $condition;
539
    }
540
541
    /**
542
     * Fetches all document data for the given user/group.
543
     *
544
     * @param array  $courseInfo
545
     * @param string $path
546
     * @param int    $toGroupId       iid
547
     * @param int    $toUserId
548
     * @param bool   $canSeeInvisible
549
     * @param bool   $search
550
     * @param int    $sessionId
551
     *
552
     * @return array with all document data
553
     */
554
    public static function getAllDocumentData(
555
        $courseInfo,
556
        $path = '/',
557
        $toGroupId = 0,
558
        $toUserId = null,
559
        $canSeeInvisible = false,
560
        $search = false,
561
        $sessionId = 0
562
    ) {
563
        if (empty($courseInfo)) {
564
            return [];
565
        }
566
567
        $tblItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
568
        $tblDocument = Database::get_course_table(TABLE_DOCUMENT);
569
570
        if (!is_null($toUserId)) {
571
            $toUserId = (int) $toUserId;
572
            $userGroupFilter = "last.to_user_id = $toUserId";
573
            if (empty($toUserId)) {
574
                $userGroupFilter = ' (last.to_user_id = 0 OR last.to_user_id IS NULL) ';
575
            }
576
        } else {
577
            $toGroupId = (int) $toGroupId;
578
            $userGroupFilter = "last.to_group_id = $toGroupId";
579
            if (empty($toGroupId)) {
580
                $userGroupFilter = '( last.to_group_id = 0 OR last.to_group_id IS NULL) ';
581
            }
582
        }
583
584
        // Escape underscores in the path so they don't act as a wildcard
585
        $originalPath = $path;
586
        $path = str_replace('_', '\_', $path);
587
588
        $visibilityBit = ' <> 2';
589
590
        // The given path will not end with a slash, unless it's the root '/'
591
        // so no root -> add slash
592
        $addedSlash = $path == '/' ? '' : '/';
593
594
        // Condition for the session
595
        $sessionId = $sessionId ?: api_get_session_id();
596
        $conditionSession = " AND (last.session_id = '$sessionId' OR (last.session_id = '0' OR last.session_id IS NULL) )";
597
        $conditionSession .= self::getSessionFolderFilters($originalPath, $sessionId);
598
599
        $sharedCondition = null;
600
        if ($originalPath == '/shared_folder') {
601
            $students = CourseManager::get_user_list_from_course_code($courseInfo['code'], $sessionId);
602
            if (!empty($students)) {
603
                $conditionList = [];
604
                foreach ($students as $studentInfo) {
605
                    $conditionList[] = '/shared_folder/sf_user_'.$studentInfo['user_id'];
606
                }
607
                $sharedCondition .= ' AND docs.path IN ("'.implode('","', $conditionList).'")';
608
            }
609
        }
610
        $where = '';
611
        if ($search != true) {
612
            $where .= "docs.path NOT LIKE '".Database::escape_string($path.$addedSlash.'%/%')."' AND";
613
        }
614
        $sql = "SELECT
615
                    docs.id,
616
                    docs.filetype,
617
                    docs.path,
618
                    docs.title,
619
                    docs.comment,
620
                    docs.size,
621
                    docs.readonly,
622
                    docs.session_id,
623
                    last.session_id item_property_session_id,
624
                    last.lastedit_date,
625
                    last.visibility,
626
                    last.insert_user_id
627
                FROM $tblItemProperty AS last
628
                INNER JOIN $tblDocument AS docs
629
                ON (
630
                    docs.id = last.ref AND
631
                    docs.c_id = last.c_id
632
                )
633
                WHERE
634
                    last.tool = '".TOOL_DOCUMENT."' AND
635
                    docs.c_id = {$courseInfo['real_id']} AND
636
                    last.c_id = {$courseInfo['real_id']} AND
637
                    docs.path LIKE '".Database::escape_string($path.$addedSlash.'%')."' AND
638
                    $where
639
                    docs.path NOT LIKE '%_DELETED_%' AND
640
                    $userGroupFilter AND
641
                    last.visibility $visibilityBit
642
                    $conditionSession
643
                    $sharedCondition
644
                ORDER BY last.iid DESC, last.session_id DESC
645
                ";
646
647
        $result = Database::query($sql);
648
649
        $documentData = [];
650
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
651
        $isCoach = api_is_coach();
652
        if ($result !== false && Database::num_rows($result) != 0) {
653
            $rows = [];
654
655
            $hideInvisibleDocuments = api_get_configuration_value('hide_invisible_course_documents_in_sessions');
656
657
            while ($row = Database::fetch_array($result, 'ASSOC')) {
658
                if (isset($rows[$row['id']])) {
659
                    continue;
660
                }
661
662
                // If we are in session and hide_invisible_course_documents_in_sessions is enabled
663
                // Then we avoid the documents that have visibility in session but that they come from a base course
664
                if ($hideInvisibleDocuments && $sessionId) {
665
                    if ($row['item_property_session_id'] == $sessionId && empty($row['session_id'])) {
666
                        continue;
667
                    }
668
                }
669
670
                if (self::isBasicCourseFolder($row['path'], $sessionId)) {
671
                    $basicCourseDocumentsContent = self::getAllDocumentData(
672
                        $courseInfo,
673
                        $row['path']
674
                    );
675
676
                    if (empty($basicCourseDocumentsContent)) {
677
                        continue;
678
                    }
679
                }
680
681
                $rows[$row['id']] = $row;
682
            }
683
684
            // If we are in session and hide_invisible_course_documents_in_sessions is enabled
685
            // Or if we are students
686
            // Then don't list the invisible or deleted documents
687
            if (($sessionId && $hideInvisibleDocuments) || (!$isCoach && !$isAllowedToEdit)) {
688
                $rows = array_filter($rows, function ($row) {
689
                    if (in_array($row['visibility'], ['0', '2'])) {
690
                        return false;
691
                    }
692
693
                    return true;
694
                });
695
            }
696
697
            foreach ($rows as $row) {
698
                if ($row['filetype'] === 'file' &&
699
                    pathinfo($row['path'], PATHINFO_EXTENSION) == 'html'
700
                ) {
701
                    // Templates management
702
                    $tblTemplate = Database::get_main_table(TABLE_MAIN_TEMPLATES);
703
                    $sql = "SELECT id FROM $tblTemplate
704
                            WHERE
705
                                course_code = '".$courseInfo['code']."' AND
706
                                user_id = '".api_get_user_id()."' AND
707
                                ref_doc = '".$row['id']."'";
708
                    $templateResult = Database::query($sql);
709
                    $row['is_template'] = (Database::num_rows($templateResult) > 0) ? 1 : 0;
710
                }
711
                $row['basename'] = basename($row['path']);
712
                // Just filling $document_data.
713
                $documentData[$row['id']] = $row;
714
            }
715
716
            // Only for the student we filter the results see BT#1652
717
            if (!$isCoach && !$isAllowedToEdit) {
718
                // Checking parents visibility.
719
                $finalDocumentData = [];
720
721
                foreach ($documentData as $row) {
722
                    $isVisible = self::check_visibility_tree(
723
                        $row['id'],
724
                        $courseInfo,
725
                        $sessionId,
726
                        api_get_user_id(),
727
                        $toGroupId
728
                    );
729
                    if ($isVisible) {
730
                        $finalDocumentData[$row['id']] = $row;
731
                    }
732
                }
733
            } else {
734
                $finalDocumentData = $documentData;
735
            }
736
737
            return $finalDocumentData;
738
        }
739
740
        return [];
741
    }
742
743
    /**
744
     * Gets the paths of all folders in a course
745
     * can show all folders (except for the deleted ones) or only visible ones.
746
     *
747
     * @param array  $_course
748
     * @param int    $groupIid          iid
749
     * @param bool   $can_see_invisible
750
     * @param bool   $getInvisibleList
751
     * @param string $path              current path
752
     *
753
     * @return array with paths
754
     */
755
    public static function get_all_document_folders(
756
        $_course,
757
        $groupIid = 0,
758
        $can_see_invisible = false,
759
        $getInvisibleList = false,
760
        $path = ''
761
    ) {
762
        if (empty($_course)) {
763
            return [];
764
        }
765
766
        $TABLE_ITEMPROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
767
        $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
768
        $groupIid = (int) $groupIid;
769
        $document_folders = [];
770
771
        $students = CourseManager::get_user_list_from_course_code(
772
            $_course['code'],
773
            api_get_session_id()
774
        );
775
776
        $conditionList = [];
777
        if (!empty($students)) {
778
            foreach ($students as $studentId => $studentInfo) {
779
                $conditionList[] = '/shared_folder/sf_user_'.$studentInfo['user_id'];
780
            }
781
        }
782
783
        $groupCondition = " last.to_group_id = $groupIid";
784
        if (empty($groupIid)) {
785
            $groupCondition = ' (last.to_group_id = 0 OR last.to_group_id IS NULL)';
786
        }
787
788
        $show_users_condition = '';
789
        if (api_get_setting('show_users_folders') === 'false') {
790
            $show_users_condition = " AND docs.path NOT LIKE '%shared_folder%'";
791
        }
792
793
        if ($can_see_invisible) {
794
            // condition for the session
795
            $session_id = api_get_session_id();
796
            $session_id = $session_id ?: api_get_session_id();
797
            $condition_session = " AND (last.session_id = '$session_id' OR (last.session_id = '0' OR last.session_id IS NULL) )";
798
            $condition_session .= self::getSessionFolderFilters($path, $session_id);
799
800
            if ($groupIid != 0) {
801
                $sql = "SELECT DISTINCT docs.id, path
802
                       FROM $TABLE_ITEMPROPERTY  AS last
803
                       INNER JOIN $TABLE_DOCUMENT  AS docs
804
                       ON (
805
                            docs.id = last.ref AND
806
                            docs.c_id = last.c_id
807
                       )
808
                       WHERE
809
                            last.tool = '".TOOL_DOCUMENT."' AND
810
                            last.c_id = {$_course['real_id']} AND
811
                            docs.c_id = {$_course['real_id']} AND
812
                            docs.filetype = 'folder' AND
813
                            $groupCondition AND
814
                            docs.path NOT LIKE '%shared_folder%' AND
815
                            docs.path NOT LIKE '%_DELETED_%' AND
816
                            last.visibility <> 2
817
                            $condition_session ";
818
            } else {
819
                $sql = "SELECT DISTINCT docs.id, path
820
                        FROM $TABLE_ITEMPROPERTY  AS last
821
                        INNER JOIN $TABLE_DOCUMENT  AS docs
822
                        ON (
823
                            docs.id = last.ref AND
824
                            docs.c_id = last.c_id
825
                        )
826
                        WHERE
827
                            last.tool = '".TOOL_DOCUMENT."' AND
828
                            last.c_id = {$_course['real_id']} AND
829
                            docs.c_id = {$_course['real_id']} AND
830
                            docs.filetype = 'folder' AND
831
                            docs.path NOT LIKE '%_DELETED_%' AND
832
                            $groupCondition AND
833
                            last.visibility <> 2
834
                            $show_users_condition
835
                            $condition_session
836
                        ";
837
            }
838
            $result = Database::query($sql);
839
840
            if ($result && Database::num_rows($result) != 0) {
841
                while ($row = Database::fetch_array($result, 'ASSOC')) {
842
                    if (self::is_folder_to_avoid($row['path'])) {
843
                        continue;
844
                    }
845
846
                    if (strpos($row['path'], '/shared_folder/') !== false) {
847
                        if (!in_array($row['path'], $conditionList)) {
848
                            continue;
849
                        }
850
                    }
851
852
                    $document_folders[$row['id']] = $row['path'];
853
                }
854
855
                if (!empty($document_folders)) {
856
                    // natsort($document_folders);
857
                    natcasesort($document_folders);
858
                }
859
860
                return $document_folders;
861
            } else {
862
                return false;
863
            }
864
        } else {
865
            // No invisible folders
866
            // Condition for the session
867
            $session_id = api_get_session_id();
868
            $condition_session = api_get_session_condition(
869
                $session_id,
870
                true,
871
                true, // needed to don't show files in elfinder browser
872
                'docs.session_id'
873
            );
874
875
            $visibilityCondition = 'last.visibility = 1';
876
            $fileType = "docs.filetype = 'folder' AND";
877
            if ($getInvisibleList) {
878
                $visibilityCondition = 'last.visibility = 0';
879
                $fileType = '';
880
            }
881
882
            //get visible folders
883
            $sql = "SELECT DISTINCT docs.id, path
884
                    FROM
885
                    $TABLE_ITEMPROPERTY AS last
886
                    INNER JOIN $TABLE_DOCUMENT AS docs
887
                    ON (docs.id = last.ref AND last.c_id = docs.c_id)
888
                    WHERE
889
                        $fileType
890
                        last.tool = '".TOOL_DOCUMENT."' AND
891
                        $groupCondition AND
892
                        $visibilityCondition
893
                        $show_users_condition
894
                        $condition_session AND
895
                        last.c_id = {$_course['real_id']}  AND
896
                        docs.c_id = {$_course['real_id']} ";
897
898
            $result = Database::query($sql);
899
900
            $visibleFolders = [];
901
            while ($row = Database::fetch_array($result, 'ASSOC')) {
902
                $visibleFolders[$row['id']] = $row['path'];
903
            }
904
905
            if ($getInvisibleList) {
906
                return $visibleFolders;
907
            }
908
909
            //get invisible folders
910
            $sql = "SELECT DISTINCT docs.id, path
911
                    FROM $TABLE_ITEMPROPERTY AS last
912
                    INNER JOIN $TABLE_DOCUMENT AS docs
913
                    ON (docs.id = last.ref AND last.c_id = docs.c_id)
914
                    WHERE
915
                        docs.filetype = 'folder' AND
916
                        last.tool = '".TOOL_DOCUMENT."' AND
917
                        $groupCondition AND
918
                        last.visibility = 0 $condition_session AND
919
                        last.c_id = {$_course['real_id']} AND
920
                        docs.c_id = {$_course['real_id']} ";
921
            $result = Database::query($sql);
922
            $invisibleFolders = [];
923
            while ($row = Database::fetch_array($result, 'ASSOC')) {
924
                //get visible folders in the invisible ones -> they are invisible too
925
                $sql = "SELECT DISTINCT docs.id, path
926
                        FROM $TABLE_ITEMPROPERTY AS last
927
                        INNER JOIN $TABLE_DOCUMENT AS docs
928
                        ON (docs.id = last.ref AND docs.c_id = last.c_id)
929
                        WHERE
930
                            docs.path LIKE '".Database::escape_string($row['path'].'/%')."' AND
931
                            docs.filetype = 'folder' AND
932
                            last.tool = '".TOOL_DOCUMENT."' AND
933
                            $groupCondition AND
934
                            last.visibility = 1 $condition_session AND
935
                            last.c_id = {$_course['real_id']} AND
936
                            docs.c_id = {$_course['real_id']}  ";
937
                $folder_in_invisible_result = Database::query($sql);
938
                while ($folders_in_invisible_folder = Database::fetch_array($folder_in_invisible_result, 'ASSOC')) {
939
                    $invisibleFolders[$folders_in_invisible_folder['id']] = $folders_in_invisible_folder['path'];
940
                }
941
            }
942
943
            // If both results are arrays -> //calculate the difference between the 2 arrays -> only visible folders are left :)
944
            if (is_array($visibleFolders) && is_array($invisibleFolders)) {
945
                $document_folders = array_diff($visibleFolders, $invisibleFolders);
946
                // natsort($document_folders);
947
                natcasesort($document_folders);
948
949
                return $document_folders;
950
            } elseif (is_array($visibleFolders)) {
951
                natcasesort($visibleFolders);
952
                // natsort($visibleFolders);
953
954
                return $visibleFolders;
955
            } else {
956
                //no visible folders found
957
                return false;
958
            }
959
        }
960
    }
961
962
    /**
963
     * This check if a document has the readonly property checked, then see if the user
964
     * is the owner of this file, if all this is true then return true.
965
     *
966
     * @param array  $_course
967
     * @param int    $user_id     id of the current user
968
     * @param string $file        path stored in the database (if not defined, $documentId must be used)
969
     * @param int    $document_id in case you dont have the file path ,
970
     *                            insert the id of the file here and leave $file in blank ''
971
     * @param bool   $to_delete
972
     * @param int    $sessionId
973
     *
974
     * @return bool true/false
975
     * */
976
    public static function check_readonly(
977
        $_course,
978
        $user_id,
979
        $file = null,
980
        $document_id = 0,
981
        $to_delete = false,
982
        $sessionId = null,
983
        $documentId = null
984
    ) {
985
        if (empty($sessionId)) {
986
            $sessionId = api_get_session_id();
987
        } else {
988
            $sessionId = intval($sessionId);
989
        }
990
991
        if (empty($document_id) || !is_numeric($document_id)) {
992
            $document_id = self::get_document_id($_course, $file, $sessionId);
993
        } else {
994
            $document_id = intval($document_id);
995
        }
996
997
        $TABLE_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
998
        $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
999
        $course_id = $_course['real_id'];
1000
1001
        if ($to_delete) {
1002
            if (self::is_folder($_course, $document_id)) {
1003
                if (!empty($file)) {
1004
                    $path = Database::escape_string($file);
1005
                    // Check
1006
                    $sql = "SELECT td.id, readonly, tp.insert_user_id
1007
                            FROM $TABLE_DOCUMENT td
1008
                            INNER JOIN $TABLE_PROPERTY tp
1009
                            ON (td.c_id = tp.c_id AND tp.ref= td.id)
1010
                            WHERE
1011
                                td.c_id = $course_id AND
1012
                                tp.c_id = $course_id AND
1013
                                td.session_id = $sessionId AND
1014
                                (path='".$path."' OR path LIKE BINARY '".$path."/%' ) ";
1015
                    // Get all id's of documents that are deleted
1016
                    $what_to_check_result = Database::query($sql);
1017
1018
                    if ($what_to_check_result && Database::num_rows($what_to_check_result) != 0) {
1019
                        // file with readonly set to 1 exist?
1020
                        $readonly_set = false;
1021
                        while ($row = Database::fetch_array($what_to_check_result)) {
1022
                            //query to delete from item_property table
1023
                            if ($row['readonly'] == 1) {
1024
                                if (!($row['insert_user_id'] == $user_id)) {
1025
                                    $readonly_set = true;
1026
                                    break;
1027
                                }
1028
                            }
1029
                        }
1030
1031
                        if ($readonly_set) {
1032
                            return true;
1033
                        }
1034
                    }
1035
                }
1036
1037
                return false;
1038
            }
1039
        }
1040
1041
        if (!empty($document_id)) {
1042
            $sql = "SELECT a.insert_user_id, b.readonly
1043
                   FROM $TABLE_PROPERTY a
1044
                   INNER JOIN $TABLE_DOCUMENT b
1045
                   ON (a.c_id = b.c_id AND a.ref= b.id)
1046
                   WHERE
1047
            			a.c_id = $course_id AND
1048
                        b.c_id = $course_id AND
1049
            			a.ref = $document_id
1050
                    LIMIT 1";
1051
            $result = Database::query($sql);
1052
            $doc_details = Database::fetch_array($result, 'ASSOC');
1053
1054
            if ($doc_details['readonly'] == 1) {
1055
                return !($doc_details['insert_user_id'] == $user_id || api_is_platform_admin());
1056
            }
1057
        }
1058
1059
        return false;
1060
    }
1061
1062
    /**
1063
     * This check if a document is a folder or not.
1064
     *
1065
     * @param array $_course
1066
     * @param int   $id      document id
1067
     *
1068
     * @return bool true/false
1069
     * */
1070
    public static function is_folder($_course, $id)
1071
    {
1072
        $table = Database::get_course_table(TABLE_DOCUMENT);
1073
        if (empty($_course)) {
1074
            return false;
1075
        }
1076
        $course_id = $_course['real_id'];
1077
        $id = (int) $id;
1078
        $sql = "SELECT filetype FROM $table
1079
                WHERE c_id = $course_id AND id= $id";
1080
        $result = Database::fetch_array(Database::query($sql), 'ASSOC');
1081
1082
        return $result['filetype'] == 'folder';
1083
    }
1084
1085
    /**
1086
     * @param int   $document_id
1087
     * @param array $course_info
1088
     * @param int   $session_id
1089
     * @param bool  $remove_content_from_db
1090
     */
1091
    public static function deleteDocumentFromDb(
1092
        $document_id,
1093
        $course_info = [],
1094
        $session_id = 0,
1095
        $remove_content_from_db = false
1096
    ) {
1097
        $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1098
        $TABLE_ITEMPROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
1099
1100
        // Deleting from the DB
1101
        $user_id = api_get_user_id();
1102
        $document_id = (int) $document_id;
1103
1104
        if (empty($course_info)) {
1105
            $course_info = api_get_course_info();
1106
        }
1107
1108
        if (empty($session_id)) {
1109
            $session_id = api_get_session_id();
1110
        }
1111
        // Soft DB delete
1112
        api_item_property_update(
1113
            $course_info,
1114
            TOOL_DOCUMENT,
1115
            $document_id,
1116
            'delete',
1117
            $user_id,
1118
            null,
1119
            null,
1120
            null,
1121
            null,
1122
            $session_id
1123
        );
1124
        self::delete_document_from_search_engine($course_info['code'], $document_id);
1125
        self::unset_document_as_template($document_id, $course_info['code'], $user_id);
1126
1127
        //Hard DB delete
1128
        if ($remove_content_from_db) {
1129
            $sql = "DELETE FROM $TABLE_ITEMPROPERTY
1130
                    WHERE
1131
                        c_id = {$course_info['real_id']} AND
1132
                        ref = ".$document_id." AND
1133
                        tool='".TOOL_DOCUMENT."'";
1134
            Database::query($sql);
1135
1136
            $sql = "DELETE FROM $TABLE_DOCUMENT
1137
                    WHERE c_id = {$course_info['real_id']} AND id = ".$document_id;
1138
            Database::query($sql);
1139
        }
1140
    }
1141
1142
    /**
1143
     * This deletes a document by changing visibility to 2, renaming it to filename_DELETED_#id
1144
     * Files/folders that are inside a deleted folder get visibility 2.
1145
     *
1146
     * @param array  $_course
1147
     * @param string $path          Path stored in the database
1148
     * @param string $base_work_dir Path to the "/document" folder of this course. Will be built if not provided.
1149
     * @param int    $sessionId     The ID of the session, if any
1150
     * @param int    $documentId    The document id, if available
1151
     * @param int    $groupId       iid
1152
     *
1153
     * @return bool true/false
1154
     *
1155
     * @todo now only files/folders in a folder get visibility 2, we should rename them too.
1156
     * @todo We should be able to get rid of this later when using only documentId (check further usage)
1157
     */
1158
    public static function delete_document(
1159
        $_course,
1160
        $path = null,
1161
        $base_work_dir = null,
1162
        $sessionId = null,
1163
        $documentId = null,
1164
        $groupId = 0
1165
    ) {
1166
        $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1167
1168
        $documentId = (int) $documentId;
1169
        $groupId = (int) $groupId;
1170
        if (empty($groupId)) {
1171
            $groupId = api_get_group_id();
1172
        }
1173
1174
        $sessionId = (int) $sessionId;
1175
        if (empty($sessionId)) {
1176
            $sessionId = api_get_session_id();
1177
        }
1178
1179
        $course_id = $_course['real_id'];
1180
1181
        if (empty($course_id)) {
1182
            return false;
1183
        }
1184
1185
        if (empty($base_work_dir)) {
1186
            if (empty($_course['directory'])) {
1187
                return false;
1188
            }
1189
            $courseDir = $_course['directory'].'/document';
1190
            $sysCoursePath = api_get_path(SYS_COURSE_PATH);
1191
            $base_work_dir = $sysCoursePath.$courseDir;
1192
        }
1193
1194
        if (empty($documentId)) {
1195
            $documentId = self::get_document_id($_course, $path, $sessionId);
1196
            $docInfo = self::get_document_data_by_id(
1197
                $documentId,
1198
                $_course['code'],
1199
                false,
1200
                $sessionId
1201
            );
1202
            $path = $docInfo['path'];
1203
        } else {
1204
            $docInfo = self::get_document_data_by_id(
1205
                $documentId,
1206
                $_course['code'],
1207
                false,
1208
                $sessionId
1209
            );
1210
            if (empty($docInfo)) {
1211
                return false;
1212
            }
1213
            $path = $docInfo['path'];
1214
        }
1215
1216
        if (empty($path) || empty($docInfo) || empty($documentId)) {
1217
            return false;
1218
        }
1219
1220
        $itemInfo = api_get_item_property_info(
1221
            $_course['real_id'],
1222
            TOOL_DOCUMENT,
1223
            $documentId,
1224
            $sessionId,
1225
            $groupId
1226
        );
1227
1228
        if (empty($itemInfo)) {
1229
            return false;
1230
        }
1231
1232
        // File was already deleted.
1233
        if ($itemInfo['lastedit_type'] == 'DocumentDeleted' ||
1234
            $itemInfo['lastedit_type'] == 'delete' ||
1235
            $itemInfo['visibility'] == 2
1236
        ) {
1237
            return false;
1238
        }
1239
1240
        // Filtering by group.
1241
        if ($itemInfo['to_group_id'] != $groupId) {
1242
            return false;
1243
        }
1244
1245
        $document_exists_in_disk = file_exists($base_work_dir.$path);
1246
        $new_path = $path.'_DELETED_'.$documentId;
1247
1248
        $file_deleted_from_db = false;
1249
        $file_deleted_from_disk = false;
1250
        $file_renamed_from_disk = false;
1251
1252
        if ($documentId) {
1253
            // Deleting doc from the DB.
1254
            self::deleteDocumentFromDb($documentId, $_course, $sessionId);
1255
            // Checking
1256
            // $file_exists_in_db = self::get_document_data_by_id($documentId, $_course['code']);
1257
            $file_deleted_from_db = true;
1258
        }
1259
1260
        // Looking for children.
1261
        if ($docInfo['filetype'] == 'folder') {
1262
            $cleanPath = Database::escape_string($path);
1263
1264
            // Deleted files inside this folder.
1265
            $sql = "SELECT id FROM $TABLE_DOCUMENT
1266
                    WHERE
1267
                        c_id = $course_id AND
1268
                        session_id = $sessionId AND
1269
                        path LIKE BINARY '".$cleanPath."/%'";
1270
1271
            // Get all id's of documents that are deleted.
1272
            $result = Database::query($sql);
1273
1274
            if ($result && Database::num_rows($result) != 0) {
1275
                // Recursive delete.
1276
                while ($row = Database::fetch_array($result)) {
1277
                    self::delete_document(
1278
                        $_course,
1279
                        null,
1280
                        $base_work_dir,
1281
                        $sessionId,
1282
                        $row['id'],
1283
                        $groupId
1284
                    );
1285
                }
1286
            }
1287
        }
1288
1289
        if ($document_exists_in_disk) {
1290
            if (api_get_setting('permanently_remove_deleted_files') === 'true') {
1291
                // Delete documents, do it like this so metadata gets deleted too
1292
                my_delete($base_work_dir.$path);
1293
                // Hard delete.
1294
                self::deleteDocumentFromDb($documentId, $_course, $sessionId, true);
1295
                $file_deleted_from_disk = true;
1296
            } else {
1297
                // Set visibility to 2 and rename file/folder to xxx_DELETED_#id (soft delete)
1298
                if (is_file($base_work_dir.$path) || is_dir($base_work_dir.$path)) {
1299
                    if (rename($base_work_dir.$path, $base_work_dir.$new_path)) {
1300
                        $new_path = Database::escape_string($new_path);
1301
1302
                        $sql = "UPDATE $TABLE_DOCUMENT
1303
                                SET path = '".$new_path."'
1304
                                WHERE
1305
                                    c_id = $course_id AND
1306
                                    session_id = $sessionId AND
1307
                                    id = ".$documentId;
1308
                        Database::query($sql);
1309
1310
                        // Soft delete.
1311
                        self::deleteDocumentFromDb($documentId, $_course, $sessionId);
1312
1313
                        // Change path of sub folders and documents in database.
1314
                        $old_item_path = $docInfo['path'];
1315
                        $new_item_path = $new_path.substr($old_item_path, strlen($path));
1316
                        $new_item_path = Database::escape_string($new_item_path);
1317
1318
                        $sql = "UPDATE $TABLE_DOCUMENT
1319
                                SET path = '".$new_item_path."'
1320
                                WHERE
1321
                                    c_id = $course_id AND
1322
                                    session_id = $sessionId AND
1323
                                    id = ".$documentId;
1324
                        Database::query($sql);
1325
1326
                        $file_renamed_from_disk = true;
1327
                    } else {
1328
                        // Couldn't rename - file permissions problem?
1329
                        error_log(
1330
                            __FILE__.' '.__LINE__.': Error renaming '.$base_work_dir.$path.' to '.$base_work_dir.$new_path.'. This is probably due to file permissions',
1331
                            0
1332
                        );
1333
                    }
1334
                }
1335
            }
1336
        }
1337
        // Checking inconsistency
1338
        //error_log('Doc status: (1 del db :'.($file_deleted_from_db?'yes':'no').') - (2 del disk: '.($file_deleted_from_disk?'yes':'no').') - (3 ren disk: '.($file_renamed_from_disk?'yes':'no').')');
1339
        if ($file_deleted_from_db && $file_deleted_from_disk ||
1340
            $file_deleted_from_db && $file_renamed_from_disk
1341
        ) {
1342
            return true;
1343
        } else {
1344
            //Something went wrong
1345
            //The file or directory isn't there anymore (on the filesystem)
1346
            // This means it has been removed externally. To prevent a
1347
            // blocking error from happening, we drop the related items from the
1348
            // item_property and the document table.
1349
            error_log(
1350
                __FILE__.' '.__LINE__.': System inconsistency detected. The file or directory '.$base_work_dir.$path.' seems to have been removed from the filesystem independently from the web platform. To restore consistency, the elements using the same path will be removed from the database',
1351
                0
1352
            );
1353
1354
            return false;
1355
        }
1356
    }
1357
1358
    /**
1359
     * Removes documents from search engine database.
1360
     *
1361
     * @param string $course_id   Course code
1362
     * @param int    $document_id Document id to delete
1363
     */
1364
    public static function delete_document_from_search_engine($course_id, $document_id)
1365
    {
1366
        // remove from search engine if enabled
1367
        if (api_get_setting('search_enabled') === 'true') {
1368
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1369
            $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
1370
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_DOCUMENT, $document_id);
1371
            $res = Database::query($sql);
1372
            if (Database::num_rows($res) > 0) {
1373
                $row2 = Database::fetch_array($res);
1374
                $di = new ChamiloIndexer();
1375
                $di->remove_document($row2['search_did']);
1376
            }
1377
            $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
1378
            $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_DOCUMENT, $document_id);
1379
            Database::query($sql);
1380
1381
            // remove terms from db
1382
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1383
            delete_all_values_for_item($course_id, TOOL_DOCUMENT, $document_id);
1384
        }
1385
    }
1386
1387
    /**
1388
     * Gets the id of a document with a given path.
1389
     *
1390
     * @param array  $courseInfo
1391
     * @param string $path
1392
     * @param int    $sessionId
1393
     *
1394
     * @return int id of document / false if no doc found
1395
     */
1396
    public static function get_document_id($courseInfo, $path, $sessionId = null, $forceFileTypeFolder = false)
1397
    {
1398
        $table = Database::get_course_table(TABLE_DOCUMENT);
1399
        $courseId = $courseInfo['real_id'];
1400
1401
        if (!isset($sessionId)) {
1402
            $sessionId = api_get_session_id();
1403
        } else {
1404
            $sessionId = intval($sessionId);
1405
        }
1406
1407
        // Special case: cache doc ID for 3600s if "/" has already been queried
1408
        // recently.
1409
        // This is a hack. Better solutions: use apcu (not always available)
1410
        // and even better: reduce the amount of calls by not processing
1411
        // documents that will not be shown (e.g. on other pages of a table)
1412
        $isSlash = ($path === '/');
1413
        if ($isSlash) {
1414
            $cSSlashString = 'docIdSlash2C'.$courseId.'S'.$sessionId;
1415
            $storedSlashId = Session::read($cSSlashString);
1416
            $now = time();
1417
            if (is_array($storedSlashId)) {
1418
                if ($storedSlashId['t'] >= $now - 3600) {
1419
                    return $storedSlashId['id'];
1420
                }
1421
            }
1422
        }
1423
1424
        $path = Database::escape_string($path);
1425
        if (!empty($courseId) && !empty($path)) {
1426
            $folderCondition = '';
1427
            if ($forceFileTypeFolder) {
1428
                $folderCondition = ' AND filetype = "folder" ';
1429
            }
1430
            $sql = "SELECT id FROM $table
1431
                    WHERE
1432
                        c_id = $courseId AND
1433
                        path LIKE BINARY '$path' AND
1434
                        session_id = $sessionId
1435
                        $folderCondition
1436
                    LIMIT 1";
1437
1438
            $result = Database::query($sql);
1439
            if (Database::num_rows($result)) {
1440
                $row = Database::fetch_array($result);
1441
                if ($isSlash) {
1442
                    Session::write($cSSlashString, ['t' => $now, 'id' => intval($row['id'])]);
1443
                }
1444
1445
                return intval($row['id']);
1446
            }
1447
            if ($isSlash) {
1448
                // Even if there is no "/" document/folder, store a result to avoid querying the database again
1449
                Session::write($cSSlashString, ['t' => $now, 'id' => false]);
1450
            }
1451
        }
1452
1453
        return false;
1454
    }
1455
1456
    /**
1457
     * Gets the document data with a given id.
1458
     *
1459
     * @param int    $id            Document Id (id field in c_document table)
1460
     * @param string $course_code   Course code
1461
     * @param bool   $load_parents  load folder parents
1462
     * @param int    $session_id    The session ID,
1463
     *                              0 if requires context *out of* session, and null to use global context
1464
     * @param bool   $ignoreDeleted
1465
     *
1466
     * @return array document content
1467
     */
1468
    public static function get_document_data_by_id(
1469
        $id,
1470
        $course_code,
1471
        $load_parents = false,
1472
        $session_id = null,
1473
        $ignoreDeleted = false
1474
    ) {
1475
        $course_info = api_get_course_info($course_code);
1476
        $course_id = $course_info['real_id'];
1477
1478
        if (empty($course_info)) {
1479
            return false;
1480
        }
1481
1482
        $session_id = empty($session_id) ? api_get_session_id() : (int) $session_id;
1483
        $groupId = api_get_group_id();
1484
        $www = api_get_path(WEB_COURSE_PATH).$course_info['path'].'/document';
1485
        $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
1486
        $id = (int) $id;
1487
        $sessionCondition = api_get_session_condition($session_id, true, true);
1488
1489
        $sql = "SELECT * FROM $TABLE_DOCUMENT
1490
                WHERE c_id = $course_id $sessionCondition AND id = $id";
1491
1492
        if ($ignoreDeleted) {
1493
            $sql .= " AND path NOT LIKE '%_DELETED_%' ";
1494
        }
1495
1496
        $result = Database::query($sql);
1497
        $courseParam = '&cidReq='.$course_code.'&id='.$id.'&id_session='.$session_id.'&gidReq='.$groupId;
1498
        if ($result && Database::num_rows($result) == 1) {
1499
            $row = Database::fetch_array($result, 'ASSOC');
1500
            //@todo need to clarify the name of the URLs not nice right now
1501
            $url_path = urlencode($row['path']);
1502
            $path = str_replace('%2F', '/', $url_path);
1503
            $pathinfo = pathinfo($row['path']);
1504
            $row['url'] = api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.$courseParam;
1505
            $row['document_url'] = api_get_path(WEB_CODE_PATH).'document/document.php?id='.$id.$courseParam;
1506
            $row['absolute_path'] = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document'.$row['path'];
1507
            $row['absolute_path_from_document'] = '/document'.$row['path'];
1508
            $row['absolute_parent_path'] = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document'.$pathinfo['dirname'].'/';
1509
            $row['direct_url'] = $www.$path;
1510
            $row['basename'] = basename($row['path']);
1511
            if (dirname($row['path']) === '.') {
1512
                $row['parent_id'] = '0';
1513
            } else {
1514
                $row['parent_id'] = self::get_document_id($course_info, dirname($row['path']), $session_id, true);
1515
                if (empty($row['parent_id'])) {
1516
                    // Try one more with session id = 0
1517
                    $row['parent_id'] = self::get_document_id($course_info, dirname($row['path']), 0, true);
1518
                }
1519
            }
1520
            $parents = [];
1521
1522
            //Use to generate parents (needed for the breadcrumb)
1523
            //@todo sorry but this for is here because there's not a parent_id in the document table so we parsed the path!!
1524
            if ($load_parents) {
1525
                $dir_array = explode('/', $row['path']);
1526
                $dir_array = array_filter($dir_array);
1527
                $array_len = count($dir_array) + 1;
1528
                $real_dir = '';
1529
1530
                for ($i = 1; $i < $array_len; $i++) {
1531
                    $real_dir .= '/'.(isset($dir_array[$i]) ? $dir_array[$i] : '');
1532
                    $parent_id = self::get_document_id($course_info, $real_dir);
1533
                    if ($session_id != 0 && empty($parent_id)) {
1534
                        $parent_id = self::get_document_id($course_info, $real_dir, 0);
1535
                    }
1536
                    if (!empty($parent_id)) {
1537
                        $sub_document_data = self::get_document_data_by_id(
1538
                            $parent_id,
1539
                            $course_code,
1540
                            false,
1541
                            $session_id
1542
                        );
1543
                        if ($session_id != 0 and !$sub_document_data) {
1544
                            $sub_document_data = self::get_document_data_by_id(
1545
                                $parent_id,
1546
                                $course_code,
1547
                                false,
1548
                                0
1549
                            );
1550
                        }
1551
                        //@todo add visibility here
1552
                        $parents[] = $sub_document_data;
1553
                    }
1554
                }
1555
            }
1556
            $row['parents'] = $parents;
1557
1558
            return $row;
1559
        }
1560
1561
        return false;
1562
    }
1563
1564
    /**
1565
     * Allow to set a specific document as a new template for CKeditor
1566
     * for a particular user in a particular course.
1567
     *
1568
     * @param string $title
1569
     * @param string $description
1570
     * @param int    $document_id_for_template the document id
1571
     * @param string $course_code
1572
     * @param int    $user_id
1573
     * @param string $image
1574
     *
1575
     * @return bool
1576
     */
1577
    public static function set_document_as_template(
1578
        $title,
1579
        $description,
1580
        $document_id_for_template,
1581
        $course_code,
1582
        $user_id,
1583
        $image
1584
    ) {
1585
        // Database table definition
1586
        $table_template = Database::get_main_table(TABLE_MAIN_TEMPLATES);
1587
        $params = [
1588
            'title' => $title,
1589
            'description' => $description,
1590
            'course_code' => $course_code,
1591
            'user_id' => $user_id,
1592
            'ref_doc' => $document_id_for_template,
1593
            'image' => $image,
1594
        ];
1595
        Database::insert($table_template, $params);
1596
1597
        return true;
1598
    }
1599
1600
    /**
1601
     * Unset a document as template.
1602
     *
1603
     * @param int    $document_id
1604
     * @param string $course_code
1605
     * @param int    $user_id
1606
     */
1607
    public static function unset_document_as_template(
1608
        $document_id,
1609
        $course_code,
1610
        $user_id
1611
    ) {
1612
        $table_template = Database::get_main_table(TABLE_MAIN_TEMPLATES);
1613
        $course_code = Database::escape_string($course_code);
1614
        $user_id = intval($user_id);
1615
        $document_id = intval($document_id);
1616
        $sql = 'SELECT id FROM '.$table_template.'
1617
                WHERE
1618
                    course_code="'.$course_code.'" AND
1619
                    user_id="'.$user_id.'" AND
1620
                    ref_doc="'.$document_id.'"';
1621
        $result = Database::query($sql);
1622
        $template_id = Database::result($result, 0, 0);
1623
1624
        my_delete(api_get_path(SYS_CODE_PATH).'upload/template_thumbnails/'.$template_id.'.jpg');
1625
1626
        $sql = 'DELETE FROM '.$table_template.'
1627
                WHERE
1628
                    course_code="'.$course_code.'" AND
1629
                    user_id="'.$user_id.'" AND
1630
                    ref_doc="'.$document_id.'"';
1631
1632
        Database::query($sql);
1633
    }
1634
1635
    /**
1636
     * Return true if the documentpath have visibility=1 as
1637
     * item_property (you should use the is_visible_by_id).
1638
     *
1639
     * @param string $doc_path the relative complete path of the document
1640
     * @param array  $course   the _course array info of the document's course
1641
     * @param int
1642
     * @param string
1643
     *
1644
     * @return bool
1645
     */
1646
    public static function is_visible(
1647
        $doc_path,
1648
        $course,
1649
        $session_id = 0,
1650
        $file_type = 'file'
1651
    ) {
1652
        $docTable = Database::get_course_table(TABLE_DOCUMENT);
1653
        $propTable = Database::get_course_table(TABLE_ITEM_PROPERTY);
1654
1655
        $userId = api_get_user_id();
1656
        $course_id = $course['real_id'];
1657
        // note the extra / at the end of doc_path to match every path in
1658
        // the document table that is part of the document path
1659
        $session_id = (int) $session_id;
1660
1661
        $drhAccessContent = api_drh_can_access_all_session_content() &&
1662
            $session_id &&
1663
            SessionManager::isSessionFollowedByDrh($session_id, $userId);
1664
1665
        $sessionAdminAccessContent = api_get_configuration_value('session_admins_access_all_content');
1666
1667
        $hasAccess = api_is_allowed_in_course() || api_is_platform_admin() || $drhAccessContent || $sessionAdminAccessContent;
1668
1669
        if (false === $hasAccess) {
1670
            return false;
1671
        }
1672
1673
        $condition = "AND d.session_id IN  ('$session_id', '0') ";
1674
        // The " d.filetype='file' " let the user see a file even if the folder is hidden see #2198
1675
1676
        /*
1677
          When using hotpotatoes files, a new html files are generated
1678
          in the hotpotatoes folder to display the test.
1679
          The genuine html file is copied to math4.htm(user_id).t.html
1680
          Images files are not copied, and keep same name.
1681
          To check the html file visibility, we don't have to check file math4.htm(user_id).t.html but file math4.htm
1682
          In this case, we have to remove (user_id).t.html to check the visibility of the file
1683
          For images, we just check the path of the image file.
1684
1685
          Exemple of hotpotatoes folder :
1686
          A.jpg
1687
          maths4-consigne.jpg
1688
          maths4.htm
1689
          maths4.htm1.t.html
1690
          maths4.htm52.t.html
1691
          maths4.htm654.t.html
1692
          omega.jpg
1693
          theta.jpg
1694
         */
1695
1696
        if (strpos($doc_path, 'HotPotatoes_files') && preg_match("/\.t\.html$/", $doc_path)) {
1697
            $doc_path = substr($doc_path, 0, strlen($doc_path) - 7 - strlen($userId));
1698
        }
1699
1700
        if (!in_array($file_type, ['file', 'folder'])) {
1701
            $file_type = 'file';
1702
        }
1703
        $doc_path = Database::escape_string($doc_path).'/';
1704
1705
        $sql = "SELECT visibility, ip.session_id
1706
                FROM $docTable d
1707
                INNER JOIN $propTable ip
1708
                ON (d.id = ip.ref AND d.c_id = ip.c_id)
1709
        		WHERE
1710
        		    d.c_id  = $course_id AND
1711
        		    ip.c_id = $course_id AND
1712
        		    ip.tool = '".TOOL_DOCUMENT."' $condition AND
1713
        			filetype = '$file_type' AND
1714
        			locate(concat(path,'/'), '$doc_path')=1
1715
                ";
1716
1717
        $result = Database::query($sql);
1718
        $isVisible = false;
1719
        $numRows = (int) Database::num_rows($result);
1720
1721
        if ($numRows) {
1722
            if (1 === $numRows) {
1723
                $row = Database::fetch_array($result, 'ASSOC');
1724
                if ($row['visibility'] == 1) {
1725
                    $isVisible = true;
1726
                }
1727
            } else {
1728
                $sessionVisibility = null;
1729
                $courseVisibility = null;
1730
                while ($row = Database::fetch_array($result, 'ASSOC')) {
1731
                    $checkSessionId = (int) $row['session_id'];
1732
                    if (empty($checkSessionId)) {
1733
                        $courseVisibility = 1 === (int) $row['visibility'];
1734
                    } else {
1735
                        if ($session_id === $checkSessionId) {
1736
                            $sessionVisibility = 1 === (int) $row['visibility'];
1737
                        }
1738
                    }
1739
                }
1740
1741
                if (empty($session_id) || (!empty($session_id) && null === $sessionVisibility)) {
1742
                    if ($courseVisibility) {
1743
                        $isVisible = true;
1744
                    }
1745
                } else {
1746
                    if ($sessionVisibility) {
1747
                        $isVisible = true;
1748
                    }
1749
                }
1750
            }
1751
        }
1752
1753
        /* improved protection of documents viewable directly through the url:
1754
            incorporates the same protections of the course at the url of
1755
            documents:
1756
            access allowed for the whole world Open, access allowed for
1757
            users registered on the platform Private access, document accessible
1758
            only to course members (see the Users list), Completely closed;
1759
            the document is only accessible to the course admin and
1760
            teaching assistants.*/
1761
        //return $_SESSION ['is_allowed_in_course'] || api_is_platform_admin();
1762
        return $isVisible;
1763
    }
1764
1765
    /**
1766
     * Return true if user can see a file.
1767
     *
1768
     * @param   int     document id
1769
     * @param   array   course info
1770
     * @param   int
1771
     * @param   int
1772
     * @param bool
1773
     *
1774
     * @return bool
1775
     */
1776
    public static function is_visible_by_id(
1777
        $doc_id,
1778
        $course_info,
1779
        $session_id,
1780
        $user_id,
1781
        $admins_can_see_everything = true,
1782
        $userIsSubscribed = null
1783
    ) {
1784
        $user_in_course = false;
1785
1786
        //1. Checking the course array
1787
        if (empty($course_info)) {
1788
            $course_info = api_get_course_info();
1789
            if (empty($course_info)) {
1790
                return false;
1791
            }
1792
        }
1793
1794
        $doc_id = (int) $doc_id;
1795
        $session_id = (int) $session_id;
1796
        // 2. Course and Session visibility are handle in local.inc.php/global.inc.php
1797
        // 3. Checking if user exist in course/session
1798
        if ($session_id == 0) {
1799
            if (is_null($userIsSubscribed)) {
1800
                $userIsSubscribed = CourseManager::is_user_subscribed_in_course(
1801
                    $user_id,
1802
                    $course_info['code']
1803
                );
1804
            }
1805
1806
            if ($userIsSubscribed === true || api_is_platform_admin()) {
1807
                $user_in_course = true;
1808
            }
1809
1810
            // Check if course is open then we can consider that the student is registered to the course
1811
            if (isset($course_info) &&
1812
                in_array(
1813
                    $course_info['visibility'],
1814
                    [COURSE_VISIBILITY_OPEN_PLATFORM, COURSE_VISIBILITY_OPEN_WORLD]
1815
                )
1816
            ) {
1817
                $user_in_course = true;
1818
            }
1819
        } else {
1820
            $user_status = SessionManager::get_user_status_in_course_session(
1821
                $user_id,
1822
                $course_info['real_id'],
1823
                $session_id
1824
            );
1825
1826
            if (in_array($user_status, ['0', '2', '6'])) {
1827
                //is true if is an student, course session teacher or coach
1828
                $user_in_course = true;
1829
            }
1830
1831
            if (api_is_platform_admin()) {
1832
                $user_in_course = true;
1833
            }
1834
        }
1835
1836
        // 4. Checking document visibility (i'm repeating the code in order to be more clear when reading ) - jm
1837
        if ($user_in_course) {
1838
            if (true === api_get_configuration_value('document_enable_accessible_from_date')) {
1839
                $extraFieldValue = new ExtraFieldValue('document');
1840
                $extraValue = $extraFieldValue->get_values_by_handler_and_field_variable($doc_id, 'accessible_from');
1841
                if (!empty($extraValue) && isset($extraValue['value'])) {
1842
                    $now = new DateTime();
1843
                    $accessibleDate = new DateTime($extraValue['value']);
1844
                    if ($now < $accessibleDate) {
1845
                        return false;
1846
                    }
1847
                }
1848
            }
1849
            // 4.1 Checking document visibility for a Course
1850
            if ($session_id == 0) {
1851
                $item_info = api_get_item_property_info(
1852
                    $course_info['real_id'],
1853
                    'document',
1854
                    $doc_id,
1855
                    0
1856
                );
1857
1858
                if (isset($item_info['visibility'])) {
1859
                    // True for admins if document exists
1860
                    if ($admins_can_see_everything && api_is_platform_admin()) {
1861
                        return true;
1862
                    }
1863
                    if ($item_info['visibility'] == 1) {
1864
                        return true;
1865
                    }
1866
                }
1867
            } else {
1868
                // 4.2 Checking document visibility for a Course in a Session
1869
                $item_info = api_get_item_property_info(
1870
                    $course_info['real_id'],
1871
                    'document',
1872
                    $doc_id,
1873
                    0
1874
                );
1875
1876
                $item_info_in_session = api_get_item_property_info(
1877
                    $course_info['real_id'],
1878
                    'document',
1879
                    $doc_id,
1880
                    $session_id
1881
                );
1882
1883
                // True for admins if document exists
1884
                if (isset($item_info['visibility'])) {
1885
                    if ($admins_can_see_everything && api_is_platform_admin()) {
1886
                        return true;
1887
                    }
1888
                }
1889
1890
                if (isset($item_info_in_session['visibility'])) {
1891
                    if ($item_info_in_session['visibility'] == 1) {
1892
                        return true;
1893
                    }
1894
                } else {
1895
                    if ($item_info['visibility'] == 1) {
1896
                        return true;
1897
                    }
1898
                }
1899
            }
1900
        } elseif ($admins_can_see_everything && api_is_platform_admin()) {
1901
            return true;
1902
        }
1903
1904
        return false;
1905
    }
1906
1907
    /**
1908
     * Allow attach a certificate to a course.
1909
     *
1910
     * @todo move to certificate.lib.php
1911
     *
1912
     * @param string $course_id
1913
     * @param int    $document_id
1914
     * @param int    $session_id
1915
     */
1916
    public static function attach_gradebook_certificate($course_id, $document_id, $session_id = 0)
1917
    {
1918
        $tbl_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1919
        $session_id = intval($session_id);
1920
        if (empty($session_id)) {
1921
            $session_id = api_get_session_id();
1922
        }
1923
1924
        if (empty($session_id)) {
1925
            $sql_session = 'AND (session_id = 0 OR isnull(session_id)) ';
1926
        } elseif ($session_id > 0) {
1927
            $sql_session = 'AND session_id='.intval($session_id);
1928
        } else {
1929
            $sql_session = '';
1930
        }
1931
        $sql = 'UPDATE '.$tbl_category.' SET document_id="'.intval($document_id).'"
1932
                WHERE course_code="'.Database::escape_string($course_id).'" '.$sql_session;
1933
        Database::query($sql);
1934
    }
1935
1936
    /**
1937
     * get the document id of default certificate.
1938
     *
1939
     * @todo move to certificate.lib.php
1940
     *
1941
     * @param string $course_id
1942
     * @param int    $session_id
1943
     *
1944
     * @return int The default certificate id
1945
     */
1946
    public static function get_default_certificate_id($course_id, $session_id = 0)
1947
    {
1948
        $tbl_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
1949
        $session_id = (int) $session_id;
1950
        if (empty($session_id)) {
1951
            $session_id = api_get_session_id();
1952
        }
1953
1954
        if (empty($session_id)) {
1955
            $sql_session = 'AND (session_id = 0 OR isnull(session_id)) ';
1956
        } elseif ($session_id > 0) {
1957
            $sql_session = 'AND session_id='.$session_id;
1958
        } else {
1959
            $sql_session = '';
1960
        }
1961
        $sql = 'SELECT document_id FROM '.$tbl_category.'
1962
                WHERE course_code="'.Database::escape_string($course_id).'" '.$sql_session;
1963
1964
        $rs = Database::query($sql);
1965
        $num = Database::num_rows($rs);
1966
        if ($num == 0) {
1967
            return null;
1968
        }
1969
        $row = Database::fetch_array($rs);
1970
1971
        return $row['document_id'];
1972
    }
1973
1974
    /**
1975
     * Allow replace user info in file html.
1976
     *
1977
     * @param int    $user_id
1978
     * @param string $course_code
1979
     * @param int    $sessionId
1980
     * @param bool   $is_preview
1981
     *
1982
     * @return array
1983
     */
1984
    public static function replace_user_info_into_html(
1985
        $user_id,
1986
        $course_code,
1987
        $sessionId,
1988
        $is_preview = false
1989
    ) {
1990
        $user_id = (int) $user_id;
1991
        $sessionId = (int) $sessionId;
1992
        $course_info = api_get_course_info($course_code);
1993
        $tbl_document = Database::get_course_table(TABLE_DOCUMENT);
1994
        $course_id = $course_info['real_id'];
1995
1996
        $document_id = self::get_default_certificate_id(
1997
            $course_code,
1998
            $sessionId
1999
        );
2000
2001
        $my_content_html = null;
2002
        if ($document_id) {
2003
            $sql = "SELECT path FROM $tbl_document
2004
                    WHERE c_id = $course_id AND id = $document_id";
2005
            $rs = Database::query($sql);
2006
            $new_content = '';
2007
            $all_user_info = [];
2008
            if (Database::num_rows($rs)) {
2009
                $row = Database::fetch_array($rs);
2010
                $filepath = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document'.$row['path'];
2011
                if (is_file($filepath)) {
2012
                    $my_content_html = file_get_contents($filepath);
2013
                }
2014
                $all_user_info = self::get_all_info_to_certificate(
2015
                    $user_id,
2016
                    $course_code,
2017
                    $sessionId,
2018
                    $is_preview
2019
                );
2020
2021
                $info_to_be_replaced_in_content_html = $all_user_info[0];
2022
                $info_to_replace_in_content_html = $all_user_info[1];
2023
                $new_content = str_replace(
2024
                    $info_to_be_replaced_in_content_html,
2025
                    $info_to_replace_in_content_html,
2026
                    $my_content_html
2027
                );
2028
            }
2029
2030
            return [
2031
                'content' => $new_content,
2032
                'variables' => $all_user_info,
2033
            ];
2034
        }
2035
2036
        return [];
2037
    }
2038
2039
    /**
2040
     * Return all content to replace and all content to be replace.
2041
     *
2042
     * @param int    $user_id
2043
     * @param string $courseCode
2044
     * @param bool   $is_preview
2045
     *
2046
     * @return array
2047
     */
2048
    public static function get_all_info_to_certificate($user_id, $courseCode, $sessionId, $is_preview = false)
2049
    {
2050
        $info_list = [];
2051
        $user_id = (int) $user_id;
2052
        $course_info = api_get_course_info($courseCode);
2053
        $sessionId = (int) $sessionId;
2054
2055
        // Portal info
2056
        $organization_name = api_get_setting('Institution');
2057
        $portal_name = api_get_setting('siteName');
2058
2059
        // Extra user data information
2060
        $extra_user_info_data = UserManager::get_extra_user_data(
2061
            $user_id,
2062
            false,
2063
            false,
2064
            false,
2065
            true
2066
        );
2067
2068
        // get extra fields
2069
        $extraField = new ExtraField('user');
2070
        $extraFields = $extraField->get_all(['filter = ? AND visible_to_self = ?' => [1, 1]]);
2071
2072
        // Student information
2073
        $user_info = api_get_user_info($user_id);
2074
        $first_name = $user_info['firstname'];
2075
        $last_name = $user_info['lastname'];
2076
        $username = $user_info['username'];
2077
        $user_picture = UserManager::getUserPicture($user_id, USER_IMAGE_SIZE_ORIGINAL);
2078
        $official_code = $user_info['official_code'];
2079
2080
        // Teacher information
2081
        $teacher_first_name = '';
2082
        $teacher_last_name = '';
2083
        $info_teacher_id = UserManager::get_user_id_of_course_admin_or_session_admin($course_info);
2084
2085
        if ($info_teacher_id) {
2086
            [
2087
                'firstname' => $teacher_first_name,
2088
                'lastname' => $teacher_last_name,
2089
            ] = api_get_user_info($info_teacher_id);
2090
        }
2091
2092
        // info gradebook certificate
2093
        $info_grade_certificate = UserManager::get_info_gradebook_certificate($courseCode, $sessionId, $user_id);
2094
        $date_long_certificate = '';
2095
        $date_certificate = '';
2096
        $date_short_no_time = '';
2097
        $url = '';
2098
        if ($info_grade_certificate) {
2099
            $date_certificate = $info_grade_certificate['created_at'];
2100
            $url = api_get_path(WEB_PATH).'certificates/index.php?id='.$info_grade_certificate['id'];
2101
        }
2102
        $date_no_time = api_convert_and_format_date(api_get_utc_datetime(), DATE_FORMAT_LONG_NO_DAY);
2103
        $date_short_no_time = api_convert_and_format_date(api_get_utc_datetime(), DATE_FORMAT_NUMBER);
2104
        if (!empty($date_certificate)) {
2105
            $date_long_certificate = api_convert_and_format_date($date_certificate);
2106
            $date_no_time = api_convert_and_format_date($date_certificate, DATE_FORMAT_LONG_NO_DAY);
2107
            $date_short_no_time = api_convert_and_format_date($date_certificate, DATE_FORMAT_NUMBER);
2108
        }
2109
2110
        if ($is_preview) {
2111
            $date_long_certificate = api_convert_and_format_date(api_get_utc_datetime());
2112
            $date_no_time = api_convert_and_format_date(api_get_utc_datetime(), DATE_FORMAT_LONG_NO_DAY);
2113
            $date_short_no_time = api_convert_and_format_date(api_get_utc_datetime(), DATE_FORMAT_NUMBER);
2114
        }
2115
2116
        $externalStyleFile = api_get_path(SYS_CSS_PATH).'themes/'.api_get_visual_theme().'/certificate.css';
2117
        $externalStyle = '';
2118
        if (is_file($externalStyleFile)) {
2119
            $externalStyle = file_get_contents($externalStyleFile);
2120
        }
2121
        $timeInCourse = Tracking::get_time_spent_on_the_course($user_id, $course_info['real_id'], $sessionId);
2122
        $timeInCourse = api_time_to_hms($timeInCourse, ':', false, true);
2123
2124
        $timeInCourseInAllSessions = 0;
2125
        $sessions = SessionManager::get_session_by_course($course_info['real_id']);
2126
2127
        if (!empty($sessions)) {
2128
            foreach ($sessions as $session) {
2129
                $timeInCourseInAllSessions += Tracking::get_time_spent_on_the_course($user_id, $course_info['real_id'], $session['id']);
2130
            }
2131
        }
2132
        $timeInCourseInAllSessions = api_time_to_hms($timeInCourseInAllSessions, ':', false, true);
2133
2134
        $first = Tracking::get_first_connection_date_on_the_course($user_id, $course_info['real_id'], $sessionId, false);
2135
        $first = api_convert_and_format_date($first, DATE_FORMAT_NUMBER);
2136
2137
        $last = Tracking::get_last_connection_date_on_the_course($user_id, $course_info, $sessionId, false);
2138
        $last = api_convert_and_format_date($last, DATE_FORMAT_NUMBER);
2139
2140
        if ($first === $last) {
2141
            $startDateAndEndDate = get_lang('The').' '.$first;
2142
        } else {
2143
            $startDateAndEndDate = sprintf(
2144
                get_lang('FromDateXToDateY'),
2145
                $first,
2146
                $last
2147
            );
2148
        }
2149
        $courseDescription = new CourseDescription();
2150
        $description = $courseDescription->get_data_by_description_type(2, $course_info['real_id'], $sessionId);
2151
        $courseObjectives = '';
2152
        if ($description) {
2153
            $courseObjectives = $description['description_content'];
2154
        }
2155
2156
        // Replace content
2157
        $info_to_replace_in_content_html = [
2158
            $first_name,
2159
            $last_name,
2160
            $username,
2161
            $user_picture,
2162
            $organization_name,
2163
            $portal_name,
2164
            $teacher_first_name,
2165
            $teacher_last_name,
2166
            $official_code,
2167
            $date_long_certificate,
2168
            $date_no_time,
2169
            $date_short_no_time,
2170
            $courseCode,
2171
            $course_info['name'],
2172
            isset($info_grade_certificate['grade']) ? $info_grade_certificate['grade'] : '',
2173
            $url,
2174
            '<a href="'.$url.'" target="_blank">'.get_lang('CertificateOnlineLink').'</a>',
2175
            '((certificate_barcode))',
2176
            $externalStyle,
2177
            $timeInCourse,
2178
            $timeInCourseInAllSessions,
2179
            $startDateAndEndDate,
2180
            $courseObjectives,
2181
        ];
2182
2183
        $tags = [
2184
            '((user_firstname))',
2185
            '((user_lastname))',
2186
            '((user_username))',
2187
            '((user_picture))',
2188
            '((gradebook_institution))',
2189
            '((gradebook_sitename))',
2190
            '((teacher_firstname))',
2191
            '((teacher_lastname))',
2192
            '((official_code))',
2193
            '((date_certificate))',
2194
            '((date_certificate_no_time))',
2195
            '((date_simple_certificate))',
2196
            '((course_code))',
2197
            '((course_title))',
2198
            '((gradebook_grade))',
2199
            '((certificate_link))',
2200
            '((certificate_link_html))',
2201
            '((certificate_barcode))',
2202
            '((external_style))',
2203
            '((time_in_course))',
2204
            '((time_in_course_in_all_sessions))',
2205
            '((start_date_and_end_date))',
2206
            '((course_objectives))',
2207
        ];
2208
2209
        if (!empty($extraFields)) {
2210
            $efv = new ExtraFieldValue('user');
2211
2212
            foreach ($extraFields as $extraField) {
2213
                $valueExtra = $efv->get_values_by_handler_and_field_variable(
2214
                    $user_id,
2215
                    $extraField['variable'],
2216
                    true
2217
                );
2218
                $tags[] = '(('.strtolower($extraField['variable']).'))';
2219
                $info_to_replace_in_content_html[] = $valueExtra['value'];
2220
            }
2221
        }
2222
2223
        $info_list[] = $tags;
2224
        $info_list[] = $info_to_replace_in_content_html;
2225
2226
        return $info_list;
2227
    }
2228
2229
    /**
2230
     * Remove default certificate.
2231
     *
2232
     * @param string $course_id              The course code
2233
     * @param int    $default_certificate_id The document id of the default certificate
2234
     */
2235
    public static function remove_attach_certificate($course_id, $default_certificate_id)
2236
    {
2237
        if (empty($default_certificate_id)) {
2238
            return false;
2239
        }
2240
2241
        $default_certificate = self::get_default_certificate_id($course_id);
2242
        if ((int) $default_certificate == (int) $default_certificate_id) {
2243
            $tbl_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
2244
            $session_id = api_get_session_id();
2245
            if ($session_id == 0 || is_null($session_id)) {
2246
                $sql_session = 'AND (session_id='.intval($session_id).' OR isnull(session_id)) ';
2247
            } elseif ($session_id > 0) {
2248
                $sql_session = 'AND session_id='.intval($session_id);
2249
            } else {
2250
                $sql_session = '';
2251
            }
2252
2253
            $sql = 'UPDATE '.$tbl_category.' SET document_id = null
2254
                    WHERE
2255
                        course_code = "'.Database::escape_string($course_id).'" AND
2256
                        document_id="'.$default_certificate_id.'" '.$sql_session;
2257
            Database::query($sql);
2258
        }
2259
    }
2260
2261
    /**
2262
     * Create directory certificate.
2263
     *
2264
     * @param array $courseInfo
2265
     */
2266
    public static function create_directory_certificate_in_course($courseInfo)
2267
    {
2268
        if (!empty($courseInfo)) {
2269
            $to_group_id = 0;
2270
            $to_user_id = null;
2271
            $course_dir = $courseInfo['path']."/document/";
2272
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
2273
            $base_work_dir = $sys_course_path.$course_dir;
2274
            $dir_name = '/certificates';
2275
            $post_dir_name = get_lang('CertificatesFiles');
2276
            $visibility_command = 'invisible';
2277
2278
            $id = self::get_document_id_of_directory_certificate();
2279
2280
            if (empty($id)) {
2281
                create_unexisting_directory(
2282
                    $courseInfo,
2283
                    api_get_user_id(),
2284
                    api_get_session_id(),
2285
                    $to_group_id,
2286
                    $to_user_id,
2287
                    $base_work_dir,
2288
                    $dir_name,
2289
                    $post_dir_name,
2290
                    null,
2291
                    false,
2292
                    false
2293
                );
2294
2295
                $id = self::get_document_id_of_directory_certificate();
2296
2297
                if (empty($id)) {
2298
                    $id = add_document(
2299
                        $courseInfo,
2300
                        $dir_name,
2301
                        'folder',
2302
                        0,
2303
                        $post_dir_name,
2304
                        null,
2305
                        0,
2306
                        true,
2307
                        $to_group_id,
2308
                        0,
2309
                        0,
2310
                        false
2311
                    );
2312
                }
2313
2314
                if (!empty($id)) {
2315
                    api_item_property_update(
2316
                        $courseInfo,
2317
                        TOOL_DOCUMENT,
2318
                        $id,
2319
                        $visibility_command,
2320
                        api_get_user_id()
2321
                    );
2322
                }
2323
            }
2324
        }
2325
    }
2326
2327
    /**
2328
     * Get the document id of the directory certificate.
2329
     *
2330
     * @return int The document id of the directory certificate
2331
     *
2332
     * @todo move to certificate.lib.php
2333
     */
2334
    public static function get_document_id_of_directory_certificate()
2335
    {
2336
        $tbl_document = Database::get_course_table(TABLE_DOCUMENT);
2337
        $course_id = api_get_course_int_id();
2338
        $sql = "SELECT id FROM $tbl_document
2339
                WHERE c_id = $course_id AND path='/certificates' ";
2340
        $rs = Database::query($sql);
2341
        $row = Database::fetch_array($rs);
2342
        if ($row) {
2343
            return $row['id'];
2344
        }
2345
2346
        return 0;
2347
    }
2348
2349
    /**
2350
     * Check if a directory given is for certificate.
2351
     *
2352
     * @todo move to certificate.lib.php
2353
     *
2354
     * @param string $dir path of directory
2355
     *
2356
     * @return bool true if is a certificate or false otherwise
2357
     */
2358
    public static function is_certificate_mode($dir)
2359
    {
2360
        // I'm in the certification module?
2361
        $is_certificate_mode = false;
2362
        $is_certificate_array = explode('/', $dir);
2363
        array_shift($is_certificate_array);
2364
        if (isset($is_certificate_array[0]) && $is_certificate_array[0] == 'certificates') {
2365
            $is_certificate_mode = true;
2366
        }
2367
2368
        return $is_certificate_mode || (isset($_GET['certificate']) && $_GET['certificate'] === 'true');
2369
    }
2370
2371
    /**
2372
     * Gets the list of included resources as a list of absolute or relative paths from a html file or string html
2373
     * This allows for a better SCORM export or replace urls inside content html from copy course
2374
     * The list will generally include pictures, flash objects, java applets, or any other
2375
     * stuff included in the source of the current item. The current item is expected
2376
     * to be an HTML file or string html. If it is not, then the function will return and empty list.
2377
     *
2378
     * @param    string  source html (content or path)
2379
     * @param    bool    is file or string html
2380
     * @param    string    type (one of the app tools) - optional (otherwise takes the current item's type)
2381
     * @param    int        level of recursivity we're in
2382
     *
2383
     * @return array List of file paths. An additional field containing 'local' or 'remote' helps determine
2384
     *               if the file should be copied into the zip or just linked
2385
     */
2386
    public static function get_resources_from_source_html(
2387
        $source_html,
2388
        $is_file = false,
2389
        $type = null,
2390
        $recursivity = 1
2391
    ) {
2392
        $max = 5;
2393
        $attributes = [];
2394
        $wanted_attributes = [
2395
            'src',
2396
            'url',
2397
            '@import',
2398
            'href',
2399
            'value',
2400
            'flashvars',
2401
            'poster',
2402
        ];
2403
        $explode_attributes = ['flashvars' => 'file'];
2404
        $abs_path = '';
2405
2406
        if ($recursivity > $max) {
2407
            return [];
2408
        }
2409
2410
        if (!isset($type)) {
2411
            $type = TOOL_DOCUMENT;
2412
        }
2413
2414
        if (!$is_file) {
2415
            $attributes = self::parse_HTML_attributes(
2416
                $source_html,
2417
                $wanted_attributes,
2418
                $explode_attributes
2419
            );
2420
        } else {
2421
            if (is_file($source_html)) {
2422
                $abs_path = $source_html;
2423
                //for now, read the whole file in one go (that's gonna be a problem when the file is too big)
2424
                $info = pathinfo($abs_path);
2425
                $ext = $info['extension'];
2426
                switch (strtolower($ext)) {
2427
                    case 'html':
2428
                    case 'htm':
2429
                    case 'shtml':
2430
                    case 'css':
2431
                        $file_content = file_get_contents($abs_path);
2432
                        // get an array of attributes from the HTML source
2433
                        $attributes = self::parse_HTML_attributes(
2434
                            $file_content,
2435
                            $wanted_attributes,
2436
                            $explode_attributes
2437
                        );
2438
                        break;
2439
                    default:
2440
                        break;
2441
                }
2442
            } else {
2443
                return false;
2444
            }
2445
        }
2446
2447
        $files_list = [];
2448
        switch ($type) {
2449
            case TOOL_DOCUMENT:
2450
            case TOOL_QUIZ:
2451
            case 'sco':
2452
                foreach ($wanted_attributes as $attr) {
2453
                    if (isset($attributes[$attr])) {
2454
                        //find which kind of path these are (local or remote)
2455
                        $sources = $attributes[$attr];
2456
                        foreach ($sources as $source) {
2457
                            //skip what is obviously not a resource
2458
                            if (strpos($source, '+this.')) {
2459
                                continue; //javascript code - will still work unaltered
2460
                            }
2461
                            if (strpos($source, '.') === false) {
2462
                                continue; //no dot, should not be an external file anyway
2463
                            }
2464
                            if (strpos($source, 'mailto:')) {
2465
                                continue; //mailto link
2466
                            }
2467
                            if (strpos($source, ';') && !strpos($source, '&amp;')) {
2468
                                continue; //avoid code - that should help
2469
                            }
2470
2471
                            if ($attr == 'value') {
2472
                                if (strpos($source, 'mp3file')) {
2473
                                    $files_list[] = [
2474
                                        substr($source, 0, strpos($source, '.swf') + 4),
2475
                                        'local',
2476
                                        'abs',
2477
                                    ];
2478
                                    $mp3file = substr($source, strpos($source, 'mp3file=') + 8);
2479
                                    if (substr($mp3file, 0, 1) == '/') {
2480
                                        $files_list[] = [$mp3file, 'local', 'abs'];
2481
                                    } else {
2482
                                        $files_list[] = [$mp3file, 'local', 'rel'];
2483
                                    }
2484
                                } elseif (strpos($source, 'flv=') === 0) {
2485
                                    $source = substr($source, 4);
2486
                                    if (strpos($source, '&') > 0) {
2487
                                        $source = substr($source, 0, strpos($source, '&'));
2488
                                    }
2489
                                    if (strpos($source, '://') > 0) {
2490
                                        if (strpos($source, api_get_path(WEB_PATH)) !== false) {
2491
                                            //we found the current portal url
2492
                                            $files_list[] = [$source, 'local', 'url'];
2493
                                        } else {
2494
                                            //we didn't find any trace of current portal
2495
                                            $files_list[] = [$source, 'remote', 'url'];
2496
                                        }
2497
                                    } else {
2498
                                        $files_list[] = [$source, 'local', 'abs'];
2499
                                    }
2500
                                    /* skipping anything else to avoid two entries
2501
                                    (while the others can have sub-files in their url, flv's can't)*/
2502
                                    continue;
2503
                                }
2504
                            }
2505
                            if (strpos($source, '://') > 0) {
2506
                                //cut at '?' in a URL with params
2507
                                if (strpos($source, '?') > 0) {
2508
                                    $second_part = substr($source, strpos($source, '?'));
2509
                                    if (strpos($second_part, '://') > 0) {
2510
                                        //if the second part of the url contains a url too, treat the second one before cutting
2511
                                        $pos1 = strpos($second_part, '=');
2512
                                        $pos2 = strpos($second_part, '&');
2513
                                        $second_part = substr($second_part, $pos1 + 1, $pos2 - ($pos1 + 1));
2514
                                        if (strpos($second_part, api_get_path(WEB_PATH)) !== false) {
2515
                                            //we found the current portal url
2516
                                            $files_list[] = [$second_part, 'local', 'url'];
2517
                                            $in_files_list[] = self::get_resources_from_source_html(
2518
                                                $second_part,
2519
                                                true,
2520
                                                TOOL_DOCUMENT,
2521
                                                $recursivity + 1
2522
                                            );
2523
                                            if (count($in_files_list) > 0) {
2524
                                                $files_list = array_merge($files_list, $in_files_list);
2525
                                            }
2526
                                        } else {
2527
                                            //we didn't find any trace of current portal
2528
                                            $files_list[] = [$second_part, 'remote', 'url'];
2529
                                        }
2530
                                    } elseif (strpos($second_part, '=') > 0) {
2531
                                        if (substr($second_part, 0, 1) === '/') {
2532
                                            //link starts with a /, making it absolute (relative to DocumentRoot)
2533
                                            $files_list[] = [$second_part, 'local', 'abs'];
2534
                                            $in_files_list[] = self::get_resources_from_source_html(
2535
                                                $second_part,
2536
                                                true,
2537
                                                TOOL_DOCUMENT,
2538
                                                $recursivity + 1
2539
                                            );
2540
                                            if (count($in_files_list) > 0) {
2541
                                                $files_list = array_merge($files_list, $in_files_list);
2542
                                            }
2543
                                        } elseif (strstr($second_part, '..') === 0) {
2544
                                            //link is relative but going back in the hierarchy
2545
                                            $files_list[] = [$second_part, 'local', 'rel'];
2546
                                            //$dir = api_get_path(SYS_CODE_PATH);//dirname($abs_path);
2547
                                            //$new_abs_path = realpath($dir.'/'.$second_part);
2548
                                            $dir = '';
2549
                                            if (!empty($abs_path)) {
2550
                                                $dir = dirname($abs_path).'/';
2551
                                            }
2552
                                            $new_abs_path = realpath($dir.$second_part);
2553
                                            $in_files_list[] = self::get_resources_from_source_html(
2554
                                                $new_abs_path,
2555
                                                true,
2556
                                                TOOL_DOCUMENT,
2557
                                                $recursivity + 1
2558
                                            );
2559
                                            if (count($in_files_list) > 0) {
2560
                                                $files_list = array_merge($files_list, $in_files_list);
2561
                                            }
2562
                                        } else {
2563
                                            //no starting '/', making it relative to current document's path
2564
                                            if (substr($second_part, 0, 2) == './') {
2565
                                                $second_part = substr($second_part, 2);
2566
                                            }
2567
                                            $files_list[] = [$second_part, 'local', 'rel'];
2568
                                            $dir = '';
2569
                                            if (!empty($abs_path)) {
2570
                                                $dir = dirname($abs_path).'/';
2571
                                            }
2572
                                            $new_abs_path = realpath($dir.$second_part);
2573
                                            $in_files_list[] = self::get_resources_from_source_html(
2574
                                                $new_abs_path,
2575
                                                true,
2576
                                                TOOL_DOCUMENT,
2577
                                                $recursivity + 1
2578
                                            );
2579
                                            if (count($in_files_list) > 0) {
2580
                                                $files_list = array_merge($files_list, $in_files_list);
2581
                                            }
2582
                                        }
2583
                                    }
2584
                                    //leave that second part behind now
2585
                                    $source = substr($source, 0, strpos($source, '?'));
2586
                                    if (strpos($source, '://') > 0) {
2587
                                        if (strpos($source, api_get_path(WEB_PATH)) !== false) {
2588
                                            //we found the current portal url
2589
                                            $files_list[] = [$source, 'local', 'url'];
2590
                                            $in_files_list[] = self::get_resources_from_source_html(
2591
                                                $source,
2592
                                                true,
2593
                                                TOOL_DOCUMENT,
2594
                                                $recursivity + 1
2595
                                            );
2596
                                            if (count($in_files_list) > 0) {
2597
                                                $files_list = array_merge($files_list, $in_files_list);
2598
                                            }
2599
                                        } else {
2600
                                            //we didn't find any trace of current portal
2601
                                            $files_list[] = [$source, 'remote', 'url'];
2602
                                        }
2603
                                    } else {
2604
                                        //no protocol found, make link local
2605
                                        if (substr($source, 0, 1) === '/') {
2606
                                            //link starts with a /, making it absolute (relative to DocumentRoot)
2607
                                            $files_list[] = [$source, 'local', 'abs'];
2608
                                            $in_files_list[] = self::get_resources_from_source_html(
2609
                                                $source,
2610
                                                true,
2611
                                                TOOL_DOCUMENT,
2612
                                                $recursivity + 1
2613
                                            );
2614
                                            if (count($in_files_list) > 0) {
2615
                                                $files_list = array_merge($files_list, $in_files_list);
2616
                                            }
2617
                                        } elseif (strstr($source, '..') === 0) {
2618
                                            //link is relative but going back in the hierarchy
2619
                                            $files_list[] = [$source, 'local', 'rel'];
2620
                                            $dir = '';
2621
                                            if (!empty($abs_path)) {
2622
                                                $dir = dirname($abs_path).'/';
2623
                                            }
2624
                                            $new_abs_path = realpath($dir.$source);
2625
                                            $in_files_list[] = self::get_resources_from_source_html(
2626
                                                $new_abs_path,
2627
                                                true,
2628
                                                TOOL_DOCUMENT,
2629
                                                $recursivity + 1
2630
                                            );
2631
                                            if (count($in_files_list) > 0) {
2632
                                                $files_list = array_merge($files_list, $in_files_list);
2633
                                            }
2634
                                        } else {
2635
                                            //no starting '/', making it relative to current document's path
2636
                                            if (substr($source, 0, 2) == './') {
2637
                                                $source = substr($source, 2);
2638
                                            }
2639
                                            $files_list[] = [$source, 'local', 'rel'];
2640
                                            $dir = '';
2641
                                            if (!empty($abs_path)) {
2642
                                                $dir = dirname($abs_path).'/';
2643
                                            }
2644
                                            $new_abs_path = realpath($dir.$source);
2645
                                            $in_files_list[] = self::get_resources_from_source_html(
2646
                                                $new_abs_path,
2647
                                                true,
2648
                                                TOOL_DOCUMENT,
2649
                                                $recursivity + 1
2650
                                            );
2651
                                            if (count($in_files_list) > 0) {
2652
                                                $files_list = array_merge($files_list, $in_files_list);
2653
                                            }
2654
                                        }
2655
                                    }
2656
                                }
2657
                                //found some protocol there
2658
                                if (strpos($source, api_get_path(WEB_PATH)) !== false) {
2659
                                    //we found the current portal url
2660
                                    $files_list[] = [$source, 'local', 'url'];
2661
                                    $in_files_list[] = self::get_resources_from_source_html(
2662
                                        $source,
2663
                                        true,
2664
                                        TOOL_DOCUMENT,
2665
                                        $recursivity + 1
2666
                                    );
2667
                                    if (count($in_files_list) > 0) {
2668
                                        $files_list = array_merge($files_list, $in_files_list);
2669
                                    }
2670
                                } else {
2671
                                    //we didn't find any trace of current portal
2672
                                    $files_list[] = [$source, 'remote', 'url'];
2673
                                }
2674
                            } else {
2675
                                //no protocol found, make link local
2676
                                if (substr($source, 0, 1) === '/') {
2677
                                    //link starts with a /, making it absolute (relative to DocumentRoot)
2678
                                    $files_list[] = [$source, 'local', 'abs'];
2679
                                    $in_files_list[] = self::get_resources_from_source_html(
2680
                                        $source,
2681
                                        true,
2682
                                        TOOL_DOCUMENT,
2683
                                        $recursivity + 1
2684
                                    );
2685
                                    if (count($in_files_list) > 0) {
2686
                                        $files_list = array_merge($files_list, $in_files_list);
2687
                                    }
2688
                                } elseif (strpos($source, '..') === 0) {
2689
                                    //link is relative but going back in the hierarchy
2690
                                    $files_list[] = [$source, 'local', 'rel'];
2691
                                    $dir = '';
2692
                                    if (!empty($abs_path)) {
2693
                                        $dir = dirname($abs_path).'/';
2694
                                    }
2695
                                    $new_abs_path = realpath($dir.$source);
2696
                                    $in_files_list[] = self::get_resources_from_source_html(
2697
                                        $new_abs_path,
2698
                                        true,
2699
                                        TOOL_DOCUMENT,
2700
                                        $recursivity + 1
2701
                                    );
2702
                                    if (count($in_files_list) > 0) {
2703
                                        $files_list = array_merge($files_list, $in_files_list);
2704
                                    }
2705
                                } else {
2706
                                    //no starting '/', making it relative to current document's path
2707
                                    if (substr($source, 0, 2) == './') {
2708
                                        $source = substr($source, 2);
2709
                                    }
2710
                                    $files_list[] = [$source, 'local', 'rel'];
2711
                                    $dir = '';
2712
                                    if (!empty($abs_path)) {
2713
                                        $dir = dirname($abs_path).'/';
2714
                                    }
2715
                                    $new_abs_path = realpath($dir.$source);
2716
                                    $in_files_list[] = self::get_resources_from_source_html(
2717
                                        $new_abs_path,
2718
                                        true,
2719
                                        TOOL_DOCUMENT,
2720
                                        $recursivity + 1
2721
                                    );
2722
                                    if (count($in_files_list) > 0) {
2723
                                        $files_list = array_merge($files_list, $in_files_list);
2724
                                    }
2725
                                }
2726
                            }
2727
                        }
2728
                    }
2729
                }
2730
                break;
2731
            default: //ignore
2732
                break;
2733
        }
2734
2735
        $checked_files_list = [];
2736
        $checked_array_list = [];
2737
2738
        if (count($files_list) > 0) {
2739
            foreach ($files_list as $idx => $file) {
2740
                if (!empty($file[0])) {
2741
                    if (!in_array($file[0], $checked_files_list)) {
2742
                        $checked_files_list[] = $files_list[$idx][0];
2743
                        $checked_array_list[] = $files_list[$idx];
2744
                    }
2745
                }
2746
            }
2747
        }
2748
2749
        return $checked_array_list;
2750
    }
2751
2752
    /**
2753
     * Parses the HTML attributes given as string.
2754
     *
2755
     * @param string HTML attribute string
2756
     * @param array List of attributes that we want to get back
2757
     * @param array
2758
     *
2759
     * @return array An associative array of attributes
2760
     *
2761
     * @author Based on a function from the HTML_Common2 PEAR module     *
2762
     */
2763
    public static function parse_HTML_attributes($attrString, $wanted = [], $explode_variables = [])
2764
    {
2765
        $attributes = [];
2766
        $regs = [];
2767
        $reduced = false;
2768
        if (count($wanted) > 0) {
2769
            $reduced = true;
2770
        }
2771
        try {
2772
            //Find all occurences of something that looks like a URL
2773
            // The structure of this regexp is:
2774
            // (find protocol) then
2775
            // (optionally find some kind of space 1 or more times) then
2776
            // find (either an equal sign or a bracket) followed by an optional space
2777
            // followed by some text without quotes (between quotes itself or not)
2778
            // then possible closing brackets if we were in the opening bracket case
2779
            // OR something like @import()
2780
            $res = preg_match_all(
2781
                '/(((([A-Za-z_:])([A-Za-z0-9_:\.-]*))'.
2782
                // '/(((([A-Za-z_:])([A-Za-z0-9_:\.-]|[^\x00-\x7F])*)' . -> seems to be taking too much
2783
                // '/(((([A-Za-z_:])([^\x00-\x7F])*)' . -> takes only last letter of parameter name
2784
                '([ \n\t\r]+)?('.
2785
                // '(=([ \n\t\r]+)?("[^"]+"|\'[^\']+\'|[^ \n\t\r]+))' . -> doesn't restrict close enough to the url itself
2786
                '(=([ \n\t\r]+)?("[^"\)]+"|\'[^\'\)]+\'|[^ \n\t\r\)]+))'.
2787
                '|'.
2788
                // '(\(([ \n\t\r]+)?("[^"]+"|\'[^\']+\'|[^ \n\t\r]+)\))' . -> doesn't restrict close enough to the url itself
2789
                '(\(([ \n\t\r]+)?("[^"\)]+"|\'[^\'\)]+\'|[^ \n\t\r\)]+)\))'.
2790
                '))'.
2791
                '|'.
2792
                // '(@import([ \n\t\r]+)?("[^"]+"|\'[^\']+\'|[^ \n\t\r]+)))?/', -> takes a lot (like 100's of thousands of empty possibilities)
2793
                '(@import([ \n\t\r]+)?("[^"]+"|\'[^\']+\'|[^ \n\t\r]+)))/',
2794
                $attrString,
2795
                $regs
2796
            );
2797
        } catch (Exception $e) {
2798
            error_log('Caught exception: '.$e->getMessage(), 0);
2799
        }
2800
        if ($res) {
2801
            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...
2802
                $name = trim($regs[3][$i]);
2803
                $check = trim($regs[0][$i]);
2804
                $value = trim($regs[10][$i]);
2805
                if (empty($value) and !empty($regs[13][$i])) {
2806
                    $value = $regs[13][$i];
2807
                }
2808
                if (empty($name) && !empty($regs[16][$i])) {
2809
                    $name = '@import';
2810
                    $value = trim($regs[16][$i]);
2811
                }
2812
                if (!empty($name)) {
2813
                    if (!$reduced || in_array(strtolower($name), $wanted)) {
2814
                        if ($name == $check) {
2815
                            $attributes[strtolower($name)][] = strtolower($name);
2816
                        } else {
2817
                            if (!empty($value) && ($value[0] == '\'' || $value[0] == '"')) {
2818
                                $value = substr($value, 1, -1);
2819
                            }
2820
2821
                            if ($value == 'API.LMSGetValue(name') {
2822
                                $value = 'API.LMSGetValue(name)';
2823
                            }
2824
                            //Gets the xx.flv value from the string flashvars="width=320&height=240&autostart=false&file=xxx.flv&repeat=false"
2825
                            if (isset($explode_variables[$name])) {
2826
                                $value_modified = str_replace('&amp;', '&', $value);
2827
                                $value_array = explode('&', $value_modified);
2828
                                foreach ($value_array as $item) {
2829
                                    $itemParts = explode('=', $item);
2830
                                    $key = $itemParts[0];
2831
                                    $item_value = !empty($itemParts[1]) ? $itemParts[1] : '';
2832
                                    if ($key == $explode_variables[$name]) {
2833
                                        $attributes[strtolower($name)][] = $item_value;
2834
                                    }
2835
                                }
2836
                            }
2837
                            $attributes[strtolower($name)][] = $value;
2838
                        }
2839
                    }
2840
                }
2841
            }
2842
        }
2843
2844
        return $attributes;
2845
    }
2846
2847
    /**
2848
     * Replace urls inside content html from a copy course.
2849
     *
2850
     * @param string $content_html
2851
     * @param string $origin_course_code
2852
     * @param string $destination_course_directory
2853
     * @param string $origin_course_path_from_zip
2854
     * @param string $origin_course_info_path
2855
     *
2856
     * @return string new content html with replaced urls or return false if content is not a string
2857
     */
2858
    public static function replaceUrlWithNewCourseCode(
2859
        $content_html,
2860
        $origin_course_code,
2861
        $destination_course_directory,
2862
        $origin_course_path_from_zip = null,
2863
        $origin_course_info_path = null
2864
    ) {
2865
        if (empty($content_html)) {
2866
            return false;
2867
        }
2868
2869
        $orig_source_html = self::get_resources_from_source_html($content_html);
2870
        $orig_course_info = api_get_course_info($origin_course_code);
2871
2872
        // Course does not exist in the current DB probably this came from a zip file?
2873
        if (empty($orig_course_info)) {
2874
            if (!empty($origin_course_path_from_zip)) {
2875
                $orig_course_path = $origin_course_path_from_zip.'/';
2876
                $orig_course_info_path = $origin_course_info_path;
2877
            }
2878
        } else {
2879
            $orig_course_path = api_get_path(SYS_COURSE_PATH).$orig_course_info['path'].'/';
2880
            $orig_course_info_path = $orig_course_info['path'];
2881
        }
2882
2883
        $destination_course_code = CourseManager::getCourseCodeFromDirectory($destination_course_directory);
2884
        $destination_course_info = api_get_course_info($destination_course_code);
2885
        $dest_course_path = api_get_path(SYS_COURSE_PATH).$destination_course_directory.'/';
2886
        $dest_course_path_rel = api_get_path(REL_COURSE_PATH).$destination_course_directory.'/';
2887
2888
        $user_id = api_get_user_id();
2889
2890
        if (!empty($orig_source_html)) {
2891
            foreach ($orig_source_html as $source) {
2892
                // Get information about source url
2893
                $real_orig_url = $source[0]; // url
2894
                $scope_url = $source[1]; // scope (local, remote)
2895
                $type_url = $source[2]; // type (rel, abs, url)
2896
2897
                // Get path and query from origin url
2898
                $orig_parse_url = parse_url($real_orig_url);
2899
                $real_orig_path = isset($orig_parse_url['path']) ? $orig_parse_url['path'] : null;
2900
                $real_orig_query = isset($orig_parse_url['query']) ? $orig_parse_url['query'] : null;
2901
2902
                // Replace origin course code by destination course code from origin url query
2903
                $dest_url_query = '';
2904
2905
                if (!empty($real_orig_query)) {
2906
                    $dest_url_query = '?'.$real_orig_query;
2907
                    if (strpos($dest_url_query, $origin_course_code) !== false) {
2908
                        $dest_url_query = str_replace($origin_course_code, $destination_course_code, $dest_url_query);
2909
                    }
2910
                }
2911
2912
                if ($scope_url == 'local') {
2913
                    if ($type_url == 'abs' || $type_url == 'rel') {
2914
                        $document_file = strstr($real_orig_path, 'document');
2915
2916
                        if ($document_file && strpos($real_orig_path, $document_file) !== false) {
2917
                            $origin_filepath = $orig_course_path.$document_file;
2918
                            $destination_filepath = $dest_course_path.$document_file;
2919
2920
                            // copy origin file inside destination course
2921
                            if (file_exists($origin_filepath)) {
2922
                                $filepath_dir = dirname($destination_filepath);
2923
2924
                                if (!is_dir($filepath_dir)) {
2925
                                    $perm = api_get_permissions_for_new_directories();
2926
                                    $result = @mkdir($filepath_dir, $perm, true);
2927
                                    if ($result) {
2928
                                        $filepath_to_add = str_replace(
2929
                                            [$dest_course_path, 'document'],
2930
                                            '',
2931
                                            $filepath_dir
2932
                                        );
2933
2934
                                        //Add to item properties to the new folder
2935
                                        $doc_id = add_document(
2936
                                            $destination_course_info,
2937
                                            $filepath_to_add,
2938
                                            'folder',
2939
                                            0,
2940
                                            basename($filepath_to_add)
2941
                                        );
2942
                                        api_item_property_update(
2943
                                            $destination_course_info,
2944
                                            TOOL_DOCUMENT,
2945
                                            $doc_id,
2946
                                            'FolderCreated',
2947
                                            $user_id,
2948
                                            null,
2949
                                            null,
2950
                                            null,
2951
                                            null
2952
                                        );
2953
                                    }
2954
                                }
2955
2956
                                if (!file_exists($destination_filepath)) {
2957
                                    $result = @copy($origin_filepath, $destination_filepath);
2958
                                    if ($result) {
2959
                                        $filepath_to_add = str_replace(
2960
                                            [$dest_course_path, 'document'],
2961
                                            '',
2962
                                            $destination_filepath
2963
                                        );
2964
                                        $size = filesize($destination_filepath);
2965
2966
                                        // Add to item properties to the file
2967
                                        $doc_id = add_document(
2968
                                            $destination_course_info,
2969
                                            $filepath_to_add,
2970
                                            'file',
2971
                                            $size,
2972
                                            basename($filepath_to_add)
2973
                                        );
2974
                                        api_item_property_update(
2975
                                            $destination_course_info,
2976
                                            TOOL_DOCUMENT,
2977
                                            $doc_id,
2978
                                            'FolderCreated',
2979
                                            $user_id,
2980
                                            null,
2981
                                            null,
2982
                                            null,
2983
                                            null
2984
                                        );
2985
                                    }
2986
                                }
2987
                            }
2988
2989
                            // Replace origin course path by destination course path.
2990
                            if (strpos($content_html, $real_orig_url) !== false) {
2991
                                $url_course_path = str_replace(
2992
                                    $orig_course_info_path.'/'.$document_file,
2993
                                    '',
2994
                                    $real_orig_path
2995
                                );
2996
                                // See BT#7780
2997
                                $destination_url = $dest_course_path_rel.$document_file.$dest_url_query;
2998
                                // If the course code doesn't exist in the path? what we do? Nothing! see BT#1985
2999
                                if (strpos($real_orig_path, $origin_course_code) === false) {
3000
                                    $url_course_path = $real_orig_path;
3001
                                    $destination_url = $real_orig_path;
3002
                                }
3003
                                $content_html = str_replace($real_orig_url, $destination_url, $content_html);
3004
                            }
3005
                        }
3006
3007
                        // replace origin course code by destination course code  from origin url
3008
                        if (strpos($real_orig_url, '?') === 0) {
3009
                            $dest_url = str_replace($origin_course_code, $destination_course_code, $real_orig_url);
3010
                            $content_html = str_replace($real_orig_url, $dest_url, $content_html);
3011
                        }
3012
                    }
3013
                }
3014
            }
3015
        }
3016
3017
        return $content_html;
3018
    }
3019
3020
    /**
3021
     * Export document to PDF.
3022
     *
3023
     * @param int    $document_id
3024
     * @param string $courseCode
3025
     * @param string $orientation
3026
     * @param bool   $showHeaderAndFooter
3027
     */
3028
    public static function export_to_pdf(
3029
        $document_id,
3030
        $courseCode,
3031
        $orientation = 'landscape',
3032
        $showHeaderAndFooter = true
3033
    ) {
3034
        $course_data = api_get_course_info($courseCode);
3035
        $document_data = self::get_document_data_by_id($document_id, $courseCode);
3036
        $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$document_data['path'];
3037
3038
        $pageFormat = 'A4';
3039
        $pdfOrientation = 'P';
3040
        if ($orientation === 'landscape') {
3041
            $pageFormat = 'A4-L';
3042
            $pdfOrientation = 'L';
3043
        }
3044
        $pdf = new PDF(
3045
            $pageFormat,
3046
            $pdfOrientation,
3047
            $showHeaderAndFooter ? [] : ['top' => 0, 'left' => 0, 'bottom' => 0, 'right' => 0]
3048
        );
3049
3050
        if (api_get_configuration_value('use_alternative_document_pdf_footer')) {
3051
            $view = new Template('', false, false, false, true, false, false);
3052
            $template = $view->get_template('export/alt_pdf_footer.tpl');
3053
            $pdf->set_custom_footer(['html' => $view->fetch($template)]);
3054
        }
3055
3056
        $pdf->html_to_pdf(
3057
            $file_path,
3058
            $document_data['title'],
3059
            $courseCode,
3060
            false,
3061
            $showHeaderAndFooter
3062
        );
3063
    }
3064
3065
    /**
3066
     * Uploads a document.
3067
     *
3068
     * @param array  $files                   the $_FILES variable
3069
     * @param string $path
3070
     * @param string $title
3071
     * @param string $comment
3072
     * @param int    $unzip                   unzip or not the file
3073
     * @param string $ifExists                overwrite, rename or warn (default)
3074
     * @param bool   $index_document          index document (search xapian module)
3075
     * @param bool   $show_output             print html messages
3076
     * @param string $fileKey
3077
     * @param bool   $treat_spaces_as_hyphens
3078
     * @param int    $userId                  Optional. User ID who upload file
3079
     * @param array  $courseInfo              Optional. Course info
3080
     * @param int    $sessionId               Optional. Session ID
3081
     * @param int    $groupId                 Optional. Group ID
3082
     * @param bool   $recordAudioExercise
3083
     *
3084
     * @return array|bool
3085
     */
3086
    public static function upload_document(
3087
        $files,
3088
        $path,
3089
        $title = '',
3090
        $comment = '',
3091
        $unzip = 0,
3092
        $ifExists = '',
3093
        $index_document = false,
3094
        $show_output = false,
3095
        $fileKey = 'file',
3096
        $treat_spaces_as_hyphens = true,
3097
        $userId = 0,
3098
        array $courseInfo = [],
3099
        $sessionId = 0,
3100
        $groupId = 0,
3101
        $recordAudioExercise = false
3102
    ) {
3103
        $course_info = $courseInfo ?: api_get_course_info();
3104
        $sessionId = $sessionId ?: api_get_session_id();
3105
        $course_dir = $course_info['path'].'/document';
3106
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
3107
        $base_work_dir = $sys_course_path.$course_dir;
3108
        $userId = $userId ?: api_get_user_id();
3109
        $groupId = $groupId ?: api_get_group_id();
3110
3111
        if ($recordAudioExercise) {
3112
            $base_work_dir = $sys_course_path.$course_info['path'].'/exercises';
3113
            $path = str_replace('/../exercises/', '/', $path);
3114
        }
3115
3116
        if (isset($files[$fileKey])) {
3117
            $uploadOk = process_uploaded_file($files[$fileKey], $show_output);
3118
            if ($uploadOk) {
3119
                $new_path = handle_uploaded_document(
3120
                    $course_info,
3121
                    $files[$fileKey],
3122
                    $base_work_dir,
3123
                    $path,
3124
                    $userId,
3125
                    $groupId,
3126
                    null,
3127
                    $unzip,
3128
                    $ifExists,
3129
                    $show_output,
3130
                    false,
3131
                    null,
3132
                    $sessionId,
3133
                    $treat_spaces_as_hyphens
3134
                );
3135
3136
                // When sending zip files
3137
                if ($new_path === true && $unzip == 1) {
3138
                    return [
3139
                        'title' => $files[$fileKey]['name'],
3140
                        'url' => '#',
3141
                    ];
3142
                }
3143
3144
                if ($new_path) {
3145
                    $documentId = self::get_document_id(
3146
                        $course_info,
3147
                        $new_path,
3148
                        $sessionId
3149
                    );
3150
3151
                    if (!empty($documentId)) {
3152
                        $table_document = Database::get_course_table(TABLE_DOCUMENT);
3153
                        $params = [];
3154
3155
                        if (!empty($title)) {
3156
                            $params['title'] = $title;
3157
                        }
3158
3159
                        if (!empty($comment)) {
3160
                            $params['comment'] = trim($comment);
3161
                        }
3162
3163
                        Database::update(
3164
                            $table_document,
3165
                            $params,
3166
                            [
3167
                                'id = ? AND c_id = ? ' => [
3168
                                    $documentId,
3169
                                    $course_info['real_id'],
3170
                                ],
3171
                            ]
3172
                        );
3173
                    }
3174
3175
                    if ($index_document) {
3176
                        self::index_document(
3177
                            $documentId,
3178
                            $course_info['code'],
3179
                            null,
3180
                            $_POST['language'],
3181
                            $_REQUEST,
3182
                            $ifExists
3183
                        );
3184
                    }
3185
3186
                    if (!empty($documentId) && is_numeric($documentId)) {
3187
                        $documentData = self::get_document_data_by_id(
3188
                            $documentId,
3189
                            $course_info['code'],
3190
                            false,
3191
                            $sessionId
3192
                        );
3193
3194
                        return $documentData;
3195
                    }
3196
                }
3197
            }
3198
        }
3199
3200
        return false;
3201
    }
3202
3203
    /**
3204
     * Obtains the text inside the file with the right parser.
3205
     */
3206
    public static function get_text_content($doc_path, $doc_mime)
3207
    {
3208
        // TODO: review w$ compatibility
3209
        // Use usual exec output lines array to store stdout instead of a temp file
3210
        // because we need to store it at RAM anyway before index on ChamiloIndexer object
3211
        $ret_val = null;
3212
        switch ($doc_mime) {
3213
            case 'text/plain':
3214
                $handle = fopen($doc_path, 'r');
3215
                $output = [fread($handle, filesize($doc_path))];
3216
                fclose($handle);
3217
                break;
3218
            case 'application/pdf':
3219
                exec("pdftotext $doc_path -", $output, $ret_val);
3220
                break;
3221
            case 'application/postscript':
3222
                $temp_file = tempnam(sys_get_temp_dir(), 'chamilo');
3223
                exec("ps2pdf $doc_path $temp_file", $output, $ret_val);
3224
                if ($ret_val !== 0) { // shell fail, probably 127 (command not found)
3225
                    return false;
3226
                }
3227
                exec("pdftotext $temp_file -", $output, $ret_val);
3228
                unlink($temp_file);
3229
                break;
3230
            case 'application/msword':
3231
                exec("catdoc $doc_path", $output, $ret_val);
3232
                break;
3233
            case 'text/html':
3234
                exec("html2text $doc_path", $output, $ret_val);
3235
                break;
3236
            case 'text/rtf':
3237
                // Note: correct handling of code pages in unrtf
3238
                // on debian lenny unrtf v0.19.2 can not, but unrtf v0.20.5 can
3239
                exec("unrtf --text $doc_path", $output, $ret_val);
3240
                if ($ret_val == 127) { // command not found
3241
                    return false;
3242
                }
3243
                // Avoid index unrtf comments
3244
                if (is_array($output) && count($output) > 1) {
3245
                    $parsed_output = [];
3246
                    foreach ($output as &$line) {
3247
                        if (!preg_match('/^###/', $line, $matches)) {
3248
                            if (!empty($line)) {
3249
                                $parsed_output[] = $line;
3250
                            }
3251
                        }
3252
                    }
3253
                    $output = $parsed_output;
3254
                }
3255
                break;
3256
            case 'application/vnd.ms-powerpoint':
3257
                exec("catppt $doc_path", $output, $ret_val);
3258
                break;
3259
            case 'application/vnd.ms-excel':
3260
                exec("xls2csv -c\" \" $doc_path", $output, $ret_val);
3261
                break;
3262
        }
3263
3264
        $content = '';
3265
        if (!is_null($ret_val)) {
3266
            if ($ret_val !== 0) { // shell fail, probably 127 (command not found)
3267
                return false;
3268
            }
3269
        }
3270
        if (isset($output)) {
3271
            foreach ($output as &$line) {
3272
                $content .= $line."\n";
3273
            }
3274
3275
            return $content;
3276
        } else {
3277
            return false;
3278
        }
3279
    }
3280
3281
    /**
3282
     * Calculates the total size of all documents in a course.
3283
     *
3284
     * @author Bert vanderkimpen
3285
     *
3286
     * @param int $course_id
3287
     * @param int $group_id   (to calculate group document space)
3288
     * @param int $session_id
3289
     *
3290
     * @return int total size
3291
     */
3292
    public static function documents_total_space($course_id = null, $group_id = null, $session_id = null)
3293
    {
3294
        $TABLE_ITEMPROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
3295
        $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
3296
3297
        $session_id = (int) $session_id;
3298
        $group_id = (int) $group_id;
3299
        $course_id = (int) $course_id;
3300
3301
        if (!$course_id) {
3302
            $course_id = api_get_course_int_id();
3303
        }
3304
3305
        $group_condition = '';
3306
        if ($group_id) {
3307
            $group_condition = " AND props.to_group_id='".$group_id."' ";
3308
        }
3309
3310
        $session_condition = '';
3311
        if ($session_id) {
3312
            $session_condition = " AND props.session_id='".$session_id."' ";
3313
        }
3314
3315
        $sql = "SELECT SUM(size)
3316
                FROM (
3317
                    SELECT ref, size
3318
                    FROM $TABLE_ITEMPROPERTY AS props
3319
                    INNER JOIN $TABLE_DOCUMENT AS docs
3320
                    ON (docs.id = props.ref AND props.c_id = docs.c_id)
3321
                    WHERE
3322
                        props.c_id = $course_id AND
3323
                        docs.c_id = $course_id AND
3324
                        props.tool = '".TOOL_DOCUMENT."' AND
3325
                        props.ref not in (
3326
                            SELECT ref
3327
                            FROM $TABLE_ITEMPROPERTY as cip
3328
                            WHERE
3329
                                cip.c_id = $course_id AND
3330
                                cip.tool = '".TOOL_DOCUMENT."' AND
3331
                                cip.visibility = 2
3332
                        )
3333
                        $group_condition
3334
                        $session_condition
3335
                    GROUP BY props.ref
3336
                    ) AS table1
3337
                ";
3338
        $result = Database::query($sql);
3339
3340
        if ($result && Database::num_rows($result) != 0) {
3341
            $row = Database::fetch_row($result);
3342
3343
            return (int) $row[0];
3344
        } else {
3345
            return 0;
3346
        }
3347
    }
3348
3349
    /**
3350
     * Display the document quota in a simple way.
3351
     *
3352
     *  Here we count 1 Kilobyte = 1024 Bytes, 1 Megabyte = 1048576 Bytes
3353
     */
3354
    public static function displaySimpleQuota($course_quota, $already_consumed_space)
3355
    {
3356
        $course_quota_m = round($course_quota / 1048576);
3357
        $already_consumed_space_m = round($already_consumed_space / 1048576, 2);
3358
        $percentage = $already_consumed_space / $course_quota * 100;
3359
        $percentage = round($percentage, 1);
3360
        $message = get_lang('YouAreCurrentlyUsingXOfYourX');
3361
        $message = sprintf($message, $already_consumed_space_m, $percentage.'%', $course_quota_m.' ');
3362
3363
        return Display::div($message, ['id' => 'document_quota']);
3364
    }
3365
3366
    /**
3367
     * Checks if there is enough place to add a file on a directory
3368
     * on the base of a maximum directory size allowed.
3369
     *
3370
     * @author Bert Vanderkimpen
3371
     *
3372
     * @param int $file_size     size of the file in byte
3373
     * @param int $max_dir_space maximum size
3374
     *
3375
     * @return bool true if there is enough space, false otherwise
3376
     *
3377
     * @see enough_space() uses  documents_total_space() function
3378
     */
3379
    public static function enough_space($file_size, $max_dir_space)
3380
    {
3381
        if ($max_dir_space) {
3382
            $already_filled_space = self::documents_total_space();
3383
            if (($file_size + $already_filled_space) > $max_dir_space) {
3384
                return false;
3385
            }
3386
        }
3387
3388
        return true;
3389
    }
3390
3391
    /**
3392
     * @param array $params count, url, extension
3393
     *
3394
     * @return string
3395
     */
3396
    public static function generateAudioJavascript($params = [])
3397
    {
3398
        return '
3399
            $(\'audio.audio_preview\').mediaelementplayer({
3400
                features: [\'playpause\'],
3401
                audioWidth: 30,
3402
                audioHeight: 30,
3403
                success: function(mediaElement, originalNode, instance) {
3404
                }
3405
            });';
3406
    }
3407
3408
    /**
3409
     * Shows a play icon next to the document title in the document list.
3410
     *
3411
     * @param string $documentWebPath
3412
     * @param array  $documentInfo
3413
     *
3414
     * @return string
3415
     */
3416
    public static function generateAudioPreview($documentWebPath, $documentInfo)
3417
    {
3418
        $filePath = $documentWebPath.$documentInfo['path'];
3419
        $extension = $documentInfo['file_extension'];
3420
3421
        if (!in_array($extension, ['mp3', 'wav', 'ogg'])) {
3422
            return '';
3423
        }
3424
3425
        return '<span class="preview"> <audio class="audio_preview skip" src="'.$filePath.'" type="audio/'.$extension.'" > </audio></span>';
3426
    }
3427
3428
    /**
3429
     * @param string $file
3430
     * @param string $extension
3431
     *
3432
     * @return string
3433
     */
3434
    public static function generateMediaPreview($file, $extension)
3435
    {
3436
        $id = api_get_unique_id();
3437
        switch ($extension) {
3438
            case 'wav':
3439
            case 'ogg':
3440
            case 'mp3':
3441
                $html = '<div style="margin: 0; position: absolute; top: 50%; left: 35%;">';
3442
                $html .= '<audio id="'.$id.'" controls="controls" src="'.$file.'" type="audio/mp3" ></audio></div>';
3443
                break;
3444
            default:
3445
                $html = '<video id="'.$id.'" width="100%" height="100%" controls>';
3446
                $html .= '<source src="'.$file.'" >';
3447
                $html .= '</video>';
3448
                break;
3449
        }
3450
3451
        return $html;
3452
    }
3453
3454
    /**
3455
     * @param array  $course_info
3456
     * @param bool   $lp_id
3457
     * @param string $target
3458
     * @param int    $session_id
3459
     * @param bool   $add_move_button
3460
     * @param string $filter_by_folder
3461
     * @param string $overwrite_url
3462
     * @param bool   $showInvisibleFiles
3463
     * @param bool   $showOnlyFolders
3464
     * @param int    $folderId
3465
     * @param bool   $addCloseButton
3466
     * @param bool   $addAudioPreview
3467
     * @param array  $filterByExtension
3468
     *
3469
     * @return string
3470
     */
3471
    public static function get_document_preview(
3472
        $course_info,
3473
        $lp_id = false,
3474
        $target = '',
3475
        $session_id = 0,
3476
        $add_move_button = false,
3477
        $filter_by_folder = null,
3478
        $overwrite_url = '',
3479
        $showInvisibleFiles = false,
3480
        $showOnlyFolders = false,
3481
        $folderId = false,
3482
        $addCloseButton = true,
3483
        $addAudioPreview = false,
3484
        $filterByExtension = []
3485
    ) {
3486
        if (empty($course_info['real_id']) || empty($course_info['code']) || !is_array($course_info)) {
3487
            return '';
3488
        }
3489
3490
        $user_id = api_get_user_id();
3491
        $userInfo = api_get_user_info();
3492
        $user_in_course = api_is_platform_admin();
3493
        if (!$user_in_course) {
3494
            if (CourseManager::is_course_teacher($user_id, $course_info['code'])) {
3495
                $user_in_course = true;
3496
            }
3497
        }
3498
3499
        // Condition for the session
3500
        $session_id = (int) $session_id;
3501
3502
        if (!$user_in_course) {
3503
            if (empty($session_id)) {
3504
                if (CourseManager::is_user_subscribed_in_course($user_id, $course_info['code'])) {
3505
                    $user_in_course = true;
3506
                }
3507
                // Check if course is open then we can consider that the student is registered to the course
3508
                if (isset($course_info) && in_array($course_info['visibility'], [2, 3])) {
3509
                    $user_in_course = true;
3510
                }
3511
            } else {
3512
                $user_status = SessionManager::get_user_status_in_course_session(
3513
                    $user_id,
3514
                    $course_info['real_id'],
3515
                    $session_id
3516
                );
3517
                //is true if is an student, course session teacher or coach
3518
                if (in_array($user_status, ['0', '2', '6'])) {
3519
                    $user_in_course = true;
3520
                }
3521
            }
3522
        }
3523
3524
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
3525
        $tbl_item_prop = Database::get_course_table(TABLE_ITEM_PROPERTY);
3526
        $condition_session = " AND (last.session_id = '$session_id' OR last.session_id = '0' OR last.session_id IS NULL)";
3527
3528
        $add_folder_filter = null;
3529
        if (!empty($filter_by_folder)) {
3530
            $add_folder_filter = " AND docs.path LIKE '".Database::escape_string($filter_by_folder)."%'";
3531
        }
3532
3533
        // If we are in LP display hidden folder https://support.chamilo.org/issues/6679
3534
        $lp_visibility_condition = null;
3535
        if ($lp_id || $showOnlyFolders) {
3536
            if ($showInvisibleFiles || $showOnlyFolders) {
3537
                $lp_visibility_condition .= ' OR last.visibility = 0';
3538
            }
3539
        }
3540
3541
        $folderCondition = " AND docs.path LIKE '/%' ";
3542
        if (!api_is_allowed_to_edit()) {
3543
            $protectedFolders = self::getProtectedFolderFromStudent();
3544
            foreach ($protectedFolders as $folder) {
3545
                $folderCondition .= " AND docs.path NOT LIKE '$folder' ";
3546
            }
3547
        }
3548
3549
        $extensionConditionToString = '';
3550
        if (!empty($filterByExtension)) {
3551
            $extensionCondition = [];
3552
            foreach ($filterByExtension as $extension) {
3553
                $extensionCondition[] = " docs.path LIKE '%.$extension' ";
3554
            }
3555
            if (!empty($extensionCondition)) {
3556
                $extensionConditionToString .= " AND (".implode('OR', $extensionCondition).") ";
3557
            }
3558
        }
3559
3560
        $parentData = [];
3561
        if ($folderId !== false) {
3562
            $parentData = self::get_document_data_by_id(
3563
                $folderId,
3564
                $course_info['code'],
3565
                false,
3566
                $session_id
3567
            );
3568
            if (!empty($parentData)) {
3569
                $cleanedPath = $parentData['path'];
3570
                $num = substr_count($cleanedPath, '/');
3571
3572
                $notLikeCondition = '';
3573
                for ($i = 1; $i <= $num; $i++) {
3574
                    $repeat = str_repeat('/%', $i + 1);
3575
                    $notLikeCondition .= " AND docs.path NOT LIKE '".Database::escape_string($cleanedPath.$repeat)."' ";
3576
                }
3577
3578
                $folderId = (int) $folderId;
3579
                $folderCondition = " AND
3580
                    docs.id <> $folderId AND
3581
                    docs.path LIKE '".$cleanedPath."/%'
3582
                    $notLikeCondition
3583
                ";
3584
            } else {
3585
                $folderCondition = " AND docs.filetype = 'file' ";
3586
            }
3587
        }
3588
3589
        $levelCondition = '';
3590
        if ($folderId === false) {
3591
            $levelCondition = " AND docs.path NOT LIKE'/%/%'";
3592
        }
3593
3594
        $sql = "SELECT DISTINCT last.visibility, docs.*
3595
                FROM $tbl_item_prop AS last
3596
                INNER JOIN $tbl_doc AS docs
3597
                ON (docs.id = last.ref AND docs.c_id = last.c_id)
3598
                WHERE
3599
                    docs.path NOT LIKE '%_DELETED_%' AND
3600
                    last.tool = '".TOOL_DOCUMENT."' $condition_session AND
3601
                    (last.visibility = '1' $lp_visibility_condition) AND
3602
                    last.visibility <> 2 AND
3603
                    docs.c_id = {$course_info['real_id']} AND
3604
                    last.c_id = {$course_info['real_id']}
3605
                    $folderCondition
3606
                    $levelCondition
3607
                    $extensionConditionToString
3608
                    $add_folder_filter
3609
                ORDER BY docs.filetype DESC, docs.title ASC";
3610
3611
        $res_doc = Database::query($sql);
3612
        $resources = Database::store_result($res_doc, 'ASSOC');
3613
3614
        $return = '';
3615
        if ($lp_id == false && $addCloseButton) {
3616
            if ($folderId === false) {
3617
                $return .= Display::div(
3618
                    Display::url(
3619
                        Display::return_icon('close.png', get_lang('Close'), [], ICON_SIZE_SMALL),
3620
                        ' javascript:void(0);',
3621
                        ['id' => 'close_div_'.$course_info['real_id'].'_'.$session_id, 'class' => 'close_div']
3622
                    ),
3623
                    ['style' => 'position:absolute;right:10px']
3624
                );
3625
            }
3626
        }
3627
3628
        // If you want to debug it, I advise you to do "echo" on the eval statements.
3629
        $newResources = [];
3630
        $added = [];
3631
        if (!empty($resources) && $user_in_course) {
3632
            foreach ($resources as $resource) {
3633
                $docId = $resource['id'];
3634
                if (in_array($docId, $added)) {
3635
                    continue;
3636
                }
3637
3638
                $is_visible = self::is_visible_by_id(
3639
                    $docId,
3640
                    $course_info,
3641
                    $session_id,
3642
                    api_get_user_id()
3643
                );
3644
3645
                if ($showOnlyFolders) {
3646
                    $isFolder = ('folder' === $resource['filetype']);
3647
                    $visibility = (int) $resource['visibility'];
3648
                    if (!$isFolder && 0 == $visibility) {
3649
                        continue;
3650
                    }
3651
                }
3652
3653
                if ($showInvisibleFiles === false) {
3654
                    if (!$is_visible) {
3655
                        continue;
3656
                    }
3657
                }
3658
                $added[] = $docId;
3659
                $newResources[] = $resource;
3660
            }
3661
        }
3662
3663
        $label = get_lang('Documents');
3664
3665
        $documents = [];
3666
        if ($folderId === false) {
3667
            $documents[$label] = [
3668
                'id' => 0,
3669
                'files' => $newResources,
3670
            ];
3671
        } else {
3672
            if (is_array($parentData)) {
3673
                $documents[$parentData['title']] = [
3674
                    'id' => (int) $folderId,
3675
                    'files' => $newResources,
3676
                ];
3677
            }
3678
        }
3679
3680
        $writeResult = self::write_resources_tree(
3681
            $userInfo,
3682
            $course_info,
3683
            $session_id,
3684
            $documents,
3685
            $lp_id,
3686
            $target,
3687
            $add_move_button,
3688
            $overwrite_url,
3689
            $folderId,
3690
            $addAudioPreview
3691
        );
3692
3693
        $return .= $writeResult;
3694
        $lpAjaxUrl = api_get_path(WEB_AJAX_PATH).'lp.ajax.php';
3695
        $extraAjaxParams = ($showOnlyFolders ? '&showOnlyFolders='.(int) $showOnlyFolders : '');
3696
        if ($lp_id === false) {
3697
            $url = $lpAjaxUrl.'?a=get_documents&lp_id=&cidReq='.$course_info['code'].$extraAjaxParams;
3698
            $return .= "<script>
3699
            $(function() {
3700
                $('.close_div').click(function() {
3701
                    var course_id = this.id.split('_')[2];
3702
                    var session_id = this.id.split('_')[3];
3703
                    $('#document_result_'+course_id+'_'+session_id).hide();
3704
                    $('.lp_resource').remove();
3705
                    $('.document_preview_container').html('');
3706
                });
3707
            });
3708
            </script>";
3709
        } else {
3710
            // For LPs
3711
            $url = $lpAjaxUrl.'?a=get_documents&lp_id='.(int) $lp_id.'&'.api_get_cidreq().$extraAjaxParams;
3712
        }
3713
3714
        if (!empty($overwrite_url)) {
3715
            $url .= '&url='.urlencode(Security::remove_XSS($overwrite_url));
3716
        }
3717
3718
        if ($add_move_button) {
3719
            $url .= '&add_move_button=1';
3720
        }
3721
3722
        $return .= "<script>
3723
            function testResources(id, img) {
3724
                var numericId = id.split('_')[1];
3725
                var parentId = 'doc_id_'+numericId;
3726
                var tempId = 'temp_'+numericId;
3727
                var image = $('#'+img);
3728
3729
                if (image.hasClass('open')) {
3730
                    image.removeClass('open');
3731
                    image.attr('src', '".Display::returnIconPath('nolines_plus.gif')."');
3732
                    $('#'+id).show();
3733
                    $('#'+tempId).hide();
3734
                } else {
3735
                    image.addClass('open');
3736
                    image.attr('src', '".Display::returnIconPath('nolines_minus.gif')."');
3737
                    $('#'+id).hide();
3738
                    $('#'+tempId).show();
3739
                    var tempDiv = $('#'+parentId).find('#'+tempId);
3740
                    if (tempDiv.length == 0) {
3741
                        $.ajax({
3742
                            type: 'GET',
3743
                            url:  '".$url."',
3744
                            data: 'folder_id='+numericId,
3745
                            success: function(data) {
3746
                                tempDiv = $('#doc_id_'+numericId).append('<div id='+tempId+'>'+data+'</div>');
3747
                            }
3748
                        });
3749
                    }
3750
                }
3751
            }
3752
            </script>";
3753
3754
        if (!$user_in_course) {
3755
            $return = '';
3756
        }
3757
3758
        return $return;
3759
    }
3760
3761
    /**
3762
     * Generate and return an HTML list of resources based on a given array.
3763
     * This list is used to show the course creator a list of available resources to choose from
3764
     * when creating a learning path.
3765
     *
3766
     * @param array  $userInfo        current user info
3767
     * @param array  $course_info
3768
     * @param int    $session_id
3769
     * @param array  $documents
3770
     * @param bool   $lp_id
3771
     * @param string $target
3772
     * @param bool   $add_move_button
3773
     * @param string $overwrite_url
3774
     * @param int    $folderId
3775
     * @param bool   $addAudioPreview
3776
     *
3777
     * @return string
3778
     */
3779
    public static function write_resources_tree(
3780
        $userInfo,
3781
        $course_info,
3782
        $session_id,
3783
        $documents,
3784
        $lp_id = false,
3785
        $target = '',
3786
        $add_move_button = false,
3787
        $overwrite_url = '',
3788
        $folderId = false,
3789
        $addAudioPreview = false
3790
    ) {
3791
        $return = '';
3792
        if (!empty($documents)) {
3793
            foreach ($documents as $key => $resource) {
3794
                if (isset($resource['id']) && isset($resource['files'])) {
3795
                    $mainFolderResource = [
3796
                        'id' => $resource['id'],
3797
                        'title' => $key,
3798
                    ];
3799
                    $close = false;
3800
3801
                    if ($folderId === false) {
3802
                        $parsedFolder = self::parseFolder($folderId, $mainFolderResource, $lp_id);
3803
                        $close = (bool) $parsedFolder;
3804
3805
                        $return .= $parsedFolder;
3806
                    }
3807
3808
                    if (isset($resource['files'])) {
3809
                        $return .= self::write_resources_tree(
3810
                            $userInfo,
3811
                            $course_info,
3812
                            $session_id,
3813
                            $resource['files'],
3814
                            $lp_id,
3815
                            $target,
3816
                            $add_move_button,
3817
                            $overwrite_url,
3818
                            false,
3819
                            $addAudioPreview
3820
                        );
3821
                    }
3822
3823
                    if ($close) {
3824
                        $return .= '</div>';
3825
                        $return .= '</ul>';
3826
                    }
3827
                } else {
3828
                    if ($resource['filetype'] === 'folder') {
3829
                        $return .= self::parseFolder($folderId, $resource, $lp_id);
3830
                    } else {
3831
                        $return .= self::parseFile(
3832
                            $userInfo,
3833
                            $course_info,
3834
                            $session_id,
3835
                            $resource,
3836
                            $lp_id,
3837
                            $add_move_button,
3838
                            $target,
3839
                            $overwrite_url,
3840
                            $addAudioPreview
3841
                        );
3842
                    }
3843
                }
3844
            }
3845
        }
3846
3847
        return $return;
3848
    }
3849
3850
    /**
3851
     * @param int   $doc_id
3852
     * @param array $courseInfo
3853
     * @param int   $sessionId
3854
     * @param int   $user_id
3855
     * @param int   $groupId               iid
3856
     * @param bool  $checkParentVisibility
3857
     *
3858
     * @return bool
3859
     */
3860
    public static function check_visibility_tree(
3861
        $doc_id,
3862
        $courseInfo,
3863
        $sessionId,
3864
        $user_id,
3865
        $groupId = 0,
3866
        $checkParentVisibility = true
3867
    ) {
3868
        if (empty($courseInfo)) {
3869
            return false;
3870
        }
3871
3872
        $courseCode = $courseInfo['code'];
3873
3874
        if (empty($courseCode)) {
3875
            return false;
3876
        }
3877
3878
        $document_data = self::get_document_data_by_id(
3879
            $doc_id,
3880
            $courseCode,
3881
            null,
3882
            $sessionId
3883
        );
3884
3885
        if ($sessionId != 0 && !$document_data) {
3886
            $document_data = self::get_document_data_by_id(
3887
                $doc_id,
3888
                $courseCode,
3889
                null,
3890
                0
3891
            );
3892
        }
3893
3894
        if (!empty($document_data)) {
3895
            // If admin or course teacher, allow anyway
3896
            if (api_is_platform_admin() || CourseManager::is_course_teacher($user_id, $courseCode)) {
3897
                return true;
3898
            }
3899
            if ($document_data['parent_id'] == false || empty($document_data['parent_id'])) {
3900
                if (!empty($groupId)) {
3901
                    return true;
3902
                }
3903
                $visible = self::is_visible_by_id($doc_id, $courseInfo, $sessionId, $user_id);
3904
3905
                return $visible;
3906
            } else {
3907
                $visible = self::is_visible_by_id($doc_id, $courseInfo, $sessionId, $user_id);
3908
3909
                if (!$visible) {
3910
                    return false;
3911
                } else {
3912
                    if ($checkParentVisibility && $doc_id != $document_data['parent_id']) {
3913
                        return self::check_visibility_tree(
3914
                            $document_data['parent_id'],
3915
                            $courseInfo,
3916
                            $sessionId,
3917
                            $user_id,
3918
                            $groupId
3919
                        );
3920
                    }
3921
3922
                    return true;
3923
                }
3924
            }
3925
        } else {
3926
            return false;
3927
        }
3928
    }
3929
3930
    /**
3931
     * Index a given document.
3932
     *
3933
     * @param   int     Document ID inside its corresponding course
3934
     * @param   string  Course code
3935
     * @param   int     Session ID (not used yet)
3936
     * @param   string  Language of document's content (defaults to course language)
3937
     * @param   array   Array of specific fields (['code'=>'value',...])
3938
     * @param   string  What to do if the file already exists (default or overwrite)
3939
     * @param   bool    When set to true, this runs the indexer without actually saving anything to any database
3940
     *
3941
     * @return bool Returns true on presumed success, false on failure
3942
     */
3943
    public static function index_document(
3944
        $docid,
3945
        $course_code,
3946
        $session_id = 0,
3947
        $lang = 'english',
3948
        $specific_fields_values = [],
3949
        $if_exists = '',
3950
        $simulation = false
3951
    ) {
3952
        if (api_get_setting('search_enabled') !== 'true') {
3953
            return false;
3954
        }
3955
        if (empty($docid) or $docid != intval($docid)) {
3956
            return false;
3957
        }
3958
        if (empty($session_id)) {
3959
            $session_id = api_get_session_id();
3960
        }
3961
        $course_info = api_get_course_info($course_code);
3962
        $course_dir = $course_info['path'].'/document';
3963
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
3964
        $base_work_dir = $sys_course_path.$course_dir;
3965
3966
        $course_id = $course_info['real_id'];
3967
        $table_document = Database::get_course_table(TABLE_DOCUMENT);
3968
3969
        $qry = "SELECT path, title FROM $table_document WHERE c_id = $course_id AND id = '$docid' LIMIT 1";
3970
        $result = Database::query($qry);
3971
        if (Database::num_rows($result) == 1) {
3972
            $row = Database::fetch_array($result);
3973
            $doc_path = api_get_path(SYS_COURSE_PATH).$course_dir.$row['path'];
3974
            //TODO: mime_content_type is deprecated, fileinfo php extension is enabled by default as of PHP 5.3.0
3975
            // now versions of PHP on Debian testing(5.2.6-5) and Ubuntu(5.2.6-2ubuntu) are lower, so wait for a while
3976
            $doc_mime = mime_content_type($doc_path);
3977
            $allowed_mime_types = self::file_get_mime_type(true);
3978
3979
            // mime_content_type does not detect correctly some formats that
3980
            // are going to be supported for index, so an extensions array is used for the moment
3981
            if (empty($doc_mime)) {
3982
                $allowed_extensions = [
3983
                    'doc',
3984
                    'docx',
3985
                    'ppt',
3986
                    'pptx',
3987
                    'pps',
3988
                    'ppsx',
3989
                    'xls',
3990
                    'xlsx',
3991
                    'odt',
3992
                    'odp',
3993
                    'ods',
3994
                    'pdf',
3995
                    'txt',
3996
                    'rtf',
3997
                    'msg',
3998
                    'csv',
3999
                    'html',
4000
                    'htm',
4001
                ];
4002
                $extensions = preg_split("/[\/\\.]/", $doc_path);
4003
                $doc_ext = strtolower($extensions[count($extensions) - 1]);
4004
                if (in_array($doc_ext, $allowed_extensions)) {
4005
                    switch ($doc_ext) {
4006
                        case 'ppt':
4007
                        case 'pps':
4008
                            $doc_mime = 'application/vnd.ms-powerpoint';
4009
                            break;
4010
                        case 'xls':
4011
                            $doc_mime = 'application/vnd.ms-excel';
4012
                            break;
4013
                    }
4014
                }
4015
            }
4016
4017
            //@todo move this nightmare in a search controller or something like that!!! J.M
4018
4019
            if (in_array($doc_mime, $allowed_mime_types)) {
4020
                $file_title = $row['title'];
4021
                $file_content = self::get_text_content($doc_path, $doc_mime);
4022
                $course_code = Database::escape_string($course_code);
4023
                $ic_slide = new IndexableChunk();
4024
                $ic_slide->addValue('title', $file_title);
4025
                $ic_slide->addCourseId($course_code);
4026
                $ic_slide->addToolId(TOOL_DOCUMENT);
4027
                $xapian_data = [
4028
                    SE_COURSE_ID => $course_code,
4029
                    SE_TOOL_ID => TOOL_DOCUMENT,
4030
                    SE_DATA => ['doc_id' => $docid],
4031
                    SE_USER => api_get_user_id(),
4032
                ];
4033
4034
                $ic_slide->xapian_data = serialize($xapian_data);
4035
                $di = new ChamiloIndexer();
4036
                $return = $di->connectDb(null, null, $lang);
4037
4038
                require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
4039
                $specific_fields = get_specific_field_list();
4040
4041
                // process different depending on what to do if file exists
4042
                /**
4043
                 * @TODO Find a way to really verify if the file had been
4044
                 * overwriten. Now all work is done at
4045
                 * handle_uploaded_document() and it's difficult to verify it
4046
                 */
4047
                if (!empty($if_exists) && $if_exists == 'overwrite') {
4048
                    // Overwrite the file on search engine
4049
                    // Actually, it consists on a delete of terms from db,
4050
                    // insert new ones, create a new search engine document,
4051
                    // and remove the old one
4052
                    // Get search_did
4053
                    $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
4054
                    $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1';
4055
                    $sql = sprintf($sql, $tbl_se_ref, $course_code, TOOL_DOCUMENT, $docid);
4056
4057
                    $res = Database::query($sql);
4058
4059
                    if (Database::num_rows($res) > 0) {
4060
                        $se_ref = Database::fetch_array($res);
4061
                        if (!$simulation) {
4062
                            $di->remove_document($se_ref['search_did']);
4063
                        }
4064
                        $all_specific_terms = '';
4065
                        foreach ($specific_fields as $specific_field) {
4066
                            if (!$simulation) {
4067
                                delete_all_specific_field_value($course_code, $specific_field['id'], TOOL_DOCUMENT, $docid);
4068
                            }
4069
                            // Update search engine
4070
                            if (isset($specific_fields_values[$specific_field['code']])) {
4071
                                $sterms = trim($specific_fields_values[$specific_field['code']]);
4072
                            } else { //if the specific field is not defined, force an empty one
4073
                                $sterms = '';
4074
                            }
4075
                            $all_specific_terms .= ' '.$sterms;
4076
                            $sterms = explode(',', $sterms);
4077
                            foreach ($sterms as $sterm) {
4078
                                $sterm = trim($sterm);
4079
                                if (!empty($sterm)) {
4080
                                    $ic_slide->addTerm($sterm, $specific_field['code']);
4081
                                    // updated the last param here from $value to $sterm without being sure - see commit15464
4082
                                    if (!$simulation) {
4083
                                        add_specific_field_value(
4084
                                            $specific_field['id'],
4085
                                            $course_code,
4086
                                            TOOL_DOCUMENT,
4087
                                            $docid,
4088
                                            $sterm
4089
                                        );
4090
                                    }
4091
                                }
4092
                            }
4093
                        }
4094
                        // Add terms also to content to make terms findable by probabilistic search
4095
                        $file_content = $all_specific_terms.' '.$file_content;
4096
4097
                        if (!$simulation) {
4098
                            $ic_slide->addValue('content', $file_content);
4099
                            $di->addChunk($ic_slide);
4100
                            // Index and return a new search engine document id
4101
                            $did = $di->index();
4102
4103
                            if ($did) {
4104
                                // update the search_did on db
4105
                                $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
4106
                                $sql = 'UPDATE %s SET search_did=%d WHERE id=%d LIMIT 1';
4107
                                $sql = sprintf($sql, $tbl_se_ref, (int) $did, (int) $se_ref['id']);
4108
                                Database::query($sql);
4109
                            }
4110
                        }
4111
                    }
4112
                } else {
4113
                    // Add all terms
4114
                    $all_specific_terms = '';
4115
                    foreach ($specific_fields as $specific_field) {
4116
                        if (isset($specific_fields_values[$specific_field['code']])) {
4117
                            $sterms = trim($specific_fields_values[$specific_field['code']]);
4118
                        } else { //if the specific field is not defined, force an empty one
4119
                            $sterms = '';
4120
                        }
4121
                        $all_specific_terms .= ' '.$sterms;
4122
                        if (!empty($sterms)) {
4123
                            $sterms = explode(',', $sterms);
4124
                            foreach ($sterms as $sterm) {
4125
                                if (!$simulation) {
4126
                                    $ic_slide->addTerm(trim($sterm), $specific_field['code']);
4127
                                    add_specific_field_value(
4128
                                        $specific_field['id'],
4129
                                        $course_code,
4130
                                        TOOL_DOCUMENT,
4131
                                        $docid,
4132
                                        $sterm
4133
                                    );
4134
                                }
4135
                            }
4136
                        }
4137
                    }
4138
                    // Add terms also to content to make terms findable by probabilistic search
4139
                    $file_content = $all_specific_terms.' '.$file_content;
4140
                    if (!$simulation) {
4141
                        $ic_slide->addValue('content', $file_content);
4142
                        $di->addChunk($ic_slide);
4143
                        // Index and return search engine document id
4144
                        $did = $di->index();
4145
                        if ($did) {
4146
                            // Save it to db
4147
                            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
4148
                            $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did)
4149
                            VALUES (NULL , \'%s\', \'%s\', %s, %s)';
4150
                            $sql = sprintf($sql, $tbl_se_ref, $course_code, TOOL_DOCUMENT, $docid, $did);
4151
                            Database::query($sql);
4152
                        } else {
4153
                            return false;
4154
                        }
4155
                    }
4156
                }
4157
            } else {
4158
                return false;
4159
            }
4160
        }
4161
4162
        return true;
4163
    }
4164
4165
    /**
4166
     * @return array
4167
     */
4168
    public static function get_web_odf_extension_list()
4169
    {
4170
        return ['ods', 'odt', 'odp'];
4171
    }
4172
4173
    /**
4174
     * Set of extension allowed to use Jodconverter.
4175
     *
4176
     * @param $mode 'from'
0 ignored issues
show
Documentation Bug introduced by
The doc comment 'from' at position 0 could not be parsed: Unknown type name ''from'' at position 0 in 'from'.
Loading history...
4177
     *              'to'
4178
     *              'all'
4179
     * @param $format   'text'
4180
     *                  'spreadsheet'
4181
     *                  'presentation'
4182
     *                  'drawing'
4183
     *                  'all'
4184
     *
4185
     * @return array
4186
     */
4187
    public static function getJodconverterExtensionList($mode, $format)
4188
    {
4189
        $extensionList = [];
4190
        $extensionListFromText = [
4191
            'odt',
4192
            'sxw',
4193
            'rtf',
4194
            'doc',
4195
            'docx',
4196
            'wpd',
4197
            'txt',
4198
        ];
4199
        $extensionListToText = [
4200
            'pdf',
4201
            'odt',
4202
            'sxw',
4203
            'rtf',
4204
            'doc',
4205
            'docx',
4206
            'txt',
4207
        ];
4208
        $extensionListFromSpreadsheet = [
4209
            'ods',
4210
            'sxc',
4211
            'xls',
4212
            'xlsx',
4213
            'csv',
4214
            'tsv',
4215
        ];
4216
        $extensionListToSpreadsheet = [
4217
            'pdf',
4218
            'ods',
4219
            'sxc',
4220
            'xls',
4221
            'xlsx',
4222
            'csv',
4223
            'tsv',
4224
        ];
4225
        $extensionListFromPresentation = [
4226
            'odp',
4227
            'sxi',
4228
            'ppt',
4229
            'pptx',
4230
        ];
4231
        $extensionListToPresentation = [
4232
            'pdf',
4233
            'swf',
4234
            'odp',
4235
            'sxi',
4236
            'ppt',
4237
            'pptx',
4238
        ];
4239
        $extensionListFromDrawing = ['odg'];
4240
        $extensionListToDrawing = ['svg', 'swf'];
4241
4242
        if ($mode === 'from') {
4243
            if ($format === 'text') {
4244
                $extensionList = array_merge($extensionList, $extensionListFromText);
4245
            } elseif ($format === 'spreadsheet') {
4246
                $extensionList = array_merge($extensionList, $extensionListFromSpreadsheet);
4247
            } elseif ($format === 'presentation') {
4248
                $extensionList = array_merge($extensionList, $extensionListFromPresentation);
4249
            } elseif ($format === 'drawing') {
4250
                $extensionList = array_merge($extensionList, $extensionListFromDrawing);
4251
            } elseif ($format === 'all') {
4252
                $extensionList = array_merge($extensionList, $extensionListFromText);
4253
                $extensionList = array_merge($extensionList, $extensionListFromSpreadsheet);
4254
                $extensionList = array_merge($extensionList, $extensionListFromPresentation);
4255
                $extensionList = array_merge($extensionList, $extensionListFromDrawing);
4256
            }
4257
        } elseif ($mode === 'to') {
4258
            if ($format === 'text') {
4259
                $extensionList = array_merge($extensionList, $extensionListToText);
4260
            } elseif ($format === 'spreadsheet') {
4261
                $extensionList = array_merge($extensionList, $extensionListToSpreadsheet);
4262
            } elseif ($format === 'presentation') {
4263
                $extensionList = array_merge($extensionList, $extensionListToPresentation);
4264
            } elseif ($format === 'drawing') {
4265
                $extensionList = array_merge($extensionList, $extensionListToDrawing);
4266
            } elseif ($format === 'all') {
4267
                $extensionList = array_merge($extensionList, $extensionListToText);
4268
                $extensionList = array_merge($extensionList, $extensionListToSpreadsheet);
4269
                $extensionList = array_merge($extensionList, $extensionListToPresentation);
4270
                $extensionList = array_merge($extensionList, $extensionListToDrawing);
4271
            }
4272
        } elseif ($mode === 'all') {
4273
            if ($format === 'text') {
4274
                $extensionList = array_merge($extensionList, $extensionListFromText);
4275
                $extensionList = array_merge($extensionList, $extensionListToText);
4276
            } elseif ($format === 'spreadsheet') {
4277
                $extensionList = array_merge($extensionList, $extensionListFromSpreadsheet);
4278
                $extensionList = array_merge($extensionList, $extensionListToSpreadsheet);
4279
            } elseif ($format === 'presentation') {
4280
                $extensionList = array_merge($extensionList, $extensionListFromPresentation);
4281
                $extensionList = array_merge($extensionList, $extensionListToPresentation);
4282
            } elseif ($format === 'drawing') {
4283
                $extensionList = array_merge($extensionList, $extensionListFromDrawing);
4284
                $extensionList = array_merge($extensionList, $extensionListToDrawing);
4285
            } elseif ($format === 'all') {
4286
                $extensionList = array_merge($extensionList, $extensionListFromText);
4287
                $extensionList = array_merge($extensionList, $extensionListToText);
4288
                $extensionList = array_merge($extensionList, $extensionListFromSpreadsheet);
4289
                $extensionList = array_merge($extensionList, $extensionListToSpreadsheet);
4290
                $extensionList = array_merge($extensionList, $extensionListFromPresentation);
4291
                $extensionList = array_merge($extensionList, $extensionListToPresentation);
4292
                $extensionList = array_merge($extensionList, $extensionListFromDrawing);
4293
                $extensionList = array_merge($extensionList, $extensionListToDrawing);
4294
            }
4295
        }
4296
4297
        return $extensionList;
4298
    }
4299
4300
    /**
4301
     * Get Format type list by extension and mode.
4302
     *
4303
     * @param string $mode Mode to search format type list
4304
     *
4305
     * @example 'from'
4306
     * @example 'to'
4307
     *
4308
     * @param string $extension file extension to check file type
4309
     *
4310
     * @return array
4311
     */
4312
    public static function getFormatTypeListConvertor($mode, $extension)
4313
    {
4314
        $formatTypesList = [];
4315
        $formatTypes = ['text', 'spreadsheet', 'presentation', 'drawing'];
4316
        foreach ($formatTypes as $formatType) {
4317
            if (in_array($extension, self::getJodconverterExtensionList($mode, $formatType))) {
4318
                $formatTypesList[] = $formatType;
4319
            }
4320
        }
4321
4322
        return $formatTypesList;
4323
    }
4324
4325
    /**
4326
     * @param string $path
4327
     * @param bool   $is_certificate_mode
4328
     *
4329
     * @return bool
4330
     */
4331
    public static function is_folder_to_avoid($path, $is_certificate_mode = false)
4332
    {
4333
        if (basename($path) == 'css') {
4334
            return true;
4335
        }
4336
4337
        if ($is_certificate_mode == false) {
4338
            //Certificate results
4339
            if (strstr($path, 'certificates')) {
4340
                return true;
4341
            }
4342
        }
4343
4344
        $foldersToAvoid = [
4345
            '/HotPotatoes_files',
4346
            '/certificates',
4347
        ];
4348
        $showSystemFolder = api_get_course_setting('show_system_folders');
4349
4350
        // Admin setting for Hide/Show Default folders to all users
4351
        if (api_get_setting('show_default_folders') == 'false'
4352
            || 2 == $showSystemFolder
4353
        ) {
4354
            $foldersToAvoid[] = '/images';
4355
            $foldersToAvoid[] = '/flash';
4356
            $foldersToAvoid[] = '/audio';
4357
            $foldersToAvoid[] = '/video';
4358
            $foldersToAvoid[] = '/learning_path';
4359
        }
4360
4361
        // Admin setting for Hide/Show the folders of all users
4362
        if (api_get_setting('show_users_folders') == 'false') {
4363
            $foldersToAvoid[] = '/shared_folder';
4364
4365
            if (strstr($path, 'shared_folder_session_')) {
4366
                return true;
4367
            }
4368
        }
4369
4370
        // Admin setting for Hide/Show chat history folder
4371
        if (api_get_setting('show_chat_folder') == 'false') {
4372
            $foldersToAvoid[] = '/chat_files';
4373
        }
4374
4375
        if (is_array($foldersToAvoid)) {
4376
            return in_array($path, $foldersToAvoid);
4377
        } else {
4378
            return false;
4379
        }
4380
    }
4381
4382
    /**
4383
     * @return array
4384
     */
4385
    public static function get_system_folders()
4386
    {
4387
        return [
4388
            '/certificates',
4389
            '/HotPotatoes_files',
4390
            '/chat_files',
4391
            '/images',
4392
            '/flash',
4393
            '/audio',
4394
            '/video',
4395
            '/shared_folder',
4396
            '/learning_path',
4397
        ];
4398
    }
4399
4400
    /**
4401
     * @return array
4402
     */
4403
    public static function getProtectedFolderFromStudent()
4404
    {
4405
        return [
4406
            '/certificates',
4407
            '/HotPotatoes_files',
4408
            '/chat_files',
4409
            '/shared_folder',
4410
            '/learning_path',
4411
        ];
4412
    }
4413
4414
    /**
4415
     * @param array $courseInfo
4416
     *
4417
     * @return string 'visible' or 'invisible' string
4418
     */
4419
    public static function getDocumentDefaultVisibility($courseInfo)
4420
    {
4421
        $settings = api_get_setting('tool_visible_by_default_at_creation');
4422
        $defaultVisibility = 'visible';
4423
4424
        if (isset($settings['documents'])) {
4425
            $portalDefaultVisibility = 'invisible';
4426
            if ($settings['documents'] == 'true') {
4427
                $portalDefaultVisibility = 'visible';
4428
            }
4429
4430
            $defaultVisibility = $portalDefaultVisibility;
4431
        }
4432
4433
        if (api_get_setting('documents_default_visibility_defined_in_course') == 'true') {
4434
            $courseVisibility = api_get_course_setting('documents_default_visibility', $courseInfo);
4435
            if (!empty($courseVisibility) && in_array($courseVisibility, ['visible', 'invisible'])) {
4436
                $defaultVisibility = $courseVisibility;
4437
            }
4438
        }
4439
4440
        return $defaultVisibility;
4441
    }
4442
4443
    /**
4444
     * @param array  $courseInfo
4445
     * @param int    $id         doc id
4446
     * @param string $visibility visible/invisible
4447
     * @param int    $userId
4448
     */
4449
    public static function updateVisibilityFromAllSessions($courseInfo, $id, $visibility, $userId)
4450
    {
4451
        $sessionList = SessionManager::get_session_by_course($courseInfo['real_id']);
4452
4453
        if (!empty($sessionList)) {
4454
            foreach ($sessionList as $session) {
4455
                $sessionId = $session['id'];
4456
                api_item_property_update(
4457
                    $courseInfo,
4458
                    TOOL_DOCUMENT,
4459
                    $id,
4460
                    $visibility,
4461
                    $userId,
4462
                    null,
4463
                    null,
4464
                    null,
4465
                    null,
4466
                    $sessionId
4467
                );
4468
            }
4469
        }
4470
    }
4471
4472
    /**
4473
     * @param string $filePath
4474
     * @param string $path
4475
     * @param array  $courseInfo
4476
     * @param int    $sessionId
4477
     * @param string $whatIfFileExists overwrite|rename
4478
     * @param int    $userId
4479
     * @param int    $groupId
4480
     * @param int    $toUserId
4481
     * @param string $comment
4482
     *
4483
     * @return bool|path
4484
     */
4485
    public static function addFileToDocumentTool(
4486
        $filePath,
4487
        $path,
4488
        $courseInfo,
4489
        $sessionId,
4490
        $userId,
4491
        $whatIfFileExists = 'overwrite',
4492
        $groupId = null,
4493
        $toUserId = null,
4494
        $comment = null
4495
    ) {
4496
        if (!file_exists($filePath)) {
4497
            return false;
4498
        }
4499
4500
        $fileInfo = pathinfo($filePath);
4501
4502
        $file = [
4503
            'name' => $fileInfo['basename'],
4504
            'tmp_name' => $filePath,
4505
            'size' => filesize($filePath),
4506
            'from_file' => true,
4507
        ];
4508
4509
        $course_dir = $courseInfo['path'].'/document';
4510
        $baseWorkDir = api_get_path(SYS_COURSE_PATH).$course_dir;
4511
4512
        $filePath = handle_uploaded_document(
4513
            $courseInfo,
4514
            $file,
4515
            $baseWorkDir,
4516
            $path,
4517
            $userId,
4518
            $groupId,
4519
            $toUserId,
4520
            false,
4521
            $whatIfFileExists,
4522
            false,
4523
            false,
4524
            $comment,
4525
            $sessionId
4526
        );
4527
4528
        if ($filePath) {
4529
            return self::get_document_id(
4530
                $courseInfo,
4531
                $filePath,
4532
                $sessionId
4533
            );
4534
        }
4535
4536
        return false;
4537
    }
4538
4539
    /**
4540
     * Converts wav to mp3 file.
4541
     * Requires the ffmpeg lib. In ubuntu: sudo apt-get install ffmpeg.
4542
     *
4543
     * @param string $wavFile
4544
     * @param bool   $removeWavFileIfSuccess
4545
     *
4546
     * @return bool
4547
     */
4548
    public static function convertWavToMp3($wavFile, $removeWavFileIfSuccess = false)
4549
    {
4550
        if (file_exists($wavFile)) {
4551
            try {
4552
                $ffmpeg = \FFMpeg\FFMpeg::create();
4553
                $video = $ffmpeg->open($wavFile);
4554
4555
                $mp3File = str_replace('wav', 'mp3', $wavFile);
4556
                $result = $video->save(new FFMpeg\Format\Audio\Mp3(), $mp3File);
4557
                if ($result && $removeWavFileIfSuccess) {
4558
                    unlink($wavFile);
4559
                }
4560
4561
                if (file_exists($mp3File)) {
4562
                    return $mp3File;
4563
                }
4564
            } catch (Exception $e) {
4565
                error_log($e->getMessage());
4566
                error_log($e->getPrevious()->getMessage());
4567
            }
4568
        }
4569
4570
        return false;
4571
    }
4572
4573
    /**
4574
     * @param string $documentData     wav document information
4575
     * @param array  $courseInfo
4576
     * @param int    $sessionId
4577
     * @param int    $userId           user that adds the document
4578
     * @param string $whatIfFileExists
4579
     * @param bool   $deleteWavFile
4580
     *
4581
     * @return bool
4582
     */
4583
    public static function addAndConvertWavToMp3(
4584
        $documentData,
4585
        $courseInfo,
4586
        $sessionId,
4587
        $userId,
4588
        $whatIfFileExists = 'overwrite',
4589
        $deleteWavFile = false
4590
    ) {
4591
        if (empty($documentData)) {
4592
            return false;
4593
        }
4594
4595
        if (isset($documentData['absolute_path']) &&
4596
            file_exists($documentData['absolute_path'])
4597
        ) {
4598
            $mp3FilePath = self::convertWavToMp3($documentData['absolute_path']);
4599
4600
            if (!empty($mp3FilePath) && file_exists($mp3FilePath)) {
4601
                $documentId = self::addFileToDocumentTool(
4602
                    $mp3FilePath,
4603
                    dirname($documentData['path']),
4604
                    $courseInfo,
4605
                    $sessionId,
4606
                    $userId,
4607
                    $whatIfFileExists,
4608
                    null,
4609
                    null,
4610
                    $documentData['comment']
4611
                );
4612
4613
                if (!empty($documentId)) {
4614
                    if ($deleteWavFile) {
4615
                        $coursePath = $courseInfo['directory'].'/document';
4616
                        $documentPath = api_get_path(SYS_COURSE_PATH).$coursePath;
4617
                        self::delete_document(
4618
                            $courseInfo,
4619
                            null,
4620
                            $documentPath,
4621
                            $sessionId,
4622
                            $documentData['id']
4623
                        );
4624
                    }
4625
4626
                    return $documentId;
4627
                }
4628
            }
4629
        }
4630
4631
        return false;
4632
    }
4633
4634
    /**
4635
     * Sets.
4636
     *
4637
     * @param string $file         ($document_data['path'])
4638
     * @param string $file_url_sys
4639
     *
4640
     * @return string
4641
     */
4642
    public static function generateAudioTempFile($file, $file_url_sys)
4643
    {
4644
        //make temp audio
4645
        $temp_folder = api_get_path(SYS_ARCHIVE_PATH).'temp/audio';
4646
        if (!file_exists($temp_folder)) {
4647
            @mkdir($temp_folder, api_get_permissions_for_new_directories(), true);
4648
        }
4649
4650
        //make htaccess with allow from all, and file index.html into temp/audio
4651
        $htaccess = api_get_path(SYS_ARCHIVE_PATH).'temp/audio/.htaccess';
4652
        if (!file_exists($htaccess)) {
4653
            $htaccess_content = "order deny,allow\r\nallow from all\r\nOptions -Indexes";
4654
            $fp = @fopen(api_get_path(SYS_ARCHIVE_PATH).'temp/audio/.htaccess', 'w');
4655
            if ($fp) {
4656
                fwrite($fp, $htaccess_content);
4657
                fclose($fp);
4658
            }
4659
        }
4660
4661
        //encript temp name file
4662
        $name_crip = sha1(uniqid()); //encript
4663
        $findext = explode(".", $file);
4664
        $extension = $findext[count($findext) - 1];
4665
        $file_crip = $name_crip.'.'.$extension;
4666
4667
        //copy file to temp/audio directory
4668
        $from_sys = $file_url_sys;
4669
        $to_sys = api_get_path(SYS_ARCHIVE_PATH).'temp/audio/'.$file_crip;
4670
4671
        if (file_exists($from_sys)) {
4672
            copy($from_sys, $to_sys);
4673
        }
4674
4675
        // get file from tmp directory
4676
        Session::write('temp_audio_nanogong', $to_sys);
4677
4678
        return api_get_path(WEB_ARCHIVE_PATH).'temp/audio/'.$file_crip;
4679
    }
4680
4681
    /**
4682
     * Erase temp nanogong audio.
4683
     */
4684
    public static function removeGeneratedAudioTempFile()
4685
    {
4686
        $tempAudio = Session::read('temp_audio_nanogong');
4687
        if (!empty(isset($tempAudio)) && is_file($tempAudio)) {
4688
            unlink($tempAudio);
4689
            Session::erase('temp_audio_nanogong');
4690
        }
4691
    }
4692
4693
    /**
4694
     * Check if the path is used in this course.
4695
     *
4696
     * @param array  $courseInfo
4697
     * @param string $path
4698
     *
4699
     * @return array
4700
     */
4701
    public static function getDocumentByPathInCourse($courseInfo, $path)
4702
    {
4703
        $table = Database::get_course_table(TABLE_DOCUMENT);
4704
        $path = Database::escape_string($path);
4705
        $courseId = $courseInfo['real_id'];
4706
        if (empty($courseId)) {
4707
            return false;
4708
        }
4709
        $sql = "SELECT * FROM $table WHERE c_id = $courseId AND path = '$path'";
4710
        $result = Database::query($sql);
4711
4712
        return Database::store_result($result, 'ASSOC');
4713
    }
4714
4715
    /**
4716
     * @param array $_course
4717
     *
4718
     * @return int
4719
     */
4720
    public static function createDefaultAudioFolder($_course)
4721
    {
4722
        if (!isset($_course['path'])) {
4723
            return false;
4724
        }
4725
4726
        $audioId = null;
4727
        $path = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
4728
        if (!is_dir($path.'audio')) {
4729
            mkdir($path.'audio', api_get_permissions_for_new_directories());
4730
            $audioId = add_document($_course, '/audio', 'folder', 0, get_lang('Audio'));
4731
            api_item_property_update(
4732
                $_course,
4733
                TOOL_DOCUMENT,
4734
                $audioId,
4735
                'FolderCreated',
4736
                api_get_user_id(),
4737
                null,
4738
                null,
4739
                null,
4740
                null,
4741
                api_get_session_id()
4742
            );
4743
        }
4744
4745
        return $audioId;
4746
    }
4747
4748
    /**
4749
     * Generate a default certificate for a courses.
4750
     *
4751
     * @todo move to certificate lib
4752
     *
4753
     * @global string $css CSS directory
4754
     * @global string $img_dir image directory
4755
     * @global string $default_course_dir Course directory
4756
     * @global string $js JS directory
4757
     *
4758
     * @param array $courseData     The course info
4759
     * @param bool  $fromBaseCourse
4760
     * @param int   $sessionId
4761
     */
4762
    public static function generateDefaultCertificate(
4763
        $courseData,
4764
        $fromBaseCourse = false,
4765
        $sessionId = 0
4766
    ) {
4767
        if (empty($courseData)) {
4768
            return false;
4769
        }
4770
4771
        global $css, $img_dir, $default_course_dir, $js;
4772
        $codePath = api_get_path(REL_CODE_PATH);
4773
        $dir = '/certificates';
4774
        $comment = null;
4775
        $title = get_lang('DefaultCertificate');
4776
        $fileName = api_replace_dangerous_char($title);
4777
        $filePath = api_get_path(SYS_COURSE_PATH)."{$courseData['directory']}/document$dir";
4778
4779
        if (!is_dir($filePath)) {
4780
            mkdir($filePath, api_get_permissions_for_new_directories());
4781
        }
4782
4783
        $fileFullPath = "$filePath/$fileName.html";
4784
        $fileType = 'file';
4785
        $templateContent = file_get_contents(api_get_path(SYS_CODE_PATH).'gradebook/certificate_template/template.html');
4786
4787
        $search = ['{CSS}', '{IMG_DIR}', '{REL_CODE_PATH}', '{COURSE_DIR}'];
4788
        $replace = [$css.$js, $img_dir, $codePath, $default_course_dir];
4789
4790
        $fileContent = str_replace($search, $replace, $templateContent);
4791
        $saveFilePath = "$dir/$fileName.html";
4792
4793
        if ($fromBaseCourse) {
4794
            $defaultCertificateId = self::get_default_certificate_id(
4795
                $courseData['code'],
4796
                0
4797
            );
4798
            if (!empty($defaultCertificateId)) {
4799
                // We have a certificate from the course base
4800
                $documentData = self::get_document_data_by_id(
4801
                    $defaultCertificateId,
4802
                    $courseData['code'],
4803
                    false,
4804
                    0
4805
                );
4806
4807
                if ($documentData) {
4808
                    $fileContent = file_get_contents($documentData['absolute_path']);
4809
                }
4810
            }
4811
        }
4812
4813
        if (file_exists($fileFullPath) === false) {
4814
            $result = file_put_contents($fileFullPath, $fileContent);
4815
            if ($result) {
4816
                $fileSize = filesize($fileFullPath);
4817
4818
                $documentId = add_document(
4819
                    $courseData,
4820
                    $saveFilePath,
4821
                    $fileType,
4822
                    $fileSize,
4823
                    $title,
4824
                    $comment,
4825
                    0, //$readonly = 0,
4826
                    true, //$save_visibility = true,
4827
                    null, //$group_id = null,
4828
                    $sessionId
4829
                );
4830
4831
                api_item_property_update(
4832
                    $courseData,
4833
                    TOOL_DOCUMENT,
4834
                    $documentId,
4835
                    'DocumentAdded',
4836
                    api_get_user_id(),
4837
                    null,
4838
                    null,
4839
                    null,
4840
                    null,
4841
                    $sessionId
4842
                );
4843
4844
                $defaultCertificateId = self::get_default_certificate_id(
4845
                    $courseData['code'],
4846
                    $sessionId
4847
                );
4848
4849
                if (!isset($defaultCertificateId)) {
4850
                    self::attach_gradebook_certificate(
4851
                        $courseData['code'],
4852
                        $documentId,
4853
                        $sessionId
4854
                    );
4855
                }
4856
            }
4857
        }
4858
    }
4859
4860
    /**
4861
     * Update the document name.
4862
     *
4863
     * @param int    $documentId The document id
4864
     * @param string $newName    The new name
4865
     */
4866
    public static function renameDocument($documentId, $newName)
4867
    {
4868
        $documentId = intval($documentId);
4869
        $newName = Database::escape_string($newName);
4870
        $docuentTable = Database::get_course_table(TABLE_DOCUMENT);
4871
4872
        $values = [
4873
            'title' => $newName,
4874
        ];
4875
4876
        $whereConditions = [
4877
            'id = ?' => $documentId,
4878
        ];
4879
4880
        Database::update($docuentTable, $values, $whereConditions);
4881
    }
4882
4883
    /**
4884
     * Get folder/file suffix.
4885
     *
4886
     * @param array $courseInfo
4887
     * @param int   $sessionId
4888
     * @param int   $groupId
4889
     *
4890
     * @return string
4891
     */
4892
    public static function getDocumentSuffix($courseInfo, $sessionId, $groupId)
4893
    {
4894
        // If no session or group, then no suffix.
4895
        if (empty($sessionId) && empty($groupId)) {
4896
            return '';
4897
        }
4898
4899
        return '__'.intval($sessionId).'__'.intval($groupId);
4900
    }
4901
4902
    /**
4903
     * Fix a document name adding session id and group id
4904
     * Turns picture.jpg -> picture__1__2.jpg
4905
     * Where 1 = session id and 2 group id
4906
     * Of session id and group id are empty then the function returns:
4907
     * picture.jpg ->  picture.jpg.
4908
     *
4909
     * @param string $name       folder or file name
4910
     * @param string $type       'folder' or 'file'
4911
     * @param array  $courseInfo
4912
     * @param int    $sessionId
4913
     * @param int    $groupId
4914
     *
4915
     * @return string
4916
     */
4917
    public static function fixDocumentName($name, $type, $courseInfo, $sessionId, $groupId)
4918
    {
4919
        $suffix = self::getDocumentSuffix($courseInfo, $sessionId, $groupId);
4920
4921
        switch ($type) {
4922
            case 'folder':
4923
                $name = $name.$suffix;
4924
                break;
4925
            case 'file':
4926
                $name = self::addSuffixToFileName($name, $suffix);
4927
                break;
4928
        }
4929
4930
        return $name;
4931
    }
4932
4933
    /**
4934
     * Add a suffix to a file Example:
4935
     * /folder/picture.jpg => to /folder/picture_this.jpg
4936
     * where "_this" is the suffix.
4937
     *
4938
     * @param string $name
4939
     * @param string $suffix
4940
     *
4941
     * @return string
4942
     */
4943
    public static function addSuffixToFileName($name, $suffix)
4944
    {
4945
        $extension = pathinfo($name, PATHINFO_EXTENSION);
4946
        $fileName = pathinfo($name, PATHINFO_FILENAME);
4947
        $dir = pathinfo($name, PATHINFO_DIRNAME);
4948
4949
        if ($dir == '.') {
4950
            $dir = null;
4951
        }
4952
4953
        if (!empty($dir) && $dir != '/') {
4954
            $dir = $dir.'/';
4955
        }
4956
4957
        $name = $dir.$fileName.$suffix.'.'.$extension;
4958
4959
        return $name;
4960
    }
4961
4962
    /**
4963
     * Check if folder exist in the course base or in the session course.
4964
     *
4965
     * @param string $folder     Example: /folder/folder2
4966
     * @param array  $courseInfo
4967
     * @param int    $sessionId
4968
     * @param int    $groupId    group.id
4969
     *
4970
     * @return bool
4971
     */
4972
    public static function folderExists(
4973
        $folder,
4974
        $courseInfo,
4975
        $sessionId,
4976
        $groupId
4977
    ) {
4978
        $courseId = $courseInfo['real_id'];
4979
4980
        if (empty($courseId)) {
4981
            return false;
4982
        }
4983
4984
        $sessionId = (int) $sessionId;
4985
        $folderWithSuffix = self::fixDocumentName(
4986
            $folder,
4987
            'folder',
4988
            $courseInfo,
4989
            $sessionId,
4990
            $groupId
4991
        );
4992
4993
        $folder = Database::escape_string($folder);
4994
        $folderWithSuffix = Database::escape_string($folderWithSuffix);
4995
4996
        // Check if pathname already exists inside document table
4997
        $tbl_document = Database::get_course_table(TABLE_DOCUMENT);
4998
        $sql = "SELECT id, path FROM $tbl_document
4999
                WHERE
5000
                    filetype = 'folder' AND
5001
                    c_id = $courseId AND
5002
                    (path = '$folder' OR path = '$folderWithSuffix') AND
5003
                    (session_id = 0 OR session_id = $sessionId)
5004
        ";
5005
5006
        $rs = Database::query($sql);
5007
        if (Database::num_rows($rs)) {
5008
            return true;
5009
        }
5010
5011
        return false;
5012
    }
5013
5014
    /**
5015
     * Check if file exist in the course base or in the session course.
5016
     *
5017
     * @param string $fileName   Example: /folder/picture.jpg
5018
     * @param array  $courseInfo
5019
     * @param int    $sessionId
5020
     * @param int    $groupId
5021
     *
5022
     * @return bool
5023
     */
5024
    public static function documentExists(
5025
        $fileName,
5026
        $courseInfo,
5027
        $sessionId,
5028
        $groupId
5029
    ) {
5030
        $courseId = $courseInfo['real_id'];
5031
5032
        if (empty($courseId)) {
5033
            return false;
5034
        }
5035
5036
        $sessionId = (int) $sessionId;
5037
        $fileNameEscape = Database::escape_string($fileName);
5038
5039
        $fileNameWithSuffix = self::fixDocumentName(
5040
            $fileName,
5041
            'file',
5042
            $courseInfo,
5043
            $sessionId,
5044
            $groupId
5045
        );
5046
5047
        $fileNameWithSuffix = Database::escape_string($fileNameWithSuffix);
5048
5049
        // Check if pathname already exists inside document table
5050
        $table = Database::get_course_table(TABLE_DOCUMENT);
5051
        $sql = "SELECT id, path FROM $table
5052
                WHERE
5053
                    filetype = 'file' AND
5054
                    c_id = $courseId AND
5055
                    (
5056
                        path = '".$fileNameEscape."' OR
5057
                        path = '$fileNameWithSuffix'
5058
                    ) AND
5059
                    (session_id = 0 OR session_id = $sessionId)
5060
        ";
5061
        $rs = Database::query($sql);
5062
        if (Database::num_rows($rs)) {
5063
            return true;
5064
        }
5065
5066
        return false;
5067
    }
5068
5069
    /**
5070
     * Undo the suffix applied to a file example:
5071
     * turns picture__1__1.jpg to picture.jpg.
5072
     *
5073
     * @param string $name
5074
     * @param int    $courseId
5075
     * @param int    $sessionId
5076
     * @param int    $groupId
5077
     *
5078
     * @return string
5079
     */
5080
    public static function undoFixDocumentName(
5081
        $name,
5082
        $courseId,
5083
        $sessionId,
5084
        $groupId
5085
    ) {
5086
        if (empty($sessionId) && empty($groupId)) {
5087
            return $name;
5088
        }
5089
5090
        $suffix = self::getDocumentSuffix(
5091
            ['real_id' => $courseId],
5092
            $sessionId,
5093
            $groupId
5094
        );
5095
5096
        $name = str_replace($suffix, '', $name);
5097
5098
        return $name;
5099
    }
5100
5101
    /**
5102
     * @param string $path
5103
     * @param string $name
5104
     * @param array  $courseInfo
5105
     * @param int    $sessionId
5106
     * @param int    $groupId
5107
     *
5108
     * @return string
5109
     */
5110
    public static function getUniqueFileName($path, $name, $courseInfo, $sessionId, $groupId)
5111
    {
5112
        $counter = 1;
5113
        $filePath = $path.$name;
5114
        $uniqueName = $name;
5115
        while ($documentExists = self::documentExists(
5116
            $filePath,
5117
            $courseInfo,
5118
            $sessionId,
5119
            $groupId
5120
        )) {
5121
            $uniqueName = self::addSuffixToFileName($name, '_'.$counter);
5122
            $filePath = $path.$uniqueName;
5123
            $counter++;
5124
        }
5125
5126
        return $uniqueName;
5127
    }
5128
5129
    /**
5130
     * Builds the form that enables the user to
5131
     * select a directory to browse/upload in.
5132
     *
5133
     * @param array    An array containing the folders we want to be able to select
5134
     * @param string    The current folder (path inside of the "document" directory, including the prefix "/")
5135
     * @param string    Group directory, if empty, prevents documents to be uploaded
5136
     * (because group documents cannot be uploaded in root)
5137
     * @param bool    Whether to change the renderer (this will add a template <span>
5138
     * to the QuickForm object displaying the form)
5139
     *
5140
     * @return string html form
5141
     */
5142
    public static function build_directory_selector(
5143
        $folders,
5144
        $document_id,
5145
        $group_dir = '',
5146
        $change_renderer = false,
5147
        &$form = null,
5148
        $selectName = 'id',
5149
        $attributes = []
5150
    ) {
5151
        $doc_table = Database::get_course_table(TABLE_DOCUMENT);
5152
        $course_id = api_get_course_int_id();
5153
        $folder_titles = [];
5154
5155
        if (is_array($folders)) {
5156
            $escaped_folders = [];
5157
            foreach ($folders as $key => $val) {
5158
                $escaped_folders[$key] = Database::escape_string($val);
5159
            }
5160
            $folder_sql = implode("','", $escaped_folders);
5161
5162
            $sql = "SELECT path, title
5163
                    FROM $doc_table
5164
                    WHERE
5165
                        filetype = 'folder' AND
5166
                        c_id = $course_id AND
5167
                        path IN ('".$folder_sql."')
5168
                        ORDER BY path";
5169
            $res = Database::query($sql);
5170
            $folder_titles = [];
5171
            while ($obj = Database::fetch_object($res)) {
5172
                $folder_titles[$obj->path] = $obj->title;
5173
            }
5174
            //natcasesort($folder_titles);
5175
        }
5176
5177
        if (empty($form)) {
5178
            $form = new FormValidator('selector', 'GET', api_get_self().'?'.api_get_cidreq());
5179
            $attributes = ['onchange' => 'javascript: document.selector.submit();'];
5180
        }
5181
        $form->addElement('hidden', 'cidReq', api_get_course_id());
5182
        $form->addElement('hidden', 'id_session', api_get_session_id());
5183
        $form->addElement('hidden', 'gidReq', api_get_group_id());
5184
5185
        $parent_select = $form->addSelect(
5186
            $selectName,
5187
            get_lang('CurrentDirectory'),
5188
            '',
5189
            $attributes
5190
        );
5191
5192
        // Group documents cannot be uploaded in the root
5193
        if (empty($group_dir)) {
5194
            $parent_select->addOption(get_lang('Documents'), '/');
5195
5196
            if (is_array($folders)) {
5197
                $foldersSortedByTitles = [];
5198
                foreach ($folders as $folder_id => &$folder) {
5199
                    $selected = ($document_id == $folder_id) ? ' selected="selected"' : '';
5200
                    $path_parts = explode('/', $folder);
5201
                    $folder_titles[$folder] = cut($folder_titles[$folder], 80);
5202
                    $counter = count($path_parts) - 2;
5203
                    if ($counter > 0) {
5204
                        $label = str_repeat('&nbsp;&nbsp;&nbsp;', $counter).' &mdash; '.$folder_titles[$folder];
5205
                    } else {
5206
                        $label = ' &mdash; '.$folder_titles[$folder];
5207
                    }
5208
                    $label = Security::remove_XSS($label);
5209
                    $foldersSortedByTitles[$folder_id] = [
5210
                        'id' => $folder_id,
5211
                        'title' => $folder_titles[$folder],
5212
                        'selected' => $selected,
5213
                        'label' => $label,
5214
                    ];
5215
                }
5216
                foreach ($folders as $id => $title) {
5217
                    $parent_select->addOption(
5218
                        $foldersSortedByTitles[$id]['label'],
5219
                        $foldersSortedByTitles[$id]['id']
5220
                    );
5221
                    if ($foldersSortedByTitles[$id]['selected'] != '') {
5222
                        $parent_select->setSelected($foldersSortedByTitles[$id]['id']);
5223
                    }
5224
                }
5225
            }
5226
        } else {
5227
            if (!empty($folders)) {
5228
                $foldersSortedByTitles = [];
5229
                foreach ($folders as $folder_id => &$folder) {
5230
                    $selected = ($document_id == $folder_id) ? ' selected="selected"' : '';
5231
                    $label = $folder_titles[$folder];
5232
                    if ($folder == $group_dir) {
5233
                        $label = get_lang('Documents');
5234
                    } else {
5235
                        $path_parts = explode('/', str_replace($group_dir, '', $folder));
5236
                        $label = cut($label, 80);
5237
                        $label = str_repeat('&nbsp;&nbsp;&nbsp;', count($path_parts) - 2).' &mdash; '.$label;
5238
                    }
5239
                    $foldersSortedByTitles[$folder_titles[$folder]] = [
5240
                        'id' => $folder_id,
5241
                        'selected' => $selected,
5242
                        'label' => $label,
5243
                    ];
5244
                }
5245
                foreach ($folder_titles as $title) {
5246
                    $parent_select->addOption(
5247
                        $foldersSortedByTitles[$title]['label'],
5248
                        $foldersSortedByTitles[$title]['id']
5249
                    );
5250
                    if ($foldersSortedByTitles[$title]['selected'] != '') {
5251
                        $parent_select->setSelected($foldersSortedByTitles[$title]['id']);
5252
                    }
5253
                }
5254
            }
5255
        }
5256
5257
        return $form->toHtml();
5258
    }
5259
5260
    /**
5261
     * Create a html hyperlink depending on if it's a folder or a file.
5262
     *
5263
     * @param string $documentWebPath
5264
     * @param array  $document_data
5265
     * @param bool   $show_as_icon      - if it is true, only a clickable icon will be shown
5266
     * @param int    $visibility        (1/0)
5267
     * @param int    $counter
5268
     * @param int    $size
5269
     * @param bool   $isAllowedToEdit
5270
     * @param bool   $isCertificateMode
5271
     *
5272
     * @return string url
5273
     */
5274
    public static function create_document_link(
5275
        $documentWebPath,
5276
        $document_data,
5277
        $show_as_icon = false,
5278
        $counter = null,
5279
        $visibility = true,
5280
        $size = 0,
5281
        $isAllowedToEdit = false,
5282
        $isCertificateMode = false
5283
    ) {
5284
        global $dbl_click_id;
5285
        $www = $documentWebPath;
5286
5287
        $secToken = Security::getTokenFromSession();
5288
5289
        $sessionId = api_get_session_id();
5290
        $courseParams = api_get_cidreq();
5291
        $webODFList = self::get_web_odf_extension_list();
5292
5293
        // Get the title or the basename depending on what we're using
5294
        if ($document_data['title'] != '') {
5295
            $title = $document_data['title'];
5296
        } else {
5297
            $title = basename($document_data['path']);
5298
        }
5299
5300
        if (api_get_configuration_value('save_titles_as_html')) {
5301
            $title = strip_tags($title);
5302
        }
5303
5304
        $filetype = $document_data['filetype'];
5305
        $path = $document_data['path'];
5306
        $url_path = urlencode($document_data['path']);
5307
        $basePageUrl = api_get_path(WEB_CODE_PATH).'document/';
5308
        $pageUrl = $basePageUrl.'document.php';
5309
5310
        // Add class="invisible" on invisible files
5311
        $visibility_class = $visibility == false ? ' class="muted"' : '';
5312
        $forcedownload_link = '';
5313
        $forcedownload_icon = '';
5314
        $prevent_multiple_click = '';
5315
        $force_download_html = '';
5316
5317
        if (!$show_as_icon) {
5318
            // Build download link (icon)
5319
            $forcedownload_link = $filetype === 'folder'
5320
                ? $pageUrl.'?'.$courseParams.'&action=downloadfolder&id='.$document_data['id'].'&sec_token='.$secToken
5321
                : $pageUrl.'?'.$courseParams.'&amp;action=download&amp;id='.$document_data['id'].'&sec_token='.$secToken;
5322
            // Folder download or file download?
5323
            $forcedownload_icon = $filetype === 'folder' ? 'save_pack.png' : 'save.png';
5324
            // Prevent multiple clicks on zipped folder download
5325
            $prevent_multiple_click = $filetype === 'folder' ? " onclick=\"javascript: if(typeof clic_$dbl_click_id == 'undefined' || !clic_$dbl_click_id) { clic_$dbl_click_id=true; window.setTimeout('clic_".($dbl_click_id++)."=false;',10000); } else { return false; }\"" : '';
5326
        }
5327
5328
        $target = '_self';
5329
        $is_browser_viewable_file = false;
5330
5331
        if ($filetype === 'file') {
5332
            // Check the extension
5333
            $ext = explode('.', $path);
5334
            $ext = strtolower($ext[count($ext) - 1]);
5335
5336
            // HTML-files an some other types are shown in a frameset by default.
5337
            $is_browser_viewable_file = self::isBrowserViewable($ext);
5338
            if ($is_browser_viewable_file) {
5339
                $url = $basePageUrl.'showinframes.php?'.$courseParams.'&id='.$document_data['id'];
5340
            } else {
5341
                // url-encode for problematic characters (we may not call them dangerous characters...)
5342
                //$path = str_replace('%2F', '/', $url_path).'?'.$courseParams;
5343
                $url = $www.str_replace('%2F', '/', $url_path).'?'.$courseParams;
5344
            }
5345
        } else {
5346
            $url = $pageUrl.'?'.$courseParams.'&id='.$document_data['id'];
5347
        }
5348
5349
        if ($isCertificateMode) {
5350
            $url .= '&certificate=true&selectcat='.(isset($_GET['selectcat']) ? $_GET['selectcat'] : '');
5351
        }
5352
5353
        // The little download icon
5354
        $tooltip_title = $title;
5355
        $tooltip_title_alt = $tooltip_title;
5356
5357
        if ($filetype === 'link') {
5358
            $tooltip_title_alt = $title;
5359
            $url = $document_data['comment'].'" target="_blank';
5360
        }
5361
5362
        if ($path === '/shared_folder') {
5363
            $tooltip_title_alt = get_lang('UserFolders');
5364
        } elseif (strstr($path, 'shared_folder_session_')) {
5365
            $tooltip_title_alt = get_lang('UserFolders').' ('.api_get_session_name(api_get_session_id()).')';
5366
        } elseif (strstr($tooltip_title, 'sf_user_')) {
5367
            $userinfo = api_get_user_info(substr($tooltip_title, 8));
5368
            $tooltip_title_alt = get_lang('UserFolder').' '.$userinfo['complete_name'];
5369
        } elseif ($path == '/chat_files') {
5370
            $tooltip_title_alt = get_lang('ChatFiles');
5371
        } elseif ($path == '/learning_path') {
5372
            $tooltip_title_alt = get_lang('LearningPaths');
5373
        } elseif ($path == '/video') {
5374
            $tooltip_title_alt = get_lang('Video');
5375
        } elseif ($path == '/audio') {
5376
            $tooltip_title_alt = get_lang('Audio');
5377
        } elseif ($path == '/flash') {
5378
            $tooltip_title_alt = get_lang('Flash');
5379
        } elseif ($path == '/images') {
5380
            $tooltip_title_alt = get_lang('Images');
5381
        } elseif ($path == '/images/gallery') {
5382
            $tooltip_title_alt = get_lang('DefaultCourseImages');
5383
        }
5384
5385
        $copyToMyFiles = $open_in_new_window_link = '';
5386
        $curdirpath = isset($_GET['curdirpath']) ? Security::remove_XSS($_GET['curdirpath']) : null;
5387
        $send_to = null;
5388
        $checkExtension = $path;
5389
        $extension = pathinfo($path, PATHINFO_EXTENSION);
5390
        $document_data['file_extension'] = $extension;
5391
5392
        if (!$show_as_icon) {
5393
            // to force download if a document can be downloaded or not
5394
            $hideDownloadIcon = false;
5395
            if (true === api_get_configuration_value('documents_hide_download_icon')) {
5396
                $hideDownloadIcon = true;
5397
            }
5398
            if (self::getHideDownloadIcon($document_data['id'])) {
5399
                $hideDownloadIcon = false;
5400
            }
5401
            if (!$hideDownloadIcon) {
5402
                if ($filetype == 'folder') {
5403
                    if ($isAllowedToEdit ||
5404
                        api_is_platform_admin() ||
5405
                        api_get_setting('students_download_folders') == 'true'
5406
                    ) {
5407
                        // filter: when I am into a shared folder, I can only show "my shared folder" for donwload
5408
                        if (self::is_shared_folder($curdirpath, $sessionId)) {
5409
                            if (preg_match('/shared_folder\/sf_user_'.api_get_user_id().'$/', urldecode($forcedownload_link)) ||
5410
                                preg_match('/shared_folder_session_'.$sessionId.'\/sf_user_'.api_get_user_id().'$/', urldecode($forcedownload_link)) ||
5411
                                $isAllowedToEdit || api_is_platform_admin()
5412
                            ) {
5413
                                $force_download_html = $size == 0 ? '' : '<a href="'.$forcedownload_link.'" style="float:right"'.$prevent_multiple_click.'>'.
5414
                                    Display::return_icon($forcedownload_icon, get_lang('Download'), [], ICON_SIZE_SMALL).'</a>';
5415
                            }
5416
                        } elseif (!preg_match('/shared_folder/', urldecode($forcedownload_link)) ||
5417
                            $isAllowedToEdit ||
5418
                            api_is_platform_admin()
5419
                        ) {
5420
                            $force_download_html = $size == 0 ? '' : '<a href="'.$forcedownload_link.'" style="float:right"'.$prevent_multiple_click.'>'.
5421
                                Display::return_icon($forcedownload_icon, get_lang('Download'), [], ICON_SIZE_SMALL).'</a>';
5422
                        }
5423
                    }
5424
                } else {
5425
                    $force_download_html = $size == 0 ? '' : '<a href="'.$forcedownload_link.'" style="float:right"'.$prevent_multiple_click.' download="'.$document_data['basename'].'">'.
5426
                        Display::return_icon($forcedownload_icon, get_lang('Download'), [], ICON_SIZE_SMALL).'</a>';
5427
                }
5428
            }
5429
5430
            // Copy files to user's myfiles
5431
            if (api_get_setting('allow_my_files') === 'true' &&
5432
                api_get_setting('users_copy_files') === 'true' && api_is_anonymous() === false
5433
            ) {
5434
                $copy_myfiles_link = $filetype === 'file' ? $pageUrl.'?'.$courseParams.'&action=copytomyfiles&id='.$document_data['id'].'&sec_token='.$secToken : api_get_self().'?'.$courseParams.'&sec_token='.$secToken;
5435
                if ($filetype === 'file') {
5436
                    $copyToMyFiles = '<a href="'.$copy_myfiles_link.'" style="float:right"'.$prevent_multiple_click.'>'.
5437
                        Display::return_icon('briefcase.png', get_lang('CopyToMyFiles'), [], ICON_SIZE_SMALL).'&nbsp;&nbsp;</a>';
5438
5439
                    if (api_get_setting('allow_my_files') === 'false') {
5440
                        $copyToMyFiles = '';
5441
                    }
5442
                }
5443
            }
5444
5445
            $pdf_icon = '';
5446
            if (!$isAllowedToEdit &&
5447
                $filetype === 'file' &&
5448
                api_get_setting('students_export2pdf') == 'true' &&
5449
                in_array($extension, ['html', 'htm'])
5450
            ) {
5451
                $pdf_icon = ' <a style="float:right".'.$prevent_multiple_click.' href="'.$pageUrl.'?'.$courseParams.'&action=export_to_pdf&id='.$document_data['id'].'&sec_token='.$secToken.'&curdirpath='.$curdirpath.'">'.
5452
                    Display::return_icon('pdf.png', get_lang('Export2PDF'), [], ICON_SIZE_SMALL).'</a> ';
5453
            }
5454
5455
            if ($is_browser_viewable_file) {
5456
                $open_in_new_window_link = '<a href="'.$www.str_replace('%2F', '/', $url_path).'?'.$courseParams.'" style="float:right"'.$prevent_multiple_click.' target="_blank">'.
5457
                    Display::return_icon('open_in_new_window.png', get_lang('OpenInANewWindow'), [], ICON_SIZE_SMALL).'&nbsp;&nbsp;</a>';
5458
            }
5459
5460
            $hook = HookDocumentItemView::create();
5461
            $hookItemViewTools = $hook->setEventData($document_data)->notifyDocumentItemView();
5462
5463
            $rightTools = implode(
5464
                PHP_EOL,
5465
                [
5466
                    $force_download_html,
5467
                    $send_to,
5468
                    $copyToMyFiles,
5469
                    $open_in_new_window_link,
5470
                    $pdf_icon,
5471
                    $hookItemViewTools ? implode(PHP_EOL, $hookItemViewTools) : '',
5472
                ]
5473
            );
5474
5475
            if ($filetype === 'file') {
5476
                // Sound preview
5477
                if (preg_match('/mp3$/i', urldecode($checkExtension)) ||
5478
                    (preg_match('/wav$/i', urldecode($checkExtension))) ||
5479
                    preg_match('/ogg$/i', urldecode($checkExtension))
5480
                ) {
5481
                    return '<span style="float:left" '.$visibility_class.'>'.
5482
                        $title.
5483
                        '</span>'.$rightTools;
5484
                } elseif (
5485
                    // Show preview
5486
                    preg_match('/swf$/i', urldecode($checkExtension)) ||
5487
                    preg_match('/png$/i', urldecode($checkExtension)) ||
5488
                    preg_match('/gif$/i', urldecode($checkExtension)) ||
5489
                    preg_match('/jpg$/i', urldecode($checkExtension)) ||
5490
                    preg_match('/jpeg$/i', urldecode($checkExtension)) ||
5491
                    preg_match('/bmp$/i', urldecode($checkExtension)) ||
5492
                    preg_match('/svg$/i', urldecode($checkExtension))
5493
                ) {
5494
                    // Simpler version of showinframesmin.php with no headers
5495
                    $url = 'show_content.php?'.$courseParams.'&id='.$document_data['id'];
5496
                    $class = 'ajax';
5497
                    if ($visibility == false) {
5498
                        $class = 'ajax text-muted';
5499
                    }
5500
5501
                    return Display::url(
5502
                            $title,
5503
                            $url,
5504
                            [
5505
                                'class' => $class,
5506
                                'title' => $tooltip_title_alt,
5507
                                'data-title' => $title,
5508
                                'style' => 'float:left;',
5509
                            ]
5510
                        )
5511
                        .$rightTools;
5512
                } else {
5513
                    // For a "PDF Download" of the file.
5514
                    $pdfPreview = null;
5515
5516
                    if (OnlyofficePlugin::create()->isEnabled() &&
5517
                        OnlyofficePlugin::isExtensionAllowed($document_data['file_extension']) &&
5518
                        method_exists('OnlyofficeTools', 'getPathToView')
5519
                    ) {
5520
                        $url = OnlyofficeTools::getPathToView($document_data['id']);
5521
                    } elseif ($ext != 'pdf' && !in_array($ext, $webODFList)) {
5522
                        $url = $basePageUrl.'showinframes.php?'.$courseParams.'&id='.$document_data['id'];
5523
                    } else {
5524
                        $pdfPreview = Display::url(
5525
                            Display::return_icon('preview.png', get_lang('Preview'), null, ICON_SIZE_SMALL),
5526
                            api_get_path(WEB_CODE_PATH).'document/showinframes.php?'.$courseParams.'&id='.$document_data['id'],
5527
                            ['style' => 'float:right']
5528
                        );
5529
                    }
5530
                    // No plugin just the old and good showinframes.php page
5531
                    return '<a href="'.$url.'" title="'.$tooltip_title_alt.'" style="float:left" '.$visibility_class.' >'.$title.'</a>'.
5532
                        $pdfPreview.$rightTools;
5533
                }
5534
            } else {
5535
                return '<a href="'.$url.'" title="'.$tooltip_title_alt.'" '.$visibility_class.' style="float:left">'.$title.'</a>'
5536
                    .$rightTools;
5537
            }
5538
            // end copy files to users myfiles
5539
        } else {
5540
            // Icon column
5541
            if (preg_match('/shared_folder/', urldecode($checkExtension)) &&
5542
                preg_match('/shared_folder$/', urldecode($checkExtension)) == false &&
5543
                preg_match('/shared_folder_session_'.$sessionId.'$/', urldecode($url)) == false
5544
            ) {
5545
                if ($filetype == 'file') {
5546
                    //Sound preview
5547
                    if (preg_match('/mp3$/i', urldecode($checkExtension)) ||
5548
                        (preg_match('/wav$/i', urldecode($checkExtension))) ||
5549
                        preg_match('/ogg$/i', urldecode($checkExtension))) {
5550
                        return self::generateAudioPreview($documentWebPath, $document_data);
5551
                    } elseif (
5552
                        // Show preview
5553
                        preg_match('/swf$/i', urldecode($checkExtension)) ||
5554
                        preg_match('/png$/i', urldecode($checkExtension)) ||
5555
                        preg_match('/gif$/i', urldecode($checkExtension)) ||
5556
                        preg_match('/jpg$/i', urldecode($checkExtension)) ||
5557
                        preg_match('/jpeg$/i', urldecode($checkExtension)) ||
5558
                        preg_match('/bmp$/i', urldecode($checkExtension)) ||
5559
                        preg_match('/svg$/i', urldecode($checkExtension))
5560
                    ) {
5561
                        $url = $basePageUrl.'showinframes.php?'.$courseParams.'&id='.$document_data['id'];
5562
5563
                        return '<a href="'.$url.'" title="'.$tooltip_title_alt.'" '.$visibility_class.' style="float:left">'.
5564
                            self::build_document_icon_tag($filetype, $path, $isAllowedToEdit).
5565
                            Display::return_icon('shared.png', get_lang('ResourceShared'), []).
5566
                            '</a>';
5567
                    } else {
5568
                        return '<a href="'.$url.'" title="'.$tooltip_title_alt.'" '.$visibility_class.' style="float:left">'.
5569
                            self::build_document_icon_tag($filetype, $path, $isAllowedToEdit).
5570
                            Display::return_icon('shared.png', get_lang('ResourceShared'), []).
5571
                            '</a>';
5572
                    }
5573
                } else {
5574
                    return '<a href="'.$url.'" title="'.$tooltip_title_alt.'" target="'.$target.'"'.$visibility_class.' style="float:left">'.
5575
                        self::build_document_icon_tag($filetype, $path, $isAllowedToEdit).
5576
                        Display::return_icon('shared.png', get_lang('ResourceShared'), []).
5577
                        '</a>';
5578
                }
5579
            } else {
5580
                if ($filetype === 'file') {
5581
                    // Sound preview with jplayer
5582
                    if (preg_match('/mp3$/i', urldecode($checkExtension)) ||
5583
                        (preg_match('/wav$/i', urldecode($checkExtension))) ||
5584
                        preg_match('/ogg$/i', urldecode($checkExtension))) {
5585
                        return self::generateAudioPreview($documentWebPath, $document_data);
5586
                    } elseif (
5587
                        //Show preview
5588
                        preg_match('/html$/i', urldecode($checkExtension)) ||
5589
                        preg_match('/htm$/i', urldecode($checkExtension)) ||
5590
                        preg_match('/swf$/i', urldecode($checkExtension)) ||
5591
                        preg_match('/png$/i', urldecode($checkExtension)) ||
5592
                        preg_match('/gif$/i', urldecode($checkExtension)) ||
5593
                        preg_match('/jpg$/i', urldecode($checkExtension)) ||
5594
                        preg_match('/jpeg$/i', urldecode($checkExtension)) ||
5595
                        preg_match('/bmp$/i', urldecode($checkExtension)) ||
5596
                        preg_match('/svg$/i', urldecode($checkExtension))
5597
                    ) {
5598
                        $url = $basePageUrl.'showinframes.php?'.$courseParams.'&id='.$document_data['id']; //without preview
5599
5600
                        return '<a href="'.$url.'" title="'.$tooltip_title_alt.'" '.$visibility_class.' style="float:left">'.
5601
                            self::build_document_icon_tag($filetype, $path, $isAllowedToEdit).
5602
                            '</a>';
5603
                    } else {
5604
                        return '<a href="'.$url.'" title="'.$tooltip_title_alt.'" '.$visibility_class.' style="float:left">'.
5605
                            self::build_document_icon_tag($filetype, $path, $isAllowedToEdit).
5606
                            '</a>';
5607
                    }
5608
                } else {
5609
                    return '<a href="'.$url.'" title="'.$tooltip_title_alt.'" target="'.$target.'"'.$visibility_class.' style="float:left">'.
5610
                        self::build_document_icon_tag($filetype, $path, $isAllowedToEdit).
5611
                        '</a>';
5612
                }
5613
            }
5614
        }
5615
    }
5616
5617
    /**
5618
     * Builds an img html tag for the file type.
5619
     *
5620
     * @param string $type            (file/folder)
5621
     * @param string $path
5622
     * @param bool   $isAllowedToEdit
5623
     *
5624
     * @return string img html tag
5625
     */
5626
    public static function build_document_icon_tag($type, $path, $isAllowedToEdit = null)
5627
    {
5628
        $basename = basename($path);
5629
        $sessionId = api_get_session_id();
5630
        if (is_null($isAllowedToEdit)) {
5631
            $isAllowedToEdit = api_is_allowed_to_edit(null, true);
5632
        }
5633
        $user_image = false;
5634
        if ($type == 'file') {
5635
            $icon = choose_image($basename);
5636
            $basename = substr(strrchr($basename, '.'), 1);
5637
        } elseif ($type == 'link') {
5638
            $icon = 'clouddoc.png';
5639
            $basename = get_lang('CloudFileLink');
5640
        } else {
5641
            if ($path == '/shared_folder') {
5642
                $icon = 'folder_users.png';
5643
                if ($isAllowedToEdit) {
5644
                    $basename = get_lang('HelpUsersFolder');
5645
                } else {
5646
                    $basename = get_lang('UserFolders');
5647
                }
5648
            } elseif (strstr($basename, 'sf_user_')) {
5649
                $userInfo = api_get_user_info(substr($basename, 8));
5650
                $icon = $userInfo['avatar_small'];
5651
                $basename = get_lang('UserFolder').' '.$userInfo['complete_name'];
5652
                $user_image = true;
5653
            } elseif (strstr($path, 'shared_folder_session_')) {
5654
                $sessionName = api_get_session_name($sessionId);
5655
                if ($isAllowedToEdit) {
5656
                    $basename = '***('.$sessionName.')*** '.get_lang('HelpUsersFolder');
5657
                } else {
5658
                    $basename = get_lang('UserFolders').' ('.$sessionName.')';
5659
                }
5660
                $icon = 'folder_users.png';
5661
            } else {
5662
                $icon = 'folder_document.png';
5663
5664
                if ($path == '/audio') {
5665
                    $icon = 'folder_audio.png';
5666
                    if ($isAllowedToEdit) {
5667
                        $basename = get_lang('HelpDefaultDirDocuments');
5668
                    } else {
5669
                        $basename = get_lang('Audio');
5670
                    }
5671
                } elseif ($path == '/flash') {
5672
                    $icon = 'folder_flash.png';
5673
                    if ($isAllowedToEdit) {
5674
                        $basename = get_lang('HelpDefaultDirDocuments');
5675
                    } else {
5676
                        $basename = get_lang('Flash');
5677
                    }
5678
                } elseif ($path == '/images') {
5679
                    $icon = 'folder_images.png';
5680
                    if ($isAllowedToEdit) {
5681
                        $basename = get_lang('HelpDefaultDirDocuments');
5682
                    } else {
5683
                        $basename = get_lang('Images');
5684
                    }
5685
                } elseif ($path == '/video') {
5686
                    $icon = 'folder_video.png';
5687
                    if ($isAllowedToEdit) {
5688
                        $basename = get_lang('HelpDefaultDirDocuments');
5689
                    } else {
5690
                        $basename = get_lang('Video');
5691
                    }
5692
                } elseif ($path == '/images/gallery') {
5693
                    $icon = 'folder_gallery.png';
5694
                    if ($isAllowedToEdit) {
5695
                        $basename = get_lang('HelpDefaultDirDocuments');
5696
                    } else {
5697
                        $basename = get_lang('Gallery');
5698
                    }
5699
                } elseif ($path == '/chat_files') {
5700
                    $icon = 'folder_chat.png';
5701
                    if ($isAllowedToEdit) {
5702
                        $basename = get_lang('HelpFolderChat');
5703
                    } else {
5704
                        $basename = get_lang('ChatFiles');
5705
                    }
5706
                } elseif ($path == '/learning_path') {
5707
                    $icon = 'folder_learningpath.png';
5708
                    if ($isAllowedToEdit) {
5709
                        $basename = get_lang('HelpFolderLearningPaths');
5710
                    } else {
5711
                        $basename = get_lang('LearningPaths');
5712
                    }
5713
                }
5714
            }
5715
        }
5716
5717
        if ($user_image) {
5718
            return Display::img($icon, $basename, [], false);
5719
        }
5720
5721
        return Display::return_icon($icon, $basename, [], ICON_SIZE_SMALL);
5722
    }
5723
5724
    /**
5725
     * Creates the row of edit icons for a file/folder.
5726
     *
5727
     * @param array $document_data
5728
     * @param int   $id
5729
     * @param bool  $is_template
5730
     * @param int   $is_read_only
5731
     * @param int   $visibility    (1/0)
5732
     *
5733
     * @return string html img tags with hyperlinks
5734
     */
5735
    public static function build_edit_icons($document_data, $id, $is_template, $is_read_only, $visibility)
5736
    {
5737
        $sessionId = api_get_session_id();
5738
        $courseParams = api_get_cidreq();
5739
        $document_id = $document_data['id'];
5740
        $type = $document_data['filetype'];
5741
        $is_read_only = $document_data['readonly'];
5742
        $path = $document_data['path'];
5743
5744
        $secToken = Security::getTokenFromSession();
5745
5746
        if ($type == 'link') {
5747
            $parent_id = self::get_document_id(
5748
                api_get_course_info(),
5749
                rtrim($path, '/'),
5750
                0
5751
            );
5752
        } else {
5753
            $parent_id = self::get_document_id(
5754
                api_get_course_info(),
5755
                dirname($path),
5756
                0
5757
            );
5758
        }
5759
5760
        if (empty($parent_id) && !empty($sessionId)) {
5761
            $parent_id = self::get_document_id(
5762
                api_get_course_info(),
5763
                dirname($path),
5764
                $sessionId
5765
            );
5766
        }
5767
5768
        $curdirpath = dirname($document_data['path']);
5769
        $is_certificate_mode = self::is_certificate_mode($path);
5770
        $curdirpath = urlencode($curdirpath);
5771
        $extension = pathinfo($path, PATHINFO_EXTENSION);
5772
        //@todo Implement remote support for converter
5773
        $usePpt2lp = api_get_setting('service_ppt2lp', 'active') == 'true' && api_get_setting('service_ppt2lp', 'host') == 'localhost';
5774
        $formatTypeList = self::getFormatTypeListConvertor('from', $extension);
5775
        $formatType = current($formatTypeList);
5776
5777
        // If document is read only *or* we're in a session and the document
5778
        // is from a non-session context, hide the edition capabilities
5779
        $modify_icons = [];
5780
        $modify_icons[] = self::getButtonEdit($is_read_only, $document_data, $extension, $is_certificate_mode);
5781
5782
        $hook = HookDocumentItemAction::create();
5783
        if (!empty($hook)) {
5784
            $hook->setEventData($document_data);
5785
            $data = $hook->notifyDocumentItemAction(HOOK_EVENT_TYPE_PRE);
5786
            if (isset($data['actions'])) {
5787
                foreach ($data['actions'] as $action) {
5788
                    $modify_icons[] = $action;
5789
                }
5790
            }
5791
        }
5792
5793
        $modify_icons[] = self::getButtonMove($is_read_only, $document_data, $is_certificate_mode, $parent_id);
5794
        $modify_icons[] = self::getButtonVisibility(
5795
            $is_read_only,
5796
            $visibility,
5797
            $document_data,
5798
            $is_certificate_mode,
5799
            $parent_id
5800
        );
5801
        $modify_icons[] = self::getButtonDelete(
5802
            $is_read_only,
5803
            $document_data,
5804
            $is_certificate_mode,
5805
            $curdirpath,
5806
            $parent_id
5807
        );
5808
5809
        if (!$is_read_only) {
5810
            // Add action to covert to PDF, will create a new document whit same filename but .pdf extension
5811
            // @TODO: add prompt to select a format target
5812
            if (!in_array($path, self::get_system_folders())) {
5813
                if ($usePpt2lp && $formatType) {
5814
                    $modify_icons[] = Display::url(
5815
                        Display::return_icon('convert.png', get_lang('Convert')),
5816
                        '#',
5817
                        ['class' => 'convertAction', 'data-documentId' => $document_id, 'data-formatType' => $formatType]
5818
                    );
5819
                }
5820
            }
5821
        }
5822
5823
        if ($type == 'file' && ($extension == 'html' || $extension == 'htm')) {
5824
            if ($is_template == 0) {
5825
                if ((isset($_GET['curdirpath']) && $_GET['curdirpath'] != '/certificates') || !isset($_GET['curdirpath'])) {
5826
                    $modify_icons[] = Display::url(
5827
                        Display::return_icon('wizard.png', get_lang('AddAsTemplate')),
5828
                        api_get_self()."?$courseParams&curdirpath=$curdirpath&add_as_template=$id&sec_token=$secToken"
5829
                    );
5830
                }
5831
                if ((isset($_GET['curdirpath']) && $_GET['curdirpath'] == '/certificates') || $is_certificate_mode) {//allow attach certificate to course
5832
                    $visibility_icon_certificate = 'nocertificate';
5833
                    if (self::get_default_certificate_id(api_get_course_id()) == $id) {
5834
                        $visibility_icon_certificate = 'certificate';
5835
                        $certificate = get_lang('DefaultCertificate');
5836
                        $preview = get_lang('PreviewCertificate');
5837
                        $is_preview = true;
5838
                    } else {
5839
                        $is_preview = false;
5840
                        $certificate = get_lang('NoDefaultCertificate');
5841
                    }
5842
                    if (isset($_GET['selectcat'])) {
5843
                        $modify_icons[] = Display::url(
5844
                            Display::return_icon($visibility_icon_certificate.'.png', $certificate),
5845
                            api_get_self()."?$courseParams&curdirpath=$curdirpath&selectcat=".intval($_GET['selectcat'])."&set_certificate=$id&sec_token=$secToken"
5846
                        );
5847
                        if ($is_preview) {
5848
                            $modify_icons[] = Display::url(
5849
                                Display::return_icon('preview_view.png', $preview),
5850
                                api_get_self()."?$courseParams&curdirpath=$curdirpath&set_preview=$id&sec_token=$secToken"
5851
                            );
5852
                        }
5853
                    }
5854
                }
5855
            } else {
5856
                $modify_icons[] = Display::url(
5857
                    Display::return_icon('wizard_na.png', get_lang('RemoveAsTemplate')),
5858
                    api_get_self()."?$courseParams&curdirpath=$curdirpath&remove_as_template=$id&sec_token=$secToken"
5859
                );
5860
            }
5861
5862
            $modify_icons[] = Display::url(
5863
                Display::return_icon('pdf.png', get_lang('Export2PDF')),
5864
                api_get_self()."?$courseParams&action=export_to_pdf&id=$id&curdirpath=$curdirpath&sec_token=$secToken"
5865
            );
5866
        }
5867
        if ($type == 'file') {
5868
            $randomUploadName = md5(uniqid(mt_rand(), true));
5869
            $modify_icons[] = Display::url(
5870
                Display::return_icon('upload_file.png', get_lang('ReplaceFile')),
5871
                "#!",
5872
                [
5873
                    'data-id' => $randomUploadName,
5874
                    'class' => 'removeHiddenFile',
5875
                ]
5876
            );
5877
            $form = new FormValidator(
5878
                'upload',
5879
                'POST',
5880
                api_get_self().'?'.api_get_cidreq()."&sec_token=$secToken",
5881
                '',
5882
                ['enctype' => 'multipart/form-data']
5883
            );
5884
            $form->addElement('html', "<div class='replaceIndividualFile upload_element_".$randomUploadName." hidden'>");
5885
            $form->addElement('hidden', 'id_'.$randomUploadName, $randomUploadName);
5886
            $form->addElement('hidden', 'currentFile', '');
5887
            $form->addElement('hidden', 'currentUrl', api_get_self().'?'.api_get_cidreq().'&id='.$document_id);
5888
            $form->addElement('hidden', 'id_'.$randomUploadName, $document_id);
5889
            $label = '';
5890
            $form->addElement('file', 'file_'.$randomUploadName, [get_lang('File'), $label], 'style="width: 250px" id="user_upload"');
5891
            $form->addButtonSend(get_lang('SendDocument'), 'submitDocument');
5892
            $form->addProgress('DocumentUpload', 'file');
5893
            $form->addElement('html', '</div>');
5894
5895
            $html = $form->returnForm();
5896
5897
            $modify_icons[] = $html;
5898
        }
5899
5900
        // Specific case to remove action icons for students on files in the Chat conversation history inside a group -refs BT#21165
5901
        if (strpos($document_data['path'], 'chat_files') !== false && $document_data['filetype'] === 'file' && api_is_student()) {
5902
            $modify_icons = [];
5903
        }
5904
5905
        return implode(PHP_EOL, $modify_icons);
5906
    }
5907
5908
    /**
5909
     * @param $folders
5910
     * @param $curdirpath
5911
     * @param $move_file
5912
     * @param string $group_dir
5913
     *
5914
     * @return string
5915
     */
5916
    public static function build_move_to_selector($folders, $curdirpath, $move_file, $group_dir = '')
5917
    {
5918
        $form = new FormValidator('move_to', 'post', api_get_self().'?'.api_get_cidreq());
5919
5920
        // Form title
5921
        $form->addHidden('move_file', $move_file);
5922
5923
        $options = [];
5924
5925
        // Group documents cannot be uploaded in the root
5926
        if ($group_dir == '') {
5927
            if ($curdirpath != '/') {
5928
                $options['/'] = get_lang('Documents');
5929
            }
5930
5931
            if (is_array($folders)) {
5932
                foreach ($folders as &$folder) {
5933
                    // Hide some folders
5934
                    if ($folder == '/HotPotatoes_files' ||
5935
                        $folder == '/certificates' ||
5936
                        basename($folder) == 'css'
5937
                    ) {
5938
                        continue;
5939
                    }
5940
                    // Admin setting for Hide/Show the folders of all users
5941
                    if (api_get_setting('show_users_folders') == 'false' &&
5942
                        (strstr($folder, '/shared_folder') || strstr($folder, 'shared_folder_session_'))
5943
                    ) {
5944
                        continue;
5945
                    }
5946
5947
                    // Admin setting for Hide/Show Default folders to all users
5948
                    if (api_get_setting('show_default_folders') == 'false' &&
5949
                        (
5950
                            $folder == '/images' ||
5951
                            $folder == '/flash' ||
5952
                            $folder == '/audio' ||
5953
                            $folder == '/video' ||
5954
                            strstr($folder, '/images/gallery') ||
5955
                            $folder == '/video/flv'
5956
                        )
5957
                    ) {
5958
                        continue;
5959
                    }
5960
5961
                    // Admin setting for Hide/Show chat history folder
5962
                    if (api_get_setting('show_chat_folder') == 'false' &&
5963
                        $folder == '/chat_files') {
5964
                        continue;
5965
                    }
5966
5967
                    // You cannot move a file to:
5968
                    // 1. current directory
5969
                    // 2. inside the folder you want to move
5970
                    // 3. inside a subfolder of the folder you want to move
5971
                    if (($curdirpath != $folder) &&
5972
                        ($folder != $move_file) &&
5973
                        (substr($folder, 0, strlen($move_file) + 1) != $move_file.'/')
5974
                    ) {
5975
                        // If document title is used, we have to display titles instead of real paths...
5976
                        $path_displayed = self::get_titles_of_path($folder);
5977
                        if (empty($path_displayed)) {
5978
                            $path_displayed = get_lang('Untitled');
5979
                        }
5980
                        $options[$folder] = $path_displayed;
5981
                    }
5982
                }
5983
            }
5984
        } else {
5985
            $firstPath = '';
5986
            foreach ($folders as $folder) {
5987
                if (($curdirpath != $folder) &&
5988
                    ($folder != $move_file) &&
5989
                    (substr($folder, 0, strlen($move_file) + 1) != $move_file.'/')
5990
                ) {
5991
                    // Cannot copy dir into his own subdir
5992
                    $path_displayed = self::get_titles_of_path($folder);
5993
                    if ($firstPath == '' && $path_displayed != '') {
5994
                        $firstPath = $path_displayed;
5995
                        $group_dir = $firstPath;
5996
                    }
5997
                    $display_folder = substr($path_displayed, strlen($group_dir));
5998
                    if ($display_folder == '') {
5999
                        $display_folder = get_lang('Documents');
6000
                    }
6001
                    $options[$folder] = $display_folder;
6002
                }
6003
            }
6004
        }
6005
        $form->addElement('select', 'move_to', get_lang('MoveTo'), $options);
6006
        $form->addButtonNext(get_lang('MoveElement'), 'move_file_submit');
6007
6008
        return $form->returnForm();
6009
    }
6010
6011
    /**
6012
     * Gets the path translated with title of docs and folders.
6013
     *
6014
     * @param string $path the real path
6015
     *
6016
     * @return the path which should be displayed
6017
     */
6018
    public static function get_titles_of_path($path)
6019
    {
6020
        global $tmp_folders_titles;
6021
        $course_id = api_get_course_int_id();
6022
        $nb_slashes = substr_count($path, '/');
6023
        $current_slash_pos = 0;
6024
        $path_displayed = '';
6025
        for ($i = 0; $i < $nb_slashes; $i++) {
6026
            // For each folder of the path, retrieve title.
6027
            $current_slash_pos = strpos($path, '/', $current_slash_pos + 1);
6028
            $tmp_path = substr($path, strpos($path, '/', 0), $current_slash_pos);
6029
6030
            if (empty($tmp_path)) {
6031
                // If empty, then we are in the final part of the path
6032
                $tmp_path = $path;
6033
            }
6034
6035
            if (!empty($tmp_folders_titles[$tmp_path])) {
6036
                // If this path has soon been stored here we don't need a new query
6037
                $path_displayed .= $tmp_folders_titles[$tmp_path];
6038
            } else {
6039
                $sql = 'SELECT title FROM '.Database::get_course_table(TABLE_DOCUMENT).'
6040
                        WHERE c_id = '.$course_id.' AND path LIKE BINARY "'.$tmp_path.'"';
6041
                $rs = Database::query($sql);
6042
                $tmp_title = '/'.Database::result($rs, 0, 0);
6043
                $path_displayed .= $tmp_title;
6044
                $tmp_folders_titles[$tmp_path] = $tmp_title;
6045
            }
6046
        }
6047
6048
        return $path_displayed;
6049
    }
6050
6051
    /**
6052
     * Creates form that asks for the directory name.
6053
     *
6054
     * @return string html-output text for the form
6055
     */
6056
    public static function create_dir_form($dirId)
6057
    {
6058
        global $document_id;
6059
        $form = new FormValidator('create_dir_form', 'post', api_get_self().'?'.api_get_cidreq());
6060
        $form->addElement('hidden', 'create_dir', 1);
6061
        $form->addElement('hidden', 'dir_id', intval($document_id));
6062
        $form->addElement('hidden', 'id', intval($dirId));
6063
        $form->addElement('header', get_lang('CreateDir'));
6064
        $form->addText('dirname', get_lang('NewDir'), ['autofocus' => 'autofocus']);
6065
        $form->addButtonCreate(get_lang('CreateFolder'));
6066
6067
        return $form->returnForm();
6068
    }
6069
6070
    /**
6071
     * Checks whether the user is in shared folder.
6072
     *
6073
     * @param string $curdirpath
6074
     * @param int    $sessionId
6075
     *
6076
     * @return bool Return true when user is into shared folder
6077
     */
6078
    public static function is_shared_folder($curdirpath, $sessionId)
6079
    {
6080
        $clean_curdirpath = Security::remove_XSS($curdirpath);
6081
        if ($clean_curdirpath == '/shared_folder') {
6082
            return true;
6083
        } elseif ($clean_curdirpath == '/shared_folder_session_'.$sessionId) {
6084
            return true;
6085
        }
6086
6087
        return false;
6088
    }
6089
6090
    /**
6091
     * Checks whether the user is into any user shared folder.
6092
     *
6093
     * @param string $path
6094
     * @param int    $sessionId
6095
     *
6096
     * @return bool Return true when user is in any user shared folder
6097
     */
6098
    public static function is_any_user_shared_folder($path, $sessionId)
6099
    {
6100
        $clean_path = Security::remove_XSS($path);
6101
        if (strpos($clean_path, 'shared_folder/sf_user_')) {
6102
            return true;
6103
        } elseif (strpos($clean_path, 'shared_folder_session_'.$sessionId.'/sf_user_')) {
6104
            return true;
6105
        }
6106
6107
        return false;
6108
    }
6109
6110
    /**
6111
     * Create users shared folder for course.
6112
     *
6113
     * @param int $userId
6114
     * @param int $sessionId
6115
     */
6116
    public static function createUserSharedFolder($userId, array $courseInfo, $sessionId = 0)
6117
    {
6118
        $documentDirectory = api_get_path(SYS_COURSE_PATH).$courseInfo['directory'].'/document';
6119
        $userInfo = api_get_user_info($userId);
6120
6121
        if (!$sessionId) {
6122
            //Create shared folder. Necessary for recycled courses.
6123
            if (!file_exists($documentDirectory.'/shared_folder')) {
6124
                create_unexisting_directory(
6125
                    $courseInfo,
6126
                    $userId,
6127
                    0,
6128
                    0,
6129
                    0,
6130
                    $documentDirectory,
6131
                    '/shared_folder',
6132
                    get_lang('UserFolders'),
6133
                    0,
6134
                    false,
6135
                    false
6136
                );
6137
            }
6138
            // Create dynamic user shared folder
6139
            if (!file_exists($documentDirectory.'/shared_folder/sf_user_'.$userId)) {
6140
                create_unexisting_directory(
6141
                    $courseInfo,
6142
                    $userId,
6143
                    0,
6144
                    0,
6145
                    0,
6146
                    $documentDirectory,
6147
                    '/shared_folder/sf_user_'.$userId,
6148
                    $userInfo['complete_name'],
6149
                    1,
6150
                    false,
6151
                    false
6152
                );
6153
            }
6154
6155
            return;
6156
        }
6157
6158
        // Create shared folder session.
6159
        if (!file_exists($documentDirectory.'/shared_folder_session_'.$sessionId)) {
6160
            create_unexisting_directory(
6161
                $courseInfo,
6162
                api_get_user_id(),
6163
                $sessionId,
6164
                0,
6165
                0,
6166
                $documentDirectory,
6167
                '/shared_folder_session_'.$sessionId,
6168
                get_lang('UserFolders').' ('.api_get_session_name($sessionId).')',
6169
                0,
6170
                false,
6171
                false
6172
            );
6173
        }
6174
        //Create dynamic user shared folder into a shared folder session
6175
        if (!file_exists($documentDirectory.'/shared_folder_session_'.$sessionId.'/sf_user_'.$userId)) {
6176
            create_unexisting_directory(
6177
                $courseInfo,
6178
                $userId,
6179
                $sessionId,
6180
                0,
6181
                0,
6182
                $documentDirectory,
6183
                '/shared_folder_session_'.$sessionId.'/sf_user_'.$userId,
6184
                $userInfo['complete_name'].'('.api_get_session_name($sessionId).')',
6185
                1,
6186
                false,
6187
                false
6188
            );
6189
        }
6190
    }
6191
6192
    /**
6193
     * Checks whether the user is into his shared folder or into a subfolder.
6194
     *
6195
     * @param int    $user_id
6196
     * @param string $path
6197
     * @param int    $sessionId
6198
     *
6199
     * @return bool Return true when user is in his user shared folder or into a subfolder
6200
     */
6201
    public static function is_my_shared_folder($user_id, $path, $sessionId)
6202
    {
6203
        $clean_path = Security::remove_XSS($path).'/';
6204
        $user_id = (int) $user_id;
6205
        //for security does not remove the last slash
6206
        $main_user_shared_folder = '/shared_folder\/sf_user_'.$user_id.'\//';
6207
        //for security does not remove the last slash
6208
        $main_user_shared_folder_session = '/shared_folder_session_'.$sessionId.'\/sf_user_'.$user_id.'\//';
6209
6210
        if (preg_match($main_user_shared_folder, $clean_path)) {
6211
            return true;
6212
        } elseif (preg_match($main_user_shared_folder_session, $clean_path)) {
6213
            return true;
6214
        } else {
6215
            return false;
6216
        }
6217
    }
6218
6219
    public static function isBasicCourseFolder($path, $sessionId)
6220
    {
6221
        $cleanPath = Security::remove_XSS($path);
6222
        $basicCourseFolder = '/basic-course-documents__'.$sessionId.'__0';
6223
6224
        return $cleanPath == $basicCourseFolder;
6225
    }
6226
6227
    /**
6228
     * Check if the file name or folder searched exist.
6229
     *
6230
     * @return bool Return true when exist
6231
     */
6232
    public static function searchKeyword($name, $keyword)
6233
    {
6234
        if (api_strripos($name, $keyword) !== false) {
6235
            return true;
6236
        }
6237
6238
        return false;
6239
    }
6240
6241
    /**
6242
     * Checks whether a document can be previewed by using the browser.
6243
     *
6244
     * @param string $file_extension the filename extension of the document (it must be in lower case)
6245
     *
6246
     * @return bool returns TRUE or FALSE
6247
     */
6248
    public static function isBrowserViewable($file_extension)
6249
    {
6250
        static $allowed_extensions = [
6251
            'htm', 'html', 'xhtml',
6252
            'gif', 'jpg', 'jpeg', 'png', 'tif', 'tiff',
6253
            'pdf', 'svg', 'swf',
6254
            'txt', 'log',
6255
            'mp4', 'ogg', 'ogv', 'ogx', 'mpg', 'mpeg', 'mov', 'avi', 'webm', 'wmv',
6256
            'mp3', 'oga', 'wav', 'au', 'wma', 'mid', 'kar',
6257
        ];
6258
6259
        /*
6260
          //TODO: make a admin switch to strict mode
6261
          1. global default $allowed_extensions
6262
          if (in_array($file_extension, $allowed_extensions)) { // Assignment + a logical check.
6263
          return true;
6264
          }
6265
          2. check native support
6266
          3. check plugins: quicktime, mediaplayer, vlc, acrobat, flash, java
6267
         */
6268
6269
        if (!($result = in_array($file_extension, $allowed_extensions))) {
6270
            // Assignment + a logical check.
6271
            return false;
6272
        }
6273
6274
        //check native support (Explorer, Opera, Firefox, Chrome, Safari)
6275
        if ($file_extension == "pdf") {
6276
            return api_browser_support('pdf');
6277
        } elseif ($file_extension == "mp3") {
6278
            return api_browser_support('mp3');
6279
        } elseif ($file_extension == "mp4") {
6280
            return api_browser_support('mp4');
6281
        } elseif ($file_extension == "ogg" || $file_extension == "ogx" || $file_extension == "ogv" || $file_extension == "oga") {
6282
            return api_browser_support('ogg');
6283
        } elseif ($file_extension == "svg") {
6284
            return api_browser_support('svg');
6285
        } elseif ($file_extension == "mpg" || $file_extension == "mpeg") {
6286
            return api_browser_support('mpg');
6287
        } elseif ($file_extension == "mov") {
6288
            return api_browser_support('mov');
6289
        } elseif ($file_extension == "wav") {
6290
            return api_browser_support('wav');
6291
        } elseif ($file_extension == "mid" || $file_extension == "kar") {
6292
            return api_browser_support('mid');
6293
        } elseif ($file_extension == "avi") {
6294
            return api_browser_support('avi');
6295
        } elseif ($file_extension == "wma") {
6296
            return api_browser_support('wma');
6297
        } elseif ($file_extension == "wmv") {
6298
            return api_browser_support('wmv');
6299
        } elseif ($file_extension == "tif" || $file_extension == "tiff") {
6300
            return api_browser_support('tif');
6301
        } elseif ($file_extension == "mov") {
6302
            return api_browser_support('mov');
6303
        } elseif ($file_extension == "au") {
6304
            return api_browser_support('au');
6305
        } elseif ($file_extension == "webm") {
6306
            return api_browser_support('webm');
6307
        }
6308
6309
        return $result;
6310
    }
6311
6312
    /**
6313
     * @param array $courseInfo
6314
     * @param int   $sessionId
6315
     *
6316
     * @return array
6317
     */
6318
    public static function getDeletedDocuments($courseInfo, $sessionId = 0)
6319
    {
6320
        $table = Database::get_course_table(TABLE_DOCUMENT);
6321
        $courseId = $courseInfo['real_id'];
6322
        $sessionCondition = api_get_session_condition($sessionId);
6323
        $sql = "SELECT * FROM $table
6324
                WHERE
6325
                  path LIKE '%DELETED%' AND
6326
                  c_id = $courseId
6327
                  $sessionCondition
6328
                ORDER BY path
6329
        ";
6330
6331
        $result = Database::query($sql);
6332
        $files = [];
6333
        while ($document = Database::fetch_array($result, 'ASSOC')) {
6334
            $files[] = $document;
6335
        }
6336
6337
        return $files;
6338
    }
6339
6340
    /**
6341
     * @param int   $id
6342
     * @param array $courseInfo
6343
     * @param int   $sessionId
6344
     *
6345
     * @return array
6346
     */
6347
    public static function getDeletedDocument($id, $courseInfo, $sessionId = 0)
6348
    {
6349
        if (empty($courseInfo)) {
6350
            return false;
6351
        }
6352
6353
        $table = Database::get_course_table(TABLE_DOCUMENT);
6354
        $courseId = $courseInfo['real_id'];
6355
        $id = (int) $id;
6356
        $sessionCondition = api_get_session_condition($sessionId);
6357
        $sql = "SELECT * FROM $table
6358
                WHERE
6359
                  path LIKE '%DELETED%' AND
6360
                  id = $id AND
6361
                  c_id = $courseId
6362
                  $sessionCondition
6363
                LIMIT 1
6364
        ";
6365
        $result = Database::query($sql);
6366
        if (Database::num_rows($result)) {
6367
            $result = Database::fetch_array($result, 'ASSOC');
6368
6369
            return $result;
6370
        }
6371
6372
        return [];
6373
    }
6374
6375
    /**
6376
     * @param int   $id
6377
     * @param array $courseInfo
6378
     * @param int   $sessionId
6379
     *
6380
     * @return bool
6381
     */
6382
    public static function purgeDocument($id, $courseInfo, $sessionId = 0)
6383
    {
6384
        $document = self::getDeletedDocument($id, $courseInfo, $sessionId);
6385
        if (!empty($document)) {
6386
            $path = $document['path'];
6387
            $coursePath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/';
6388
            my_delete($coursePath.$path);
6389
            // Hard delete.
6390
            self::deleteDocumentFromDb($id, $courseInfo, $sessionId, true);
6391
6392
            return true;
6393
        }
6394
6395
        return false;
6396
    }
6397
6398
    /**
6399
     * @param array $courseInfo
6400
     * @param int   $sessionId
6401
     */
6402
    public static function purgeDocuments($courseInfo, $sessionId)
6403
    {
6404
        $files = self::getDeletedDocuments($courseInfo, $sessionId);
6405
        foreach ($files as $file) {
6406
            self::purgeDocument($file['id'], $courseInfo, $sessionId);
6407
        }
6408
    }
6409
6410
    /**
6411
     * @param int   $id
6412
     * @param array $courseInfo
6413
     * @param int   $sessionId
6414
     */
6415
    public static function downloadDeletedDocument($id, $courseInfo, $sessionId)
6416
    {
6417
        $document = self::getDeletedDocument($id, $courseInfo, $sessionId);
6418
        if (!empty($document)) {
6419
            $coursePath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/';
6420
6421
            if (Security::check_abs_path($coursePath.$document['path'], $coursePath)) {
6422
                self::file_send_for_download($coursePath.$document['path'], true);
6423
                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...
6424
            }
6425
        }
6426
    }
6427
6428
    /**
6429
     * @param array $courseInfo
6430
     * @param int   $sessionId
6431
     *
6432
     * @return bool
6433
     */
6434
    public static function downloadAllDeletedDocument($courseInfo, $sessionId)
6435
    {
6436
        $files = self::getDeletedDocuments($courseInfo, $sessionId);
6437
6438
        if (empty($files)) {
6439
            return false;
6440
        }
6441
6442
        $coursePath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
6443
6444
        // Creating a ZIP file.
6445
        $tempZipFile = api_get_path(SYS_ARCHIVE_PATH).api_get_unique_id().'.zip';
6446
        $zip = new PclZip($tempZipFile);
6447
        foreach ($files as $file) {
6448
            $zip->add(
6449
                $coursePath.$file['path'],
6450
                PCLZIP_OPT_REMOVE_PATH,
6451
                $coursePath
6452
            );
6453
        }
6454
6455
        if (Security::check_abs_path($tempZipFile, api_get_path(SYS_ARCHIVE_PATH))) {
6456
            self::file_send_for_download($tempZipFile, true);
6457
            @unlink($tempZipFile);
6458
            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...
6459
        }
6460
    }
6461
6462
    /**
6463
     * Delete documents from a session in a course.
6464
     *
6465
     * @param array $courseInfo
6466
     * @param int   $sessionId
6467
     *
6468
     * @return bool
6469
     */
6470
    public static function deleteDocumentsFromSession($courseInfo, $sessionId)
6471
    {
6472
        if (empty($courseInfo)) {
6473
            return false;
6474
        }
6475
6476
        if (empty($sessionId)) {
6477
            return false;
6478
        }
6479
6480
        $itemPropertyTable = Database::get_course_table(TABLE_ITEM_PROPERTY);
6481
        $documentTable = Database::get_course_table(TABLE_DOCUMENT);
6482
6483
        $conditionSession = api_get_session_condition($sessionId, true, false, 'd.session_id');
6484
        $courseId = $courseInfo['real_id'];
6485
6486
        // get invisible folders
6487
        $sql = "SELECT DISTINCT d.id, path
6488
                FROM $itemPropertyTable i
6489
                INNER JOIN $documentTable d
6490
                ON (i.c_id = d.c_id)
6491
                WHERE
6492
                    d.id = i.ref AND
6493
                    i.tool = '".TOOL_DOCUMENT."'
6494
                    $conditionSession AND
6495
                    i.c_id = $courseId AND
6496
                    d.c_id = $courseId ";
6497
6498
        $result = Database::query($sql);
6499
        $documents = Database::store_result($result, 'ASSOC');
6500
        if ($documents) {
6501
            $course_dir = $courseInfo['directory'].'/document';
6502
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
6503
            $base_work_dir = $sys_course_path.$course_dir;
6504
6505
            foreach ($documents as $document) {
6506
                $documentId = $document['id'];
6507
                self::delete_document(
6508
                    $courseInfo,
6509
                    null,
6510
                    $base_work_dir,
6511
                    $sessionId,
6512
                    $documentId
6513
                );
6514
            }
6515
        }
6516
6517
        $sql = "DELETE FROM $documentTable
6518
                WHERE c_id = $courseId AND session_id = $sessionId";
6519
        Database::query($sql);
6520
6521
        $sql = "DELETE FROM $itemPropertyTable
6522
                WHERE c_id = $courseId AND session_id = $sessionId AND tool = '".TOOL_DOCUMENT."'";
6523
        Database::query($sql);
6524
    }
6525
6526
    /**
6527
     * Update the file or directory path in the document db document table.
6528
     *
6529
     * @author - Hugues Peeters <[email protected]>
6530
     *
6531
     * @param string $action     - action type require : 'delete' or 'update'
6532
     * @param string $old_path   - old path info stored to change
6533
     * @param string $new_path   - new path info to substitute
6534
     * @param int    $documentId - iid of specific document
6535
     *
6536
     * @desc Update the file or directory path in the document db document table
6537
     */
6538
    public static function updateDbInfo($action, $old_path, $new_path = '', $documentId = 0)
6539
    {
6540
        $documentId = (int) $documentId;
6541
        $dbTable = Database::get_course_table(TABLE_DOCUMENT);
6542
        $course_id = api_get_course_int_id();
6543
        $old_path = Database::escape_string($old_path);
6544
        switch ($action) {
6545
            case 'delete':
6546
                $query = "DELETE FROM $dbTable
6547
                          WHERE
6548
                            c_id = $course_id AND
6549
                            (
6550
                                path LIKE BINARY '".$old_path."' OR
6551
                                path LIKE BINARY '".$old_path."/%'
6552
                            )";
6553
                Database::query($query);
6554
                break;
6555
            case 'update':
6556
                if ($new_path[0] == '.') {
6557
                    $new_path = substr($new_path, 1);
6558
                }
6559
                $new_path = str_replace('//', '/', $new_path);
6560
6561
                // Attempt to update	- tested & working for root	dir
6562
                $new_path = Database::escape_string($new_path);
6563
                $query = "UPDATE $dbTable SET
6564
                            path = CONCAT('".$new_path."', SUBSTRING(path, LENGTH('".$old_path."')+1) )
6565
                          WHERE
6566
                                c_id = $course_id AND
6567
                                (path LIKE BINARY '".$old_path."' OR path LIKE BINARY '".$old_path."/%')";
6568
                if ($documentId != 0) {
6569
                    $query .= " AND iid = $documentId";
6570
                }
6571
                Database::query($query);
6572
                break;
6573
        }
6574
    }
6575
6576
    /**
6577
     * This function calculates the resized width and resized heigt
6578
     * according to the source and target widths
6579
     * and heights, height so that no distortions occur
6580
     * parameters.
6581
     *
6582
     * @param $image = the absolute path to the image
0 ignored issues
show
Documentation Bug introduced by
The doc comment = at position 0 could not be parsed: Unknown type name '=' at position 0 in =.
Loading history...
6583
     * @param $target_width = how large do you want your resized image
6584
     * @param $target_height = how large do you want your resized image
6585
     * @param $slideshow (default=0) =
6586
     *      indicates weither we are generating images for a slideshow or not,
6587
     *		this overrides the $_SESSION["image_resizing"] a bit so that a thumbnail
6588
     *	    view is also possible when you choose not to resize the source images
6589
     *
6590
     * @return array
6591
     */
6592
    public static function resizeImageSlideShow(
6593
        $image,
6594
        $target_width,
6595
        $target_height,
6596
        $slideshow = 0
6597
    ) {
6598
        // Modifications by Ivan Tcholakov, 04-MAY-2009.
6599
        $result = [];
6600
        $imageResize = Session::read('image_resizing');
6601
        if ($imageResize == 'resizing' || $slideshow == 1) {
6602
            $new_sizes = api_resize_image($image, $target_width, $target_height);
6603
            $result[] = $new_sizes['height'];
6604
            $result[] = $new_sizes['width'];
6605
        } else {
6606
            $size = api_getimagesize($image);
6607
            $result[] = $size['height'];
6608
            $result[] = $size['width'];
6609
        }
6610
6611
        return $result;
6612
    }
6613
6614
    /**
6615
     * Calculates the total size of a directory by adding the sizes (that
6616
     * are stored in the database) of all files & folders in this directory.
6617
     *
6618
     * @param string $value           Document path or document id
6619
     * @param bool   $canSeeInvisible
6620
     * @param bool   $byId            Default true, if is getting size by document id or false if getting by path
6621
     *
6622
     * @return int Total size
6623
     */
6624
    public static function getTotalFolderSize($value, $canSeeInvisible = false, $byId = true)
6625
    {
6626
        $table_itemproperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
6627
        $table_document = Database::get_course_table(TABLE_DOCUMENT);
6628
        $tool_document = TOOL_DOCUMENT;
6629
6630
        $course_id = api_get_course_int_id();
6631
        $session_id = api_get_session_id();
6632
        $session_condition = api_get_session_condition(
6633
            $session_id,
6634
            true,
6635
            true,
6636
            'props.session_id'
6637
        );
6638
6639
        if (empty($course_id)) {
6640
            return 0;
6641
        }
6642
6643
        $visibility_rule = ' props.visibility '.($canSeeInvisible ? '<> 2' : '= 1');
6644
6645
        if ($byId) {
6646
            $id = (int) $value;
6647
            $query = "SELECT path FROM $table_document WHERE id = $id";
6648
            $result1 = Database::query($query);
6649
            if ($result1 && Database::num_rows($result1) != 0) {
6650
                $row = Database::fetch_row($result1);
6651
                $path = $row[0];
6652
            } else {
6653
                return 0;
6654
            }
6655
        } else {
6656
            $path = Database::escape_string($value);
6657
        }
6658
6659
        $sql = "SELECT SUM(table1.size) FROM (
6660
                SELECT props.ref, size
6661
                FROM $table_itemproperty AS props
6662
                INNER JOIN $table_document AS docs
6663
                ON (docs.id = props.ref AND docs.c_id = props.c_id)
6664
                WHERE
6665
                    docs.c_id = $course_id AND
6666
                    docs.path LIKE '$path/%' AND
6667
                    props.c_id = $course_id AND
6668
                    props.tool = '$tool_document' AND
6669
                    $visibility_rule AND
6670
                    props.ref not in (
6671
                        SELECT ref
6672
                        FROM $table_itemproperty as cip
6673
                        WHERE
6674
                            cip.c_id = $course_id AND
6675
                            cip.tool = '$tool_document' AND
6676
                            cip.visibility = 2
6677
                    )
6678
                    $session_condition
6679
                GROUP BY ref
6680
            ) as table1";
6681
6682
        $result = Database::query($sql);
6683
        if ($result && Database::num_rows($result) != 0) {
6684
            $row = Database::fetch_row($result);
6685
6686
            return $row[0] == null ? 0 : $row[0];
6687
        } else {
6688
            return 0;
6689
        }
6690
    }
6691
6692
    /**
6693
     * Adds a cloud link to the database.
6694
     *
6695
     * @author - Aquilino Blanco Cores <[email protected]>
6696
     *
6697
     * @param array  $_course
6698
     * @param string $path
6699
     * @param string $url
6700
     * @param string $name
6701
     *
6702
     * @return int id of document or 0 if already exists or there was a problem creating it
6703
     */
6704
    public static function addCloudLink($_course, $path, $url, $name)
6705
    {
6706
        $file_path = $path;
6707
        if (!self::cloudLinkExists($_course, $path, $url)) {
6708
            $doc_id = add_document($_course, $file_path, 'link', 0, $name, $url);
6709
            if ($doc_id) {
6710
                // Update document item_property
6711
                api_item_property_update(
6712
                    $_course,
6713
                    TOOL_DOCUMENT,
6714
                    $doc_id,
6715
                    'DocumentAdded',
6716
                    api_get_user_id(),
6717
                    api_get_group_id(),
6718
                    api_get_user_id(),
6719
                    null,
6720
                    null,
6721
                    api_get_session_id()
6722
                );
6723
            }
6724
6725
            // If the file is in a folder, we need to update all parent folders
6726
            item_property_update_on_folder($_course, $file_path, api_get_user_id());
6727
6728
            return $doc_id;
6729
        } else {
6730
            return 0;
6731
        }
6732
    }
6733
6734
    /**
6735
     * Deletes a cloud link from the database.
6736
     *
6737
     * @author - Aquilino Blanco Cores <[email protected]>
6738
     *
6739
     * @param array  $courseInfo
6740
     * @param string $documentId
6741
     *
6742
     * @return bool true if success / false if an error occurred
6743
     */
6744
    public static function deleteCloudLink($courseInfo, $documentId)
6745
    {
6746
        if (empty($documentId) || empty($courseInfo)) {
6747
            return false;
6748
        }
6749
6750
        $documentId = (int) $documentId;
6751
        $fileDeletedFromDb = false;
6752
        if (!empty($documentId)) {
6753
            self::deleteDocumentFromDb($documentId, $courseInfo, 0, true);
6754
            // checking
6755
            $table = Database::get_course_table(TABLE_DOCUMENT);
6756
            $courseId = $courseInfo['real_id'];
6757
            $sql = "SELECT * FROM $table WHERE id = $documentId AND c_id = $courseId";
6758
            $result = Database::query($sql);
6759
            $exists = Database::num_rows($result) > 0;
6760
            $fileDeletedFromDb = !$exists;
6761
        }
6762
6763
        return $fileDeletedFromDb;
6764
    }
6765
6766
    /**
6767
     * Gets the id of a cloud link with a given path.
6768
     *
6769
     * @author - Aquilino Blanco Cores <[email protected]>
6770
     *
6771
     * @param array  $courseInfo
6772
     * @param string $path
6773
     * @param string $url
6774
     *
6775
     * @return int link's id / false if no link found
6776
     */
6777
    public static function getCloudLinkId($courseInfo, $path, $url)
6778
    {
6779
        $table = Database::get_course_table(TABLE_DOCUMENT);
6780
6781
        if (empty($courseInfo)) {
6782
            return false;
6783
        }
6784
6785
        $courseId = (int) $courseInfo['real_id'];
6786
        $path = Database::escape_string($path);
6787
6788
        if (substr($path, -1) != '/') {
6789
            // Add final slash to path if not present
6790
            $path .= '/';
6791
        }
6792
6793
        if (!empty($courseId) && !empty($path)) {
6794
            $sql = "SELECT id FROM $table
6795
                    WHERE
6796
                        c_id = $courseId AND
6797
                        path LIKE BINARY '$path' AND
6798
                        comment = '$url' AND
6799
                        filetype = 'link'
6800
                    LIMIT 1";
6801
            $result = Database::query($sql);
6802
            if ($result && Database::num_rows($result)) {
6803
                $row = Database::fetch_array($result);
6804
6805
                return intval($row[0]);
6806
            }
6807
        }
6808
6809
        return false;
6810
    }
6811
6812
    /**
6813
     * Checks if a cloud link exists.
6814
     *
6815
     * @author - Aquilino Blanco Cores <[email protected]>
6816
     *
6817
     * @param array  $courseInfo
6818
     * @param string $path
6819
     * @param string $url
6820
     *
6821
     * @return bool true if it exists false in other case
6822
     */
6823
    public static function cloudLinkExists($courseInfo, $path, $url)
6824
    {
6825
        $exists = self::getCloudLinkId($courseInfo, $path, $url);
6826
6827
        return $exists;
6828
    }
6829
6830
    /**
6831
     * Gets the wellformed URLs regular expression in order to use it on forms' verifications.
6832
     *
6833
     * @author Aquilino Blanco Cores <[email protected]>
6834
     *
6835
     * @return string the well formed URLs regular expressions string
6836
     */
6837
    public static function getWellFormedUrlRegex()
6838
    {
6839
        return '/\(?((http|https|ftp):\/\/)(?:((?:[^\W\s]|\.|-|[:]{1})+)@{1})?((?:www.)?(?:[^\W\s]|\.|-)+[\.][^\W\s]{2,4}|localhost(?=\/)|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?::(\d*))?([\/]?[^\s\?]*[\/]{1})*(?:\/?([^\s\n\?\[\]\{\}\#]*(?:(?=\.)){1}|[^\s\n\?\[\]\{\}\.\#]*)?([\.]{1}[^\s\?\#]*)?)?(?:\?{1}([^\s\n\#\[\]]*))?([\#][^\s\n]*)?\)?/i';
6840
    }
6841
6842
    /**
6843
     * Gets the files hosting sites' whitelist.
6844
     *
6845
     * @author Aquilino Blanco Cores <[email protected]>
6846
     *
6847
     * @return array the sites list
6848
     */
6849
    public static function getFileHostingWhiteList()
6850
    {
6851
        $links = api_get_configuration_value('documents_custom_cloud_link_list');
6852
6853
        if (!empty($links) && isset($links['links'])) {
6854
            return $links['links'];
6855
        }
6856
6857
        return [
6858
            'asuswebstorage.com',
6859
            'box.com',
6860
            'dropbox.com',
6861
            'dropboxusercontent.com',
6862
            'docs.google.com',
6863
            'drive.google.com',
6864
            'fileserve.com',
6865
            'icloud.com',
6866
            'livefilestore.com', // OneDrive
6867
            'mediafire.com',
6868
            'mega.nz',
6869
            'onedrive.live.com',
6870
            'scribd.com',
6871
            'slideshare.net',
6872
            'sharepoint.com',
6873
            'wetransfer.com',
6874
        ];
6875
    }
6876
6877
    /**
6878
     * @param int $userId
6879
     *
6880
     * @return array Example [ 0 => ['code' => 'ABC', 'directory' => 'ABC0', 'path' => '/images/gallery/test.png', 'code_path' => 'ABC:/images/gallery/test.png'], 1 => ...]
6881
     */
6882
    public static function getAllDocumentsCreatedByUser($userId)
6883
    {
6884
        $tblItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
6885
        $tblDocument = Database::get_course_table(TABLE_DOCUMENT);
6886
        $tblCourse = Database::get_main_table(TABLE_MAIN_COURSE);
6887
        $userId = (int) $userId;
6888
6889
        $sql = "SELECT DISTINCT c.code, c.directory, docs.path
6890
                FROM $tblItemProperty AS last
6891
                INNER JOIN $tblDocument AS docs
6892
                ON (
6893
                    docs.id = last.ref AND
6894
                    docs.c_id = last.c_id AND
6895
                    docs.filetype <> 'folder'
6896
                )
6897
                INNER JOIN $tblCourse as c
6898
                ON (
6899
                    docs.c_id = c.id
6900
                )
6901
                WHERE
6902
                    last.tool = '".TOOL_DOCUMENT."' AND
6903
                    last.insert_user_id = $userId AND
6904
                    docs.path NOT LIKE '%_DELETED_%'
6905
                ORDER BY c.directory, docs.path
6906
                ";
6907
        $result = Database::query($sql);
6908
6909
        $list = [];
6910
        if (Database::num_rows($result) != 0) {
6911
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6912
                $row['code_path'] = $row['code'].':'.$row['path'];
6913
                $list[] = $row;
6914
            }
6915
        }
6916
6917
        return $list;
6918
    }
6919
6920
    /**
6921
     * Writes the content of a sent file to an existing one in the system, backing up the previous one.
6922
     *
6923
     * @param array  $_course
6924
     * @param string $path          Path stored in the database
6925
     * @param string $base_work_dir Path to the documents folder (if not defined, $documentId must be used)
6926
     * @param int    $sessionId     The ID of the session, if any
6927
     * @param int    $documentId    The document id, if available
6928
     * @param int    $groupId       iid
6929
     * @param file   $file          $_FILES content
6930
     *
6931
     * @return bool true/false
6932
     */
6933
    public static function writeContentIntoDocument(
6934
        $_course,
6935
        $path = null,
6936
        $base_work_dir = null,
6937
        $sessionId = null,
6938
        $documentId = null,
6939
        $groupId = 0,
6940
        $file = null
6941
    ) {
6942
        $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
6943
6944
        $documentId = (int) $documentId;
6945
        $groupId = (int) $groupId;
6946
        if (empty($groupId)) {
6947
            $groupId = api_get_group_id();
6948
        }
6949
6950
        $sessionId = (int) $sessionId;
6951
        if (empty($sessionId)) {
6952
            $sessionId = api_get_session_id();
6953
        }
6954
6955
        $course_id = $_course['real_id'];
6956
6957
        if (empty($course_id)) {
6958
            Display::addFlash(Display::return_message(get_lang('NoCourse'), 'error'));
6959
6960
            return false;
6961
        }
6962
6963
        if (empty($base_work_dir)) {
6964
            Display::addFlash(get_lang('Path'));
6965
6966
            return false;
6967
        }
6968
6969
        if (isset($file) && $file['error'] == 4) {
6970
            //no file
6971
            Display::addFlash(Display::return_message(get_lang('NoArchive'), 'error'));
6972
6973
            return false;
6974
        }
6975
6976
        if (empty($documentId)) {
6977
            $documentId = self::get_document_id($_course, $path, $sessionId);
6978
            $docInfo = self::get_document_data_by_id(
6979
                $documentId,
6980
                $_course['code'],
6981
                false,
6982
                $sessionId
6983
            );
6984
            $path = $docInfo['path'];
6985
        } else {
6986
            $docInfo = self::get_document_data_by_id(
6987
                $documentId,
6988
                $_course['code'],
6989
                false,
6990
                $sessionId
6991
            );
6992
            if (empty($docInfo)) {
6993
                Display::addFlash(Display::return_message(get_lang('ArchiveName'), 'error'));
6994
6995
                return false;
6996
            }
6997
            $path = $docInfo['path'];
6998
        }
6999
7000
        if (empty($path) || empty($docInfo) || empty($documentId)) {
7001
            $str = '';
7002
            $str .= "<br>".get_lang('Path');
7003
            $str .= "<br>".get_lang('ArchiveName');
7004
            $str .= "<br>".get_lang('NoFileSpecified');
7005
            Display::addFlash(Display::return_message($str, 'error'));
7006
7007
            return false;
7008
        }
7009
7010
        $itemInfo = api_get_item_property_info(
7011
            $_course['real_id'],
7012
            TOOL_DOCUMENT,
7013
            $documentId,
7014
            $sessionId,
7015
            $groupId
7016
        );
7017
7018
        if (empty($itemInfo)) {
7019
            Display::addFlash(Display::return_message(get_lang('NoFileSpecified'), 'error'));
7020
7021
            return false;
7022
        }
7023
7024
        // Filtering by group.
7025
        if ($itemInfo['to_group_id'] != $groupId) {
7026
            Display::addFlash(Display::return_message(get_lang("NoGroupsAvailable"), 'error'));
7027
7028
            return false;
7029
        }
7030
        $now = new DateTime();
7031
        $now = $now->format('Y_m_d__H_i_s_');
7032
7033
        $document_exists_in_disk = file_exists($base_work_dir.$path);
7034
        $new_path = $path.'_REPLACED_DATE_'.$now.'_ID_'.$documentId;
7035
7036
        $fileMoved = false;
7037
        $file_renamed_from_disk = false;
7038
7039
        $originalMime = self::file_get_mime_type($base_work_dir.$path);
7040
        $newMime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $file['tmp_name']);
7041
        if ($originalMime != $newMime) {
7042
            Display::addFlash(Display::return_message(get_lang('FileError'), 'error'));
7043
7044
            return false;
7045
        }
7046
7047
        if ($document_exists_in_disk) {
7048
            // Move old file to xxx_REPLACED_DATE_#date_ID_#id (soft delete)
7049
            if (is_file($base_work_dir.$path) || is_dir($base_work_dir.$path)) {
7050
                if (rename($base_work_dir.$path, $base_work_dir.$new_path)) {
7051
                    $file_renamed_from_disk = true;
7052
                } else {
7053
                    // Couldn't rename - file permissions problem?
7054
                    Display::addFlash(Display::return_message(get_lang('ErrorImportingFile'), 'error'));
7055
                    error_log(
7056
                        __FILE__.' '.__LINE__.': Error renaming '.$base_work_dir.$path.' to '
7057
                        .$base_work_dir.$new_path.'. This is probably due to file permissions',
7058
                        0
7059
                    );
7060
                }
7061
            }
7062
7063
            if (move_uploaded_file($file['tmp_name'], $base_work_dir.$path)) {
7064
                //update file size into db
7065
                $size = filesize($base_work_dir.$path);
7066
                $sql = "UPDATE $TABLE_DOCUMENT
7067
                                SET size = '".$size."'
7068
                                WHERE
7069
                                    c_id = $course_id AND
7070
                                    session_id = $sessionId AND
7071
                                    id = ".$documentId;
7072
                Database::query($sql);
7073
                $fileMoved = true;
7074
            }
7075
        }
7076
        // Checking inconsistency
7077
        if ($fileMoved &&
7078
            $file_renamed_from_disk
7079
        ) {
7080
            return true;
7081
        } else {
7082
            Display::addFlash(Display::return_message(get_lang('ErrorImportingFile'), 'warning'));
7083
            //Something went wrong
7084
            //The file or directory isn't there anymore (on the filesystem)
7085
            // This means it has been removed externally. To prevent a
7086
            // blocking error from happening, we drop the related items from the
7087
            // item_property and the document table.
7088
            error_log(
7089
                __FILE__.' '.__LINE__.': System inconsistency detected. '.
7090
                'The file or directory '.$base_work_dir.$path.
7091
                ' seems to have been removed from the filesystem independently from '.
7092
                'the web platform. To restore consistency, the elements using the same '.
7093
                'path will be removed from the database',
7094
                0
7095
            );
7096
7097
            return false;
7098
        }
7099
    }
7100
7101
    /**
7102
     * It gest extra value to define if download icon is visible or not.
7103
     *
7104
     * @param $documentId
7105
     *
7106
     * @return bool
7107
     */
7108
    public static function getHideDownloadIcon($documentId)
7109
    {
7110
        $extraFieldValue = new ExtraFieldValue('document');
7111
        $extraValue = $extraFieldValue->get_values_by_handler_and_field_variable($documentId, 'can_be_downloaded');
7112
        $canBeDownloadedIcon = false;
7113
        if (!empty($extraValue) && isset($extraValue['value'])) {
7114
            $canBeDownloadedIcon = (bool) $extraValue['value'];
7115
        }
7116
7117
        return $canBeDownloadedIcon;
7118
    }
7119
7120
    /**
7121
     * Retrieves all documents in a course by their parent folder ID.
7122
     *
7123
     * @param array    $courseInfo      Information about the course.
7124
     * @param int      $parentId        The ID of the parent folder.
7125
     * @param int      $toGroupId       (Optional) The ID of the group to filter by. Default is 0.
7126
     * @param int|null $toUserId        (Optional) The ID of the user to filter by. Default is null.
7127
     * @param bool     $canSeeInvisible (Optional) Whether to include invisible documents. Default is false.
7128
     * @param bool     $search          (Optional) Whether to perform a search or fetch all documents. Default is true.
7129
     * @param int      $sessionId       (Optional) The session ID to filter by. Default is 0.
7130
     *
7131
     * @return array List of documents that match the criteria.
7132
     */
7133
    public static function getAllDocumentsByParentId(
7134
        $courseInfo,
7135
        $parentId,
7136
        $toGroupId = 0,
7137
        $toUserId = null,
7138
        $canSeeInvisible = false,
7139
        $search = true,
7140
        $sessionId = 0
7141
    ) {
7142
        if (empty($courseInfo)) {
7143
            return [];
7144
        }
7145
7146
        $tblDocument = Database::get_course_table(TABLE_DOCUMENT);
7147
7148
        $parentId = (int) $parentId;
7149
7150
        $sql = "SELECT path, filetype
7151
            FROM $tblDocument
7152
            WHERE id = $parentId
7153
              AND c_id = {$courseInfo['real_id']}";
7154
7155
        $result = Database::query($sql);
7156
7157
        if ($result === false || Database::num_rows($result) == 0) {
7158
            return [];
7159
        }
7160
7161
        $parentRow = Database::fetch_array($result, 'ASSOC');
7162
        $parentPath = $parentRow['path'];
7163
        $filetype = $parentRow['filetype'];
7164
7165
        if ($filetype !== 'folder') {
7166
            return [];
7167
        }
7168
7169
        return self::getAllDocumentData(
7170
            $courseInfo,
7171
            $parentPath,
7172
            $toGroupId,
7173
            $toUserId,
7174
            $canSeeInvisible,
7175
            $search,
7176
            $sessionId
7177
        );
7178
    }
7179
7180
    public static function autoResizeImageIfNeeded(int $size, string $tmpName): int
7181
    {
7182
        $resizeMax = api_get_configuration_value('wysiwyg_image_auto_resize_max');
7183
7184
        if (is_array($resizeMax)) {
7185
            if ($size > ($resizeMax['mb'] * 1024 * 1024)) {
7186
                throw new Exception(get_lang('UplFileTooBig'));
7187
            }
7188
7189
            $temp = new Image($tmpName);
7190
            $pictureInfo = $temp->get_image_info();
7191
7192
            $thumbSize = 0;
7193
7194
            if ($pictureInfo['width'] > $pictureInfo['height']) {
7195
                if ($pictureInfo['width'] > $resizeMax['w']) {
7196
                    $thumbSize = $resizeMax['w'];
7197
                }
7198
            } else {
7199
                if ($pictureInfo['height'] > $resizeMax['h']) {
7200
                    $thumbSize = $resizeMax['h'];
7201
                }
7202
            }
7203
7204
            if ($thumbSize) {
7205
                $temp->resize($thumbSize);
7206
                $temp->send_image($tmpName);
7207
7208
                return filesize($tmpName);
7209
            }
7210
        }
7211
7212
        return $size;
7213
    }
7214
7215
    /**
7216
     * Parse file information into a link.
7217
     *
7218
     * @param array  $userInfo        Current user info
7219
     * @param array  $courseInfo
7220
     * @param int    $session_id
7221
     * @param array  $resource
7222
     * @param int    $lp_id
7223
     * @param bool   $add_move_button
7224
     * @param string $target
7225
     * @param string $overwrite_url
7226
     * @param bool   $addAudioPreview
7227
     *
7228
     * @return string|null
7229
     */
7230
    private static function parseFile(
7231
        $userInfo,
7232
        $courseInfo,
7233
        $session_id,
7234
        $resource,
7235
        $lp_id,
7236
        $add_move_button,
7237
        $target,
7238
        $overwrite_url,
7239
        $addAudioPreview = false
7240
    ) {
7241
        $img_sys_path = api_get_path(SYS_CODE_PATH).'img/';
7242
        $web_code_path = api_get_path(WEB_CODE_PATH);
7243
7244
        $documentId = $resource['id'];
7245
        $path = $resource['path'];
7246
7247
        if (empty($path)) {
7248
            $num = 0;
7249
        } else {
7250
            $num = substr_count($path, '/') - 1;
7251
        }
7252
7253
        // It's a file.
7254
        $icon = choose_image($path);
7255
        $position = strrpos($icon, '.');
7256
        $icon = substr($icon, 0, $position).'_small.gif';
7257
        $my_file_title = Security::remove_XSS($resource['title']);
7258
        $visibility = $resource['visibility'];
7259
7260
        // If title is empty we try to use the path
7261
        if (empty($my_file_title)) {
7262
            $my_file_title = basename($path);
7263
        }
7264
7265
        if (api_get_configuration_value('save_titles_as_html')) {
7266
            $my_file_title = strip_tags($my_file_title);
7267
        }
7268
7269
        // Show the "image name" not the filename of the image.
7270
        if ($lp_id) {
7271
            // LP URL
7272
            $url = api_get_path(WEB_CODE_PATH).
7273
                'lp/lp_controller.php?'.api_get_cidreq().'&action=add_item&type='.TOOL_DOCUMENT.'&file='.$documentId.'&lp_id='.(int) $lp_id;
7274
        } else {
7275
            // Direct document URL
7276
            $url = $web_code_path.'document/document.php?cidReq='.$courseInfo['code'].'&id_session='.$session_id.'&id='.$documentId;
7277
        }
7278
7279
        if (!empty($overwrite_url)) {
7280
            $overwrite_url = Security::remove_XSS($overwrite_url);
7281
            $url = $overwrite_url.'&cidReq='.$courseInfo['code'].'&id_session='.$session_id.'&document_id='.$documentId;
7282
        }
7283
7284
        $img = Display::returnIconPath($icon);
7285
        if (!file_exists($img_sys_path.$icon)) {
7286
            $img = Display::returnIconPath('default_small.gif');
7287
        }
7288
7289
        $icon = '<img alt="" src="'.$img.'" title="" />';
7290
7291
        $preview = '';
7292
        if ($addAudioPreview) {
7293
            $path = api_get_path(WEB_COURSE_PATH).$courseInfo['directory'].'/document';
7294
            $resource['file_extension'] = pathinfo($resource['path'], PATHINFO_EXTENSION);
7295
            $preview = self::generateAudioPreview($path, $resource);
7296
            if (!empty($preview)) {
7297
                $preview .= '&nbsp;';
7298
                $icon = '';
7299
            }
7300
        }
7301
7302
        $link = Display::url(
7303
            $icon.'&nbsp;'.
7304
            $my_file_title,
7305
            $url,
7306
            ['target' => $target, 'class' => 'moved']
7307
        );
7308
7309
        $directUrl = $web_code_path.'document/document.php?cidReq='.$courseInfo['code'].'&id_session='.$session_id.'&id='.$documentId;
7310
        $link .= '&nbsp;'.Display::url(
7311
                Display::return_icon('preview_view.png', get_lang('Preview')),
7312
                $directUrl,
7313
                ['target' => '_blank']
7314
            );
7315
7316
        $visibilityClass = null;
7317
        if ($visibility == 0) {
7318
            $visibilityClass = ' text-muted ';
7319
        }
7320
        $return = null;
7321
7322
        if ($lp_id == false) {
7323
            $return .= '<li class="doc_resource '.$visibilityClass.' " data_id="'.$documentId.'" data_type="document" title="'.$my_file_title.'" >';
7324
        } else {
7325
            $return .= '<li class="doc_resource lp_resource_element '.$visibilityClass.' " data_id="'.$documentId.'" data_type="document" title="'.$my_file_title.'" >';
7326
        }
7327
7328
        $return .= '<div class="item_data" style="margin-left:'.($num * 5).'px;margin-right:5px;">';
7329
        $return .= $preview;
7330
7331
        if ($add_move_button) {
7332
            $return .= '<a class="moved" href="#">';
7333
            $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
7334
            $return .= '</a> ';
7335
        }
7336
7337
        $return .= $link;
7338
        $sessionStar = api_get_session_image($resource['session_id'], $userInfo['status']);
7339
        $return .= $sessionStar;
7340
7341
        $return .= '</div></li>';
7342
7343
        return $return;
7344
    }
7345
7346
    /**
7347
     * @param int   $folderId
7348
     * @param array $resource
7349
     * @param int   $lp_id
7350
     *
7351
     * @return string|null
7352
     */
7353
    private static function parseFolder($folderId, $resource, $lp_id)
7354
    {
7355
        $title = isset($resource['title']) ? $resource['title'] : null;
7356
        $path = isset($resource['path']) ? $resource['path'] : null;
7357
7358
        if (empty($path)) {
7359
            $num = 0;
7360
        } else {
7361
            $num = substr_count($path, '/');
7362
        }
7363
7364
        // It's a folder.
7365
        //hide some folders
7366
        if (in_array(
7367
            $path,
7368
            ['/shared_folder', '/chat_files', '/HotPotatoes_files', '/css', '/certificates']
7369
        )) {
7370
            return null;
7371
        } elseif (preg_match('/_groupdocs/', $path)) {
7372
            return null;
7373
        } elseif (preg_match('/sf_user_/', $path)) {
7374
            return null;
7375
        } elseif (preg_match('/shared_folder_session_/', $path)) {
7376
            return null;
7377
        }
7378
7379
        // if in LP, hidden folder are displayed in grey
7380
        $folder_class_hidden = '';
7381
        if ($lp_id) {
7382
            if (isset($resource['visibility']) && $resource['visibility'] == 0) {
7383
                $folder_class_hidden = ' doc_folder_hidden'; // in base.css
7384
            }
7385
        }
7386
        $onclick = 'onclick="javascript: testResources(\'res_'.$resource['id'].'\',\'img_'.$resource['id'].'\')"';
7387
        $return = null;
7388
7389
        if (empty($path)) {
7390
            $return = '<ul class="lp_resource">';
7391
        }
7392
7393
        $return .= '<li
7394
            class="doc_folder'.$folder_class_hidden.'"
7395
            id="doc_id_'.$resource['id'].'"
7396
            style="margin-left:'.($num * 18).'px;"
7397
            >';
7398
7399
        $image = Display::returnIconPath('nolines_plus.gif');
7400
        if (empty($path)) {
7401
            $image = Display::returnIconPath('nolines_minus.gif');
7402
        }
7403
        $return .= '<img
7404
            style="cursor: pointer;"
7405
            src="'.$image.'"
7406
            align="absmiddle"
7407
            id="img_'.$resource['id'].'" '.$onclick.'
7408
        >';
7409
7410
        $return .= Display::return_icon('lp_folder.gif').'&nbsp;';
7411
        $return .= '<span '.$onclick.' style="cursor: pointer;" >'.
7412
            Security::remove_XSS($title).
7413
            '</span>';
7414
        $return .= '</li>';
7415
7416
        if (empty($path)) {
7417
            if ($folderId == false) {
7418
                $return .= '<div id="res_'.$resource['id'].'" >';
7419
            } else {
7420
                $return .= '<div id="res_'.$resource['id'].'" style="display: none;" >';
7421
            }
7422
        }
7423
7424
        return $return;
7425
    }
7426
7427
    /**
7428
     * Get the button to edit document.
7429
     *
7430
     * @param bool   $isReadOnly
7431
     * @param string $extension
7432
     * @param bool   $isCertificateMode
7433
     *
7434
     * @return string
7435
     */
7436
    private static function getButtonEdit($isReadOnly, array $documentData, $extension, $isCertificateMode)
7437
    {
7438
        $extension = strtolower($extension);
7439
        $iconEn = Display::return_icon('edit.png', get_lang('Modify'));
7440
        $iconDis = Display::return_icon('edit_na.png', get_lang('Modify'));
7441
        $courseParams = api_get_cidreq();
7442
        $webOdfExtensionList = self::get_web_odf_extension_list();
7443
        $path = $documentData['path'];
7444
        $document_id = $documentData['id'];
7445
7446
        if ($isReadOnly) {
7447
            if (!api_is_course_admin() && !api_is_platform_admin()) {
7448
                return $iconDis;
7449
            }
7450
7451
            if (
7452
                $extension == 'svg' && api_browser_support('svg') &&
7453
                api_get_setting('enabled_support_svg') == 'true'
7454
            ) {
7455
                return Display::url($iconEn, "edit_draw.php?$courseParams&id=$document_id");
7456
            }
7457
7458
            if (
7459
                in_array($extension, $webOdfExtensionList) &&
7460
                api_get_configuration_value('enabled_support_odf') === true
7461
            ) {
7462
                return Display::url($iconEn, "edit_odf.php?$courseParams&id=$document_id");
7463
            }
7464
7465
            if (
7466
                in_array($extension, ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'pxd']) &&
7467
                api_get_setting('enabled_support_pixlr') == 'true'
7468
            ) {
7469
                return Display::url($iconEn, "edit_paint.php?$courseParams&id=$document_id");
7470
            }
7471
7472
            return Display::url($iconEn, "edit_document.php?$courseParams&id=$document_id");
7473
        }
7474
7475
        if (in_array($path, self::get_system_folders()) || $documentData['filetype'] === 'folder' && strpos($path, 'chat_files') !== false) {
7476
            return $iconDis;
7477
        }
7478
7479
        if ($isCertificateMode) {
7480
            return Display::url($iconEn, "edit_document.php?$courseParams&id=$document_id&curdirpath=/certificates");
7481
        }
7482
7483
        $sessionId = api_get_session_id();
7484
7485
        if ($sessionId && $documentData['session_id'] != $sessionId) {
7486
            return $iconDis;
7487
        }
7488
7489
        if (
7490
            $extension == 'svg' && api_browser_support('svg') &&
7491
            api_get_setting('enabled_support_svg') == 'true'
7492
        ) {
7493
            return Display::url($iconEn, "edit_draw.php?$courseParams&id=$document_id");
7494
        }
7495
7496
        if (
7497
            in_array($extension, $webOdfExtensionList) &&
7498
            api_get_configuration_value('enabled_support_odf') === true
7499
        ) {
7500
            return Display::url($iconEn, "edit_odf.php?$courseParams&id=$document_id");
7501
        }
7502
7503
        if (
7504
            in_array($extension, ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'pxd']) &&
7505
            api_get_setting('enabled_support_pixlr') == 'true'
7506
        ) {
7507
            return Display::url($iconEn, "edit_paint.php?$courseParams&id=$document_id");
7508
        }
7509
7510
        return Display::url($iconEn, "edit_document.php?$courseParams&id=$document_id");
7511
    }
7512
7513
    /**
7514
     * Get the button to move document.
7515
     *
7516
     * @param bool $isReadOnly
7517
     * @param bool $isCertificateMode
7518
     * @param int  $parentId
7519
     *
7520
     * @return string
7521
     */
7522
    private static function getButtonMove($isReadOnly, array $documentData, $isCertificateMode, $parentId)
7523
    {
7524
        $iconEn = Display::return_icon('move.png', get_lang('Move'));
7525
        $iconDis = Display::return_icon('move_na.png', get_lang('Move'));
7526
7527
        if ($isReadOnly) {
7528
            return $iconDis;
7529
        }
7530
7531
        $path = $documentData['path'];
7532
        $document_id = $documentData['id'];
7533
        $sessionId = api_get_session_id();
7534
        $courseParams = api_get_cidreq();
7535
7536
        if ($isCertificateMode || in_array($path, self::get_system_folders()) || $documentData['filetype'] === 'folder' && strpos($path, 'chat_files') !== false) {
7537
            return $iconDis;
7538
        }
7539
7540
        if ($sessionId) {
7541
            if ($documentData['session_id'] != $sessionId) {
7542
                return $iconDis;
7543
            }
7544
        }
7545
7546
        $urlMoveParams = http_build_query([
7547
            'id' => $parentId,
7548
            'move' => $document_id,
7549
            'sec_token' => Security::getTokenFromSession(),
7550
        ]);
7551
7552
        return Display::url(
7553
            $iconEn,
7554
            api_get_self()."?$courseParams&$urlMoveParams"
7555
        );
7556
    }
7557
7558
    /**
7559
     * Get the button to set visibility to document.
7560
     *
7561
     * @param bool $isReadOnly
7562
     * @param int  $visibility
7563
     * @param bool $isCertificateMode
7564
     * @param int  $parentId
7565
     *
7566
     * @return string|null
7567
     */
7568
    private static function getButtonVisibility(
7569
        $isReadOnly,
7570
        $visibility,
7571
        array $documentData,
7572
        $isCertificateMode,
7573
        $parentId
7574
    ) {
7575
        $visibility_icon = $visibility == 0 ? 'invisible' : 'visible';
7576
        $visibility_command = $visibility == 0 ? 'set_visible' : 'set_invisible';
7577
        $courseParams = api_get_cidreq();
7578
7579
        if ($isReadOnly) {
7580
            if (api_is_allowed_to_edit() || api_is_platform_admin()) {
7581
                return Display::return_icon($visibility_icon.'.png', get_lang('VisibilityCannotBeChanged'));
7582
            }
7583
7584
            return null;
7585
        }
7586
7587
        if ($isCertificateMode) {
7588
            return Display::return_icon($visibility_icon.'.png', get_lang('VisibilityCannotBeChanged'));
7589
        }
7590
7591
        if (api_is_allowed_to_edit() || api_is_platform_admin()) {
7592
            $tip_visibility = $visibility_icon == 'invisible' ? get_lang('Show') : get_lang('Hide');
7593
            $secToken = Security::getTokenFromSession();
7594
7595
            return Display::url(
7596
                Display::return_icon($visibility_icon.'.png', $tip_visibility),
7597
                api_get_self()."?$courseParams&id=$parentId&$visibility_command={$documentData['id']}&sec_token=$secToken"
7598
            );
7599
        }
7600
7601
        return null;
7602
    }
7603
7604
    /**
7605
     * GEt the button to delete a document.
7606
     *
7607
     * @param bool   $isReadOnly
7608
     * @param bool   $isCertificateMode
7609
     * @param string $curDirPath
7610
     * @param int    $parentId
7611
     *
7612
     * @return string
7613
     */
7614
    private static function getButtonDelete(
7615
        $isReadOnly,
7616
        array $documentData,
7617
        $isCertificateMode,
7618
        $curDirPath,
7619
        $parentId
7620
    ) {
7621
        $iconEn = Display::return_icon('delete.png', get_lang('Delete'));
7622
        $iconDis = Display::return_icon('delete_na.png', get_lang('ThisFolderCannotBeDeleted'));
7623
        $path = $documentData['path'];
7624
        $id = $documentData['id'];
7625
        $courseParams = api_get_cidreq();
7626
7627
        if ($isReadOnly) {
7628
            return $iconDis;
7629
        }
7630
7631
        if (in_array($path, self::get_system_folders()) || $documentData['filetype'] === 'folder' && strpos($path, 'chat_files') !== false) {
7632
            return $iconDis;
7633
        }
7634
7635
        $titleToShow = addslashes(basename($documentData['title']));
7636
        $urlDeleteParams = http_build_query([
7637
            'curdirpath' => $curDirPath,
7638
            'action' => 'delete_item',
7639
            'id' => $parentId,
7640
            'deleteid' => $documentData['id'],
7641
            'sec_token' => Security::getTokenFromSession(),
7642
        ]);
7643
7644
        $btn = Display::url(
7645
            $iconEn,
7646
            '#',
7647
            [
7648
                'data-item-title' => $titleToShow,
7649
                'data-href' => api_get_self()."?$courseParams&$urlDeleteParams",
7650
                'data-toggle' => 'modal',
7651
                'data-target' => '#confirm-delete',
7652
            ]
7653
        );
7654
7655
        if (
7656
            isset($_GET['curdirpath']) &&
7657
            $_GET['curdirpath'] == '/certificates' &&
7658
            self::get_default_certificate_id(api_get_course_id()) == $id
7659
        ) {
7660
            return $btn;
7661
        }
7662
7663
        if ($isCertificateMode) {
7664
            return $btn;
7665
        }
7666
7667
        $sessionId = api_get_session_id();
7668
7669
        if ($sessionId) {
7670
            if ($documentData['session_id'] != $sessionId) {
7671
                return $iconDis;
7672
            }
7673
        }
7674
7675
        return $btn;
7676
    }
7677
7678
    /**
7679
     * Include MathJax script in document.
7680
     *
7681
     * @param string file content $content
7682
     *
7683
     * @return string file content
7684
     */
7685
    private static function includeMathJaxScript($content)
7686
    {
7687
        $scriptTag = '<script src="'.api_get_path(WEB_PUBLIC_PATH).'assets/MathJax/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>';
7688
        // Find position of </body> tag
7689
        $pos = strpos($content, '</body>');
7690
        // If </body> tag found, insert the script tag before it
7691
        if ($pos !== false) {
7692
            $content = substr_replace($content, $scriptTag, $pos, 0);
7693
        } else {
7694
            // If </body> tag not found, just append the script tag at the end
7695
            $content .= $scriptTag;
7696
        }
7697
7698
        return $content;
7699
    }
7700
}
7701