Passed
Push — master ( 08314e...a4ad40 )
by Julito
10:14
created

DocumentManager::get_resources_from_source_html()   F

Complexity

Conditions 66
Paths 99

Size

Total Lines 364
Code Lines 249

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 66
eloc 249
nc 99
nop 4
dl 0
loc 364
rs 3.3333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
3263
            $filePath,
3264
            $courseInfo,
3265
            $sessionId,
3266
            $groupId
3267
        )) {
3268
            $uniqueName = self::addSuffixToFileName($name, '_'.$counter);
3269
            $filePath = $path.$uniqueName;
3270
            $counter++;
3271
        }
3272
3273
        return $uniqueName;
3274
    }
3275
3276
    /**
3277
     * Builds the form that enables the user to
3278
     * select a directory to browse/upload in.
3279
     *
3280
     * @param array    An array containing the folders we want to be able to select
3281
     * @param string    The current folder (path inside of the "document" directory, including the prefix "/")
3282
     * @param string    Group directory, if empty, prevents documents to be uploaded
3283
     * (because group documents cannot be uploaded in root)
3284
     * @param bool    Whether to change the renderer (this will add a template <span>
3285
     * to the QuickForm object displaying the form)
3286
     *
3287
     * @return string html form
3288
     */
3289
    public static function build_directory_selector(
3290
        $folders,
3291
        $document_id,
3292
        $group_dir = '',
3293
        $change_renderer = false,
3294
        &$form = null,
3295
        $selectName = 'id'
3296
    ) {
3297
        $doc_table = Database::get_course_table(TABLE_DOCUMENT);
3298
        $course_id = api_get_course_int_id();
3299
        $folder_titles = [];
3300
3301
        if (is_array($folders)) {
3302
            $escaped_folders = [];
3303
            foreach ($folders as $key => &$val) {
3304
                $escaped_folders[$key] = Database::escape_string($val);
3305
            }
3306
            $folder_sql = implode("','", $escaped_folders);
3307
3308
            $sql = "SELECT DISTINCT docs.title, n.path
3309
                    FROM resource_node AS n
3310
                    INNER JOIN $doc_table AS docs
3311
                    ON (docs.resource_node_id = n.id)
3312
                    INNER JOIN resource_link l
3313
                    ON (l.resource_node_id = n.id)
3314
                    WHERE
3315
                        l.c_id = $course_id AND
3316
                        docs.filetype = 'folder' AND
3317
                        n.path IN ('".$folder_sql."') AND
3318
                        l.visibility NOT IN ('".ResourceLink::VISIBILITY_DELETED."')
3319
                         ";
3320
3321
            /*$sql = "SELECT path, title
3322
                    FROM $doc_table
3323
                    WHERE
3324
                        filetype = 'folder' AND
3325
                        c_id = $course_id AND
3326
                        path IN ('".$folder_sql."') ";*/
3327
            $res = Database::query($sql);
3328
            $folder_titles = [];
3329
            while ($obj = Database::fetch_object($res)) {
3330
                $folder_titles[$obj->path] = $obj->title;
3331
            }
3332
        }
3333
3334
        $attributes = [];
3335
        if (empty($form)) {
3336
            $form = new FormValidator('selector', 'GET', api_get_self().'?'.api_get_cidreq());
3337
            $attributes = ['onchange' => 'javascript: document.selector.submit();'];
3338
        }
3339
        $form->addElement('hidden', 'cidReq', api_get_course_id());
3340
        $form->addElement('hidden', 'cid', api_get_course_int_id());
3341
        $form->addElement('hidden', 'sid', api_get_session_id());
3342
        $form->addElement('hidden', 'gid', api_get_group_id());
3343
3344
        $parent_select = $form->addSelect(
3345
            $selectName,
3346
            get_lang('Current folder'),
3347
            [],
3348
            $attributes
3349
        );
3350
3351
        // Group documents cannot be uploaded in the root
3352
        if (empty($group_dir)) {
3353
            $parent_select->addOption(get_lang('Documents'), '/');
3354
3355
            if (is_array($folders)) {
3356
                foreach ($folders as $folder_id => &$folder) {
3357
                    if (!isset($folder_titles[$folder])) {
3358
                        continue;
3359
                    }
3360
                    $selected = ($document_id == $folder_id) ? ' selected="selected"' : '';
3361
                    $path_parts = explode('/', $folder);
3362
                    $folder_titles[$folder] = cut($folder_titles[$folder], 80);
3363
                    $counter = count($path_parts) - 2;
3364
                    if ($counter > 0) {
3365
                        $label = str_repeat('&nbsp;&nbsp;&nbsp;', $counter).' &mdash; '.$folder_titles[$folder];
3366
                    } else {
3367
                        $label = ' &mdash; '.$folder_titles[$folder];
3368
                    }
3369
                    $parent_select->addOption($label, $folder_id);
3370
                    if ('' != $selected) {
3371
                        $parent_select->setSelected($folder_id);
3372
                    }
3373
                }
3374
            }
3375
        } else {
3376
            if (!empty($folders)) {
3377
                foreach ($folders as $folder_id => &$folder) {
3378
                    $selected = ($document_id == $folder_id) ? ' selected="selected"' : '';
3379
                    $label = $folder_titles[$folder];
3380
                    if ($folder == $group_dir) {
3381
                        $label = get_lang('Documents');
3382
                    } else {
3383
                        $path_parts = explode('/', str_replace($group_dir, '', $folder));
3384
                        $label = cut($label, 80);
3385
                        $label = str_repeat('&nbsp;&nbsp;&nbsp;', count($path_parts) - 2).' &mdash; '.$label;
3386
                    }
3387
                    $parent_select->addOption($label, $folder_id);
3388
                    if ('' != $selected) {
3389
                        $parent_select->setSelected($folder_id);
3390
                    }
3391
                }
3392
            }
3393
        }
3394
3395
        return $form->toHtml();
3396
    }
3397
3398
    /**
3399
     * Builds an img html tag for the file type.
3400
     *
3401
     * @param string $type            (file/folder)
3402
     * @param string $path
3403
     * @param bool   $isAllowedToEdit
3404
     *
3405
     * @return string img html tag
3406
     */
3407
    public static function build_document_icon_tag($type, $path, $isAllowedToEdit = null)
3408
    {
3409
        $basename = basename($path);
3410
        $sessionId = api_get_session_id();
3411
        if (is_null($isAllowedToEdit)) {
3412
            $isAllowedToEdit = api_is_allowed_to_edit(null, true);
3413
        }
3414
        $user_image = false;
3415
        if ('file' == $type) {
3416
            $icon = choose_image($basename);
3417
            $basename = substr(strrchr($basename, '.'), 1);
3418
        } elseif ('link' == $type) {
3419
            $icon = 'clouddoc.png';
3420
            $basename = get_lang('Cloud file link');
3421
        } else {
3422
            if ('/shared_folder' == $path) {
3423
                $icon = 'folder_users.png';
3424
                if ($isAllowedToEdit) {
3425
                    $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3426
The users folder contains a folder for each user who has accessed it through the documents tool, or when any file has been sent in the course through the online editor. If neither circumstances has occurred, then no user folder will have been created. In the case of groups, files that are sent through the editor will be added in the folder of each group, which is only accessible by students from this group.');
3427
                } else {
3428
                    $basename = get_lang('Folders of users');
3429
                }
3430
            } elseif (strstr($basename, 'sf_user_')) {
3431
                $userInfo = api_get_user_info(substr($basename, 8));
3432
                $icon = $userInfo['avatar_small'];
3433
                $basename = get_lang('User folder').' '.$userInfo['complete_name'];
3434
                $user_image = true;
3435
            } elseif (strstr($path, 'shared_folder_session_')) {
3436
                $sessionName = api_get_session_name($sessionId);
3437
                if ($isAllowedToEdit) {
3438
                    $basename = '***('.$sessionName.')*** '.get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3439
The users folder contains a folder for each user who has accessed it through the documents tool, or when any file has been sent in the course through the online editor. If neither circumstances has occurred, then no user folder will have been created. In the case of groups, files that are sent through the editor will be added in the folder of each group, which is only accessible by students from this group.');
3440
                } else {
3441
                    $basename = get_lang('Folders of users').' ('.$sessionName.')';
3442
                }
3443
                $icon = 'folder_users.png';
3444
            } else {
3445
                $icon = 'folder_document.png';
3446
3447
                if ('/audio' == $path) {
3448
                    $icon = 'folder_audio.png';
3449
                    if ($isAllowedToEdit) {
3450
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3451
This folder contains the default archives. You can clear files or add new ones, but if a file is hidden when it is inserted in a web document, the students will not be able to see it in this document. When inserting a file in a web document, first make sure it is visible. The folders can remain hidden.');
3452
                    } else {
3453
                        $basename = get_lang('Audio');
3454
                    }
3455
                } elseif ('/flash' == $path) {
3456
                    $icon = 'folder_flash.png';
3457
                    if ($isAllowedToEdit) {
3458
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3459
This folder contains the default archives. You can clear files or add new ones, but if a file is hidden when it is inserted in a web document, the students will not be able to see it in this document. When inserting a file in a web document, first make sure it is visible. The folders can remain hidden.');
3460
                    } else {
3461
                        $basename = get_lang('Flash');
3462
                    }
3463
                } elseif ('/images' == $path) {
3464
                    $icon = 'folder_images.png';
3465
                    if ($isAllowedToEdit) {
3466
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3467
This folder contains the default archives. You can clear files or add new ones, but if a file is hidden when it is inserted in a web document, the students will not be able to see it in this document. When inserting a file in a web document, first make sure it is visible. The folders can remain hidden.');
3468
                    } else {
3469
                        $basename = get_lang('Images');
3470
                    }
3471
                } elseif ('/video' == $path) {
3472
                    $icon = 'folder_video.png';
3473
                    if ($isAllowedToEdit) {
3474
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3475
This folder contains the default archives. You can clear files or add new ones, but if a file is hidden when it is inserted in a web document, the students will not be able to see it in this document. When inserting a file in a web document, first make sure it is visible. The folders can remain hidden.');
3476
                    } else {
3477
                        $basename = get_lang('Video');
3478
                    }
3479
                } elseif ('/images/gallery' == $path) {
3480
                    $icon = 'folder_gallery.png';
3481
                    if ($isAllowedToEdit) {
3482
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3483
This folder contains the default archives. You can clear files or add new ones, but if a file is hidden when it is inserted in a web document, the students will not be able to see it in this document. When inserting a file in a web document, first make sure it is visible. The folders can remain hidden.');
3484
                    } else {
3485
                        $basename = get_lang('Gallery');
3486
                    }
3487
                } elseif ('/chat_files' == $path) {
3488
                    $icon = 'folder_chat.png';
3489
                    if ($isAllowedToEdit) {
3490
                        $basename = get_lang('INFORMATION VISIBLE TO THE TEACHER ONLY:
3491
This folder contains all sessions that have been opened in the chat. Although the chat sessions can often be trivial, others can be really interesting and worthy of being incorporated as an additional work document. To do this without changing the visibility of this folder, make the file visible and link it from where you deem appropriate. It is not recommended to make this folder visible to all.');
3492
                    } else {
3493
                        $basename = get_lang('Chat conversations history');
3494
                    }
3495
                } elseif ('/learning_path' == $path) {
3496
                    $icon = 'folder_learningpath.png';
3497
                    if ($isAllowedToEdit) {
3498
                        $basename = get_lang('HelpFolderLearning paths');
3499
                    } else {
3500
                        $basename = get_lang('Learning paths');
3501
                    }
3502
                }
3503
            }
3504
        }
3505
3506
        if ($user_image) {
3507
            return Display::img($icon, $basename, [], false);
3508
        }
3509
3510
        return Display::return_icon($icon, $basename, [], ICON_SIZE_SMALL);
3511
    }
3512
3513
    public static function isBasicCourseFolder($path, $sessionId)
3514
    {
3515
        $cleanPath = Security::remove_XSS($path);
3516
        $basicCourseFolder = '/basic-course-documents__'.$sessionId.'__0';
3517
3518
        return $cleanPath == $basicCourseFolder;
3519
    }
3520
3521
    /**
3522
     * Adds a new document to the database.
3523
     *
3524
     * @param array  $courseInfo
3525
     * @param string $path
3526
     * @param string $fileType
3527
     * @param int    $fileSize
3528
     * @param string $title
3529
     * @param string $comment
3530
     * @param int    $readonly
3531
     * @param int    $visibility       see ResourceLink constants
3532
     * @param int    $groupId          group.id
3533
     * @param int    $sessionId        Session ID, if any
3534
     * @param int    $userId           creator user id
3535
     * @param bool   $sendNotification
3536
     * @param string $content
3537
     * @param int    $parentId
3538
     * @param string $realPath
3539
     *
3540
     * @return CDocument|false
3541
     */
3542
    public static function addDocument(
3543
        $courseInfo,
3544
        $path,
3545
        $fileType,
3546
        $fileSize,
3547
        $title,
3548
        $comment = null,
3549
        $readonly = 0,
3550
        $visibility = null,
3551
        $groupId = 0,
3552
        $sessionId = 0,
3553
        $userId = 0,
3554
        $sendNotification = true,
3555
        $content = '',
3556
        $parentId = 0,
3557
        $realPath = ''
3558
    ) {
3559
        $userId = empty($userId) ? api_get_user_id() : $userId;
3560
        if (empty($userId)) {
3561
            return false;
3562
        }
3563
3564
        $userEntity = api_get_user_entity($userId);
3565
        if (null === $userEntity) {
3566
            return false;
3567
        }
3568
3569
        $courseEntity = api_get_course_entity($courseInfo['real_id']);
3570
        if (null === $courseEntity) {
3571
            return false;
3572
        }
3573
3574
        $sessionId = empty($sessionId) ? api_get_session_id() : $sessionId;
3575
        $session = api_get_session_entity($sessionId);
3576
        $group = api_get_group_entity($groupId);
3577
        $readonly = (int) $readonly;
3578
        $documentRepo = Container::getDocumentRepository();
3579
3580
        /** @var \Chamilo\CoreBundle\Entity\AbstractResource $parentResource */
3581
        $parentResource = $courseEntity;
3582
        if (!empty($parentId)) {
3583
            $parent = $documentRepo->find($parentId);
3584
            if ($parent) {
3585
                $parentResource = $parent;
3586
            }
3587
        }
3588
3589
        $document = $documentRepo->findCourseResourceByTitle(
3590
            $title,
3591
            $parentResource->getResourceNode(),
3592
            $courseEntity,
3593
            $session,
3594
            $group
3595
        );
3596
3597
        // Document already exists
3598
        if (null !== $document) {
3599
            return $document;
3600
        }
3601
3602
//        $criteria = ['path' => $path, 'course' => $courseEntity];
3603
//        $document = $documentRepo->findOneBy($criteria);
3604
//
3605
//        // Document already exists
3606
//        if ($document) {
3607
//            return false;
3608
//        }
3609
3610
        // is updated using the title
3611
        $document = (new CDocument())
3612
            ->setFiletype($fileType)
3613
            ->setTitle($title)
3614
            ->setComment($comment)
3615
            ->setReadonly(1 === $readonly)
3616
            ->setParent($parentResource)
3617
            ->addCourseLink($courseEntity, $session, $group)
3618
        ;
3619
3620
        $em = Database::getManager();
3621
        $em->persist($document);
3622
        $em->flush();
3623
3624
        $repo = Container::getDocumentRepository();
3625
        if (!empty($content)) {
3626
            $repo->addFileFromString($document, $title, 'text/html', $content, true);
3627
        } else {
3628
            if (!empty($realPath) && !is_dir($realPath) && file_exists($realPath)) {
3629
                $repo->addFileFromPath($document, $title, $realPath);
3630
            }
3631
        }
3632
3633
        if ($document) {
3634
            $allowNotification = api_get_configuration_value('send_notification_when_document_added');
3635
            if ($sendNotification && $allowNotification) {
3636
                $courseTitle = $courseEntity->getTitle();
3637
                if (!empty($sessionId)) {
3638
                    $sessionInfo = api_get_session_info($sessionId);
3639
                    $courseTitle .= ' ( '.$sessionInfo['name'].') ';
3640
                }
3641
3642
                $url = api_get_path(WEB_CODE_PATH).
3643
                    'document/showinframes.php?cid='.$courseEntity->getId().'&sid='.$sessionId.'&id='.$document->getIid();
3644
                $link = Display::url(basename($title), $url, ['target' => '_blank']);
3645
                $userInfo = api_get_user_info($userId);
3646
                $message = sprintf(
3647
                    get_lang('A new document %s has been added to the document tool in your course %s by %s.'),
3648
                    $link,
3649
                    $courseTitle,
3650
                    $userInfo['complete_name']
3651
                );
3652
                $subject = sprintf(get_lang('New document added to course %s'), $courseTitle);
3653
                MessageManager::sendMessageToAllUsersInCourse($subject, $message, $courseEntity, $sessionId);
3654
            }
3655
3656
            return $document;
3657
        }
3658
3659
        return false;
3660
    }
3661
}
3662