Passed
Push — master ( d8488a...475d69 )
by Angel Fernando Quiroz
08:30
created

api_get_js()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\AccessUrl;
6
use Chamilo\CoreBundle\Entity\Course;
7
use Chamilo\CoreBundle\Entity\Language;
8
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
9
use Chamilo\CoreBundle\Entity\SettingsCurrent;
10
use Chamilo\CoreBundle\Entity\User;
11
use Chamilo\CoreBundle\Entity\UserCourseCategory;
12
use Chamilo\CoreBundle\Enums\ActionIcon;
13
use Chamilo\CoreBundle\Enums\ObjectIcon;
14
use Chamilo\CoreBundle\Exception\NotAllowedException;
15
use Chamilo\CoreBundle\Framework\Container;
16
use Chamilo\CoreBundle\Helpers\MailHelper;
17
use Chamilo\CoreBundle\Helpers\PermissionHelper;
18
use Chamilo\CoreBundle\Helpers\PluginHelper;
19
use Chamilo\CoreBundle\Helpers\ThemeHelper;
20
use Chamilo\CourseBundle\Entity\CGroup;
21
use Chamilo\CourseBundle\Entity\CLp;
22
use ChamiloSession as Session;
23
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
24
use Symfony\Component\Finder\Finder;
25
use Symfony\Component\Mime\Address;
26
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
27
use Symfony\Component\Security\Core\User\UserInterface;
28
use Symfony\Component\Validator\Constraints as Assert;
29
use ZipStream\Option\Archive;
30
use ZipStream\ZipStream;
31
32
/**
33
 * This is a code library for Chamilo.
34
 * It is included by default in every Chamilo file (through including the global.inc.php)
35
 * This library is in process of being transferred to src/Chamilo/CoreBundle/Helpers/ChamiloHelper.
36
 * Whenever a function is transferred to the ChamiloUtil class, the places where it is used should include
37
 * the "use Chamilo\CoreBundle\Utils\ChamiloUtil;" statement.
38
 */
39
40
// PHP version requirement.
41
define('REQUIRED_PHP_VERSION', '8.2');
42
define('REQUIRED_MIN_MEMORY_LIMIT', '128');
43
define('REQUIRED_MIN_UPLOAD_MAX_FILESIZE', '10');
44
define('REQUIRED_MIN_POST_MAX_SIZE', '10');
45
46
// USER STATUS CONSTANTS
47
/** global status of a user: student */
48
define('STUDENT', 5);
49
/** global status of a user: course manager */
50
define('COURSEMANAGER', 1);
51
/** global status of a user: session admin */
52
define('SESSIONADMIN', 3);
53
/** global status of a user: human resources manager */
54
define('DRH', 4);
55
/** global status of a user: anonymous visitor */
56
define('ANONYMOUS', 6);
57
/** global status of a user: low security, necessary for inserting data from
58
 * the teacher through HTMLPurifier */
59
define('COURSEMANAGERLOWSECURITY', 10);
60
// Soft user status
61
define('PLATFORM_ADMIN', 11);
62
define('SESSION_COURSE_COACH', 12);
63
define('SESSION_GENERAL_COACH', 13);
64
define('COURSE_STUDENT', 14); //student subscribed in a course
65
define('SESSION_STUDENT', 15); //student subscribed in a session course
66
define('COURSE_TUTOR', 16); // student is tutor of a course (NOT in session)
67
define('STUDENT_BOSS', 17); // student is boss
68
define('INVITEE', 20);
69
define('HRM_REQUEST', 21); //HRM has request for linking with user
70
71
// COURSE VISIBILITY CONSTANTS
72
/** only visible for course admin */
73
define('COURSE_VISIBILITY_CLOSED', 0);
74
/** only visible for users registered in the course */
75
define('COURSE_VISIBILITY_REGISTERED', 1);
76
/** Open for all registered users on the platform */
77
define('COURSE_VISIBILITY_OPEN_PLATFORM', 2);
78
/** Open for the whole world */
79
define('COURSE_VISIBILITY_OPEN_WORLD', 3);
80
/** Invisible to all except admin */
81
define('COURSE_VISIBILITY_HIDDEN', 4);
82
83
define('COURSE_REQUEST_PENDING', 0);
84
define('COURSE_REQUEST_ACCEPTED', 1);
85
define('COURSE_REQUEST_REJECTED', 2);
86
define('DELETE_ACTION_ENABLED', false);
87
88
// EMAIL SENDING RECIPIENT CONSTANTS
89
define('SEND_EMAIL_EVERYONE', 1);
90
define('SEND_EMAIL_STUDENTS', 2);
91
define('SEND_EMAIL_TEACHERS', 3);
92
93
// SESSION VISIBILITY CONSTANTS
94
define('SESSION_VISIBLE_READ_ONLY', 1);
95
define('SESSION_VISIBLE', 2);
96
/**
97
 * @deprecated Use Session::INVISIBLE
98
 */
99
define('SESSION_INVISIBLE', 3); // not available
100
define('SESSION_AVAILABLE', 4);
101
102
define('SESSION_LINK_TARGET', '_self');
103
104
define('SUBSCRIBE_ALLOWED', 1);
105
define('SUBSCRIBE_NOT_ALLOWED', 0);
106
define('UNSUBSCRIBE_ALLOWED', 1);
107
define('UNSUBSCRIBE_NOT_ALLOWED', 0);
108
109
// SURVEY VISIBILITY CONSTANTS
110
define('SURVEY_VISIBLE_TUTOR', 0);
111
define('SURVEY_VISIBLE_TUTOR_STUDENT', 1);
112
define('SURVEY_VISIBLE_PUBLIC', 2);
113
114
// CONSTANTS defining all tools, using the english version
115
/* When you add a new tool you must add it into function api_get_tools_lists() too */
116
define('TOOL_DOCUMENT', 'document');
117
define('TOOL_LP_FINAL_ITEM', 'final_item');
118
define('TOOL_READOUT_TEXT', 'readout_text');
119
define('TOOL_THUMBNAIL', 'thumbnail');
120
define('TOOL_HOTPOTATOES', 'hotpotatoes');
121
define('TOOL_CALENDAR_EVENT', 'calendar_event');
122
define('TOOL_LINK', 'link');
123
define('TOOL_LINK_CATEGORY', 'link_category');
124
define('TOOL_COURSE_DESCRIPTION', 'course_description');
125
define('TOOL_SEARCH', 'search');
126
define('TOOL_LEARNPATH', 'learnpath');
127
define('TOOL_LEARNPATH_CATEGORY', 'learnpath_category');
128
define('TOOL_AGENDA', 'agenda');
129
define('TOOL_ANNOUNCEMENT', 'announcement');
130
define('TOOL_FORUM', 'forum');
131
define('TOOL_FORUM_CATEGORY', 'forum_category');
132
define('TOOL_FORUM_THREAD', 'forum_thread');
133
define('TOOL_FORUM_POST', 'forum_post');
134
define('TOOL_FORUM_ATTACH', 'forum_attachment');
135
define('TOOL_FORUM_THREAD_QUALIFY', 'forum_thread_qualify');
136
define('TOOL_THREAD', 'thread');
137
define('TOOL_POST', 'post');
138
define('TOOL_DROPBOX', 'dropbox');
139
define('TOOL_QUIZ', 'quiz');
140
define('TOOL_TEST_CATEGORY', 'test_category');
141
define('TOOL_USER', 'user');
142
define('TOOL_GROUP', 'group');
143
define('TOOL_BLOGS', 'blog_management');
144
define('TOOL_CHAT', 'chat');
145
define('TOOL_STUDENTPUBLICATION', 'student_publication');
146
define('TOOL_TRACKING', 'tracking');
147
define('TOOL_HOMEPAGE_LINK', 'homepage_link');
148
define('TOOL_COURSE_SETTING', 'course_setting');
149
define('TOOL_BACKUP', 'backup');
150
define('TOOL_COPY_COURSE_CONTENT', 'copy_course_content');
151
define('TOOL_RECYCLE_COURSE', 'recycle_course');
152
define('TOOL_COURSE_HOMEPAGE', 'course_homepage');
153
define('TOOL_COURSE_RIGHTS_OVERVIEW', 'course_rights');
154
define('TOOL_UPLOAD', 'file_upload');
155
define('TOOL_COURSE_MAINTENANCE', 'course_maintenance');
156
define('TOOL_SURVEY', 'survey');
157
define('TOOL_WIKI', 'wiki');
158
define('TOOL_GLOSSARY', 'glossary');
159
define('TOOL_GRADEBOOK', 'gradebook');
160
define('TOOL_NOTEBOOK', 'notebook');
161
define('TOOL_ATTENDANCE', 'attendance');
162
define('TOOL_COURSE_PROGRESS', 'course_progress');
163
define('TOOL_PORTFOLIO', 'portfolio');
164
define('TOOL_PORTFOLIO_COMMENT', 'portfolio_comment');
165
define('TOOL_PLAGIARISM', 'compilatio');
166
define('TOOL_XAPI', 'xapi');
167
168
// CONSTANTS defining Chamilo interface sections
169
define('SECTION_CAMPUS', 'mycampus');
170
define('SECTION_COURSES', 'mycourses');
171
define('SECTION_CATALOG', 'catalog');
172
define('SECTION_MYPROFILE', 'myprofile');
173
define('SECTION_MYAGENDA', 'myagenda');
174
define('SECTION_COURSE_ADMIN', 'course_admin');
175
define('SECTION_PLATFORM_ADMIN', 'platform_admin');
176
define('SECTION_MYGRADEBOOK', 'mygradebook');
177
define('SECTION_TRACKING', 'session_my_space');
178
define('SECTION_SOCIAL', 'social-network');
179
define('SECTION_DASHBOARD', 'dashboard');
180
define('SECTION_REPORTS', 'reports');
181
define('SECTION_GLOBAL', 'global');
182
define('SECTION_INCLUDE', 'include');
183
define('SECTION_CUSTOMPAGE', 'custompage');
184
185
// event logs types
186
define('LOG_COURSE_DELETE', 'course_deleted');
187
define('LOG_COURSE_CREATE', 'course_created');
188
define('LOG_COURSE_SETTINGS_CHANGED', 'course_settings_changed');
189
190
// @todo replace 'soc_gr' with social_group
191
define('LOG_GROUP_PORTAL_CREATED', 'soc_gr_created');
192
define('LOG_GROUP_PORTAL_UPDATED', 'soc_gr_updated');
193
define('LOG_GROUP_PORTAL_DELETED', 'soc_gr_deleted');
194
define('LOG_GROUP_PORTAL_USER_DELETE_ALL', 'soc_gr_delete_users');
195
196
define('LOG_GROUP_PORTAL_ID', 'soc_gr_portal_id');
197
define('LOG_GROUP_PORTAL_REL_USER_ARRAY', 'soc_gr_user_array');
198
199
define('LOG_GROUP_PORTAL_USER_SUBSCRIBED', 'soc_gr_u_subs');
200
define('LOG_GROUP_PORTAL_USER_UNSUBSCRIBED', 'soc_gr_u_unsubs');
201
define('LOG_GROUP_PORTAL_USER_UPDATE_ROLE', 'soc_gr_update_role');
202
203
define('LOG_MESSAGE_DATA', 'message_data');
204
define('LOG_MESSAGE_DELETE', 'msg_deleted');
205
define('LOG_WIKI_ACCESS', 'wiki_page_view');
206
define('LOG_WIKI_PAGE_ID', 'wiki_page_id');
207
208
const LOG_RESOURCE_LINK_DELETE = 'resource_link_deleted';
209
const LOG_RESOURCE_LINK_SOFT_DELETE = 'resource_link_soft_deleted';
210
const LOG_RESOURCE_NODE = 'resource_node_id';
211
const LOG_RESOURCE_LINK = 'resource_link_id';
212
const LOG_RESOURCE_NODE_AND_RESOURCE_LINK = 'resource_node_id_and_resource_link_id';
213
214
define('LOG_USER_DELETE', 'user_deleted');
215
define('LOG_USER_CREATOR_DELETED', 'user_creator_deleted');
216
define('LOG_USER_PREDELETE', 'user_predeleted');
217
define('LOG_USER_CREATE', 'user_created');
218
define('LOG_USER_UPDATE', 'user_updated');
219
define('LOG_USER_PASSWORD_UPDATE', 'user_password_updated');
220
define('LOG_USER_ENABLE', 'user_enable');
221
define('LOG_USER_DISABLE', 'user_disable');
222
define('LOG_USER_ANONYMIZE', 'user_anonymized');
223
define('LOG_USER_FIELD_CREATE', 'user_field_created');
224
define('LOG_USER_FIELD_DELETE', 'user_field_deleted');
225
define('LOG_SESSION_CREATE', 'session_created');
226
define('LOG_SESSION_DELETE', 'session_deleted');
227
define('LOG_SESSION_ADD_USER_COURSE', 'session_add_user_course');
228
define('LOG_SESSION_DELETE_USER_COURSE', 'session_delete_user_course');
229
define('LOG_SESSION_ADD_USER', 'session_add_user');
230
define('LOG_SESSION_DELETE_USER', 'session_delete_user');
231
define('LOG_SESSION_ADD_COURSE', 'session_add_course');
232
define('LOG_SESSION_DELETE_COURSE', 'session_delete_course');
233
define('LOG_SESSION_CATEGORY_CREATE', 'session_cat_created'); //changed in 1.9.8
234
define('LOG_SESSION_CATEGORY_DELETE', 'session_cat_deleted'); //changed in 1.9.8
235
define('LOG_CONFIGURATION_SETTINGS_CHANGE', 'settings_changed');
236
define('LOG_PLATFORM_LANGUAGE_CHANGE', 'platform_lng_changed'); //changed in 1.9.8
237
define('LOG_SUBSCRIBE_USER_TO_COURSE', 'user_subscribed');
238
define('LOG_UNSUBSCRIBE_USER_FROM_COURSE', 'user_unsubscribed');
239
define('LOG_ATTEMPTED_FORCED_LOGIN', 'attempted_forced_login');
240
define('LOG_PLUGIN_CHANGE', 'plugin_changed');
241
define('LOG_HOMEPAGE_CHANGED', 'homepage_changed');
242
define('LOG_PROMOTION_CREATE', 'promotion_created');
243
define('LOG_PROMOTION_DELETE', 'promotion_deleted');
244
define('LOG_CAREER_CREATE', 'career_created');
245
define('LOG_CAREER_DELETE', 'career_deleted');
246
define('LOG_USER_PERSONAL_DOC_DELETED', 'user_doc_deleted');
247
//define('LOG_WIKI_ACCESS', 'wiki_page_view');
248
// All results from an exercise
249
define('LOG_EXERCISE_RESULT_DELETE', 'exe_result_deleted');
250
// Logs only the one attempt
251
define('LOG_EXERCISE_ATTEMPT_DELETE', 'exe_attempt_deleted');
252
define('LOG_LP_ATTEMPT_DELETE', 'lp_attempt_deleted');
253
define('LOG_QUESTION_RESULT_DELETE', 'qst_attempt_deleted');
254
define('LOG_QUESTION_SCORE_UPDATE', 'score_attempt_updated');
255
256
define('LOG_MY_FOLDER_CREATE', 'my_folder_created');
257
define('LOG_MY_FOLDER_CHANGE', 'my_folder_changed');
258
define('LOG_MY_FOLDER_DELETE', 'my_folder_deleted');
259
define('LOG_MY_FOLDER_COPY', 'my_folder_copied');
260
define('LOG_MY_FOLDER_CUT', 'my_folder_cut');
261
define('LOG_MY_FOLDER_PASTE', 'my_folder_pasted');
262
define('LOG_MY_FOLDER_UPLOAD', 'my_folder_uploaded');
263
264
// Event logs data types (max 20 chars)
265
define('LOG_COURSE_CODE', 'course_code');
266
define('LOG_COURSE_ID', 'course_id');
267
define('LOG_USER_ID', 'user_id');
268
define('LOG_USER_OBJECT', 'user_object');
269
define('LOG_USER_FIELD_VARIABLE', 'user_field_variable');
270
define('LOG_SESSION_ID', 'session_id');
271
272
define('LOG_QUESTION_ID', 'question_id');
273
define('LOG_SESSION_CATEGORY_ID', 'session_category_id');
274
define('LOG_CONFIGURATION_SETTINGS_CATEGORY', 'settings_category');
275
define('LOG_CONFIGURATION_SETTINGS_VARIABLE', 'settings_variable');
276
define('LOG_PLATFORM_LANGUAGE', 'default_platform_language');
277
define('LOG_PLUGIN_UPLOAD', 'plugin_upload');
278
define('LOG_PLUGIN_ENABLE', 'plugin_enable');
279
define('LOG_PLUGIN_SETTINGS_CHANGE', 'plugin_settings_change');
280
define('LOG_CAREER_ID', 'career_id');
281
define('LOG_PROMOTION_ID', 'promotion_id');
282
define('LOG_GRADEBOOK_LOCKED', 'gradebook_locked');
283
define('LOG_GRADEBOOK_UNLOCKED', 'gradebook_unlocked');
284
define('LOG_GRADEBOOK_ID', 'gradebook_id');
285
//define('LOG_WIKI_PAGE_ID', 'wiki_page_id');
286
define('LOG_EXERCISE_ID', 'exercise_id');
287
define('LOG_EXERCISE_AND_USER_ID', 'exercise_and_user_id');
288
define('LOG_LP_ID', 'lp_id');
289
define('LOG_EXERCISE_ATTEMPT_QUESTION_ID', 'exercise_a_q_id');
290
define('LOG_EXERCISE_ATTEMPT', 'exe_id');
291
292
define('LOG_WORK_DIR_DELETE', 'work_dir_delete');
293
define('LOG_WORK_FILE_DELETE', 'work_file_delete');
294
define('LOG_WORK_DATA', 'work_data_array');
295
296
define('LOG_MY_FOLDER_PATH', 'path');
297
define('LOG_MY_FOLDER_NEW_PATH', 'new_path');
298
299
define('LOG_TERM_CONDITION_ACCEPTED', 'term_condition_accepted');
300
define('LOG_USER_CONFIRMED_EMAIL', 'user_confirmed_email');
301
define('LOG_USER_REMOVED_LEGAL_ACCEPT', 'user_removed_legal_accept');
302
303
define('LOG_USER_DELETE_ACCOUNT_REQUEST', 'user_delete_account_request');
304
305
define('LOG_QUESTION_CREATED', 'question_created');
306
define('LOG_QUESTION_UPDATED', 'question_updated');
307
define('LOG_QUESTION_DELETED', 'question_deleted');
308
define('LOG_QUESTION_REMOVED_FROM_QUIZ', 'question_removed_from_quiz');
309
310
define('LOG_SURVEY_ID', 'survey_id');
311
define('LOG_SURVEY_CREATED', 'survey_created');
312
define('LOG_SURVEY_DELETED', 'survey_deleted');
313
define('LOG_SURVEY_CLEAN_RESULTS', 'survey_clean_results');
314
define('USERNAME_PURIFIER', '/[^0-9A-Za-z_\.@\$-]/');
315
316
//used when login_is_email setting is true
317
define('USERNAME_PURIFIER_MAIL', '/[^0-9A-Za-z_\.@]/');
318
define('USERNAME_PURIFIER_SHALLOW', '/\s/');
319
320
// This constant is a result of Windows OS detection, it has a boolean value:
321
// true whether the server runs on Windows OS, false otherwise.
322
define('IS_WINDOWS_OS', api_is_windows_os());
323
324
// Patterns for processing paths. Examples.
325
define('REPEATED_SLASHES_PURIFIER', '/\/{2,}/'); // $path = preg_replace(REPEATED_SLASHES_PURIFIER, '/', $path);
326
define('VALID_WEB_PATH', '/https?:\/\/[^\/]*(\/.*)?/i'); // $is_valid_path = preg_match(VALID_WEB_PATH, $path);
327
// $new_path = preg_replace(VALID_WEB_SERVER_BASE, $new_base, $path);
328
define('VALID_WEB_SERVER_BASE', '/https?:\/\/[^\/]*/i');
329
// Constants for api_get_path() and api_get_path_type(), etc. - registered path types.
330
// basic (leaf elements)
331
define('REL_CODE_PATH', 'REL_CODE_PATH');
332
define('REL_COURSE_PATH', 'REL_COURSE_PATH');
333
define('REL_HOME_PATH', 'REL_HOME_PATH');
334
335
// Constants for api_get_path() and api_get_path_type(), etc. - registered path types.
336
define('WEB_PATH', 'WEB_PATH');
337
define('SYS_PATH', 'SYS_PATH');
338
define('SYMFONY_SYS_PATH', 'SYMFONY_SYS_PATH');
339
340
define('REL_PATH', 'REL_PATH');
341
define('WEB_COURSE_PATH', 'WEB_COURSE_PATH');
342
define('WEB_CODE_PATH', 'WEB_CODE_PATH');
343
define('SYS_CODE_PATH', 'SYS_CODE_PATH');
344
define('SYS_LANG_PATH', 'SYS_LANG_PATH');
345
define('WEB_IMG_PATH', 'WEB_IMG_PATH');
346
define('WEB_CSS_PATH', 'WEB_CSS_PATH');
347
define('WEB_PUBLIC_PATH', 'WEB_PUBLIC_PATH');
348
define('SYS_CSS_PATH', 'SYS_CSS_PATH');
349
define('SYS_PLUGIN_PATH', 'SYS_PLUGIN_PATH');
350
define('WEB_PLUGIN_PATH', 'WEB_PLUGIN_PATH');
351
define('WEB_PLUGIN_ASSET_PATH', 'WEB_PLUGIN_ASSET_PATH');
352
define('SYS_ARCHIVE_PATH', 'SYS_ARCHIVE_PATH');
353
define('WEB_ARCHIVE_PATH', 'WEB_ARCHIVE_PATH');
354
define('LIBRARY_PATH', 'LIBRARY_PATH');
355
define('CONFIGURATION_PATH', 'CONFIGURATION_PATH');
356
define('WEB_LIBRARY_PATH', 'WEB_LIBRARY_PATH');
357
define('WEB_LIBRARY_JS_PATH', 'WEB_LIBRARY_JS_PATH');
358
define('WEB_AJAX_PATH', 'WEB_AJAX_PATH');
359
define('SYS_TEST_PATH', 'SYS_TEST_PATH');
360
define('SYS_TEMPLATE_PATH', 'SYS_TEMPLATE_PATH');
361
define('SYS_PUBLIC_PATH', 'SYS_PUBLIC_PATH');
362
define('SYS_FONTS_PATH', 'SYS_FONTS_PATH');
363
364
// Relations type with Course manager
365
define('COURSE_RELATION_TYPE_COURSE_MANAGER', 1);
366
367
// Relations type with Human resources manager
368
define('COURSE_RELATION_TYPE_RRHH', 1);
369
370
// User image sizes
371
define('USER_IMAGE_SIZE_ORIGINAL', 1);
372
define('USER_IMAGE_SIZE_BIG', 2);
373
define('USER_IMAGE_SIZE_MEDIUM', 3);
374
define('USER_IMAGE_SIZE_SMALL', 4);
375
376
// Gradebook link constants
377
// Please do not change existing values, they are used in the database !
378
define('GRADEBOOK_ITEM_LIMIT', 1000);
379
380
define('LINK_EXERCISE', 1);
381
define('LINK_DROPBOX', 2);
382
define('LINK_STUDENTPUBLICATION', 3);
383
define('LINK_LEARNPATH', 4);
384
define('LINK_FORUM_THREAD', 5);
385
//define('LINK_WORK',6);
386
define('LINK_ATTENDANCE', 7);
387
define('LINK_SURVEY', 8);
388
define('LINK_HOTPOTATOES', 9);
389
define('LINK_PORTFOLIO', 10);
390
391
// Score display types constants
392
define('SCORE_DIV', 1); // X / Y
393
define('SCORE_PERCENT', 2); // XX %
394
define('SCORE_DIV_PERCENT', 3); // X / Y (XX %)
395
define('SCORE_AVERAGE', 4); // XX %
396
define('SCORE_DECIMAL', 5); // 0.50  (X/Y)
397
define('SCORE_BAR', 6); // Uses the Display::bar_progress function
398
define('SCORE_SIMPLE', 7); // X
399
define('SCORE_IGNORE_SPLIT', 8); //  ??
400
define('SCORE_DIV_PERCENT_WITH_CUSTOM', 9); // X / Y (XX %) - Good!
401
define('SCORE_CUSTOM', 10); // Good!
402
define('SCORE_DIV_SIMPLE_WITH_CUSTOM', 11); // X - Good!
403
define('SCORE_DIV_SIMPLE_WITH_CUSTOM_LETTERS', 12); // X - Good!
404
define('SCORE_ONLY_SCORE', 13); // X - Good!
405
define('SCORE_NUMERIC', 14);
406
407
define('SCORE_BOTH', 1);
408
define('SCORE_ONLY_DEFAULT', 2);
409
define('SCORE_ONLY_CUSTOM', 3);
410
411
// From display.lib.php
412
413
define('MAX_LENGTH_BREADCRUMB', 100);
414
define('ICON_SIZE_ATOM', 8);
415
define('ICON_SIZE_TINY', 16);
416
define('ICON_SIZE_SMALL', 22);
417
define('ICON_SIZE_MEDIUM', 32);
418
define('ICON_SIZE_LARGE', 48);
419
define('ICON_SIZE_BIG', 64);
420
define('ICON_SIZE_HUGE', 128);
421
define('SHOW_TEXT_NEAR_ICONS', false);
422
423
// Session catalog
424
define('CATALOG_COURSES', 0);
425
define('CATALOG_SESSIONS', 1);
426
define('CATALOG_COURSES_SESSIONS', 2);
427
428
// Hook type events, pre-process and post-process.
429
// All means to be executed for both hook event types
430
define('HOOK_EVENT_TYPE_PRE', 0);
431
define('HOOK_EVENT_TYPE_POST', 1);
432
define('HOOK_EVENT_TYPE_ALL', 10);
433
434
// Group permissions
435
define('GROUP_PERMISSION_OPEN', '1');
436
define('GROUP_PERMISSION_CLOSED', '2');
437
438
// Group user permissions
439
define('GROUP_USER_PERMISSION_ADMIN', 1); // the admin of a group
440
define('GROUP_USER_PERMISSION_READER', 2); // a normal user
441
define('GROUP_USER_PERMISSION_PENDING_INVITATION', 3); // When an admin/moderator invites a user
442
define('GROUP_USER_PERMISSION_PENDING_INVITATION_SENT_BY_USER', 4); // an user joins a group
443
define('GROUP_USER_PERMISSION_MODERATOR', 5); // a moderator
444
define('GROUP_USER_PERMISSION_ANONYMOUS', 6); // an anonymous user
445
define('GROUP_USER_PERMISSION_HRM', 7); // a human resources manager
446
447
define('GROUP_IMAGE_SIZE_ORIGINAL', 1);
448
define('GROUP_IMAGE_SIZE_BIG', 2);
449
define('GROUP_IMAGE_SIZE_MEDIUM', 3);
450
define('GROUP_IMAGE_SIZE_SMALL', 4);
451
define('GROUP_TITLE_LENGTH', 50);
452
453
// Exercise
454
// @todo move into a class
455
define('ALL_ON_ONE_PAGE', 1);
456
define('ONE_PER_PAGE', 2);
457
458
define('EXERCISE_FEEDBACK_TYPE_END', 0); //Feedback 		 - show score and expected answers
459
define('EXERCISE_FEEDBACK_TYPE_DIRECT', 1); //DirectFeedback - Do not show score nor answers
460
define('EXERCISE_FEEDBACK_TYPE_EXAM', 2); // NoFeedback 	 - Show score only
461
define('EXERCISE_FEEDBACK_TYPE_POPUP', 3); // Popup BT#15827
462
463
define('RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS', 0); //show score and expected answers
464
define('RESULT_DISABLE_NO_SCORE_AND_EXPECTED_ANSWERS', 1); //Do not show score nor answers
465
define('RESULT_DISABLE_SHOW_SCORE_ONLY', 2); //Show score only
466
define('RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES', 3); //Show final score only with categories
467
define('RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT', 4);
468
define('RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK', 5);
469
define('RESULT_DISABLE_RANKING', 6);
470
define('RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER', 7);
471
define('RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING', 8);
472
define('RESULT_DISABLE_RADAR', 9);
473
define('RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK', 10);
474
475
define('EXERCISE_MAX_NAME_SIZE', 80);
476
477
// Question types (edit next array as well when adding values)
478
// @todo move into a class
479
define('UNIQUE_ANSWER', 1);
480
define('MULTIPLE_ANSWER', 2);
481
define('FILL_IN_BLANKS', 3);
482
define('MATCHING', 4);
483
define('FREE_ANSWER', 5);
484
define('HOT_SPOT', 6);
485
define('HOT_SPOT_ORDER', 7);
486
define('HOT_SPOT_DELINEATION', 8);
487
define('MULTIPLE_ANSWER_COMBINATION', 9);
488
define('UNIQUE_ANSWER_NO_OPTION', 10);
489
define('MULTIPLE_ANSWER_TRUE_FALSE', 11);
490
define('MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE', 12);
491
define('ORAL_EXPRESSION', 13);
492
define('GLOBAL_MULTIPLE_ANSWER', 14);
493
define('MEDIA_QUESTION', 15);
494
define('CALCULATED_ANSWER', 16);
495
define('UNIQUE_ANSWER_IMAGE', 17);
496
define('DRAGGABLE', 18);
497
define('MATCHING_DRAGGABLE', 19);
498
define('ANNOTATION', 20);
499
define('READING_COMPREHENSION', 21);
500
define('MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY', 22);
501
define('UPLOAD_ANSWER', 23);
502
define('MATCHING_COMBINATION', 24);
503
define('MATCHING_DRAGGABLE_COMBINATION', 25);
504
define('HOT_SPOT_COMBINATION', 26);
505
define('FILL_IN_BLANKS_COMBINATION', 27);
506
define('MULTIPLE_ANSWER_DROPDOWN_COMBINATION', 28);
507
define('MULTIPLE_ANSWER_DROPDOWN', 29);
508
define('ANSWER_IN_OFFICE_DOC', 30);
509
define('PAGE_BREAK', 31);
510
511
define('EXERCISE_CATEGORY_RANDOM_SHUFFLED', 1);
512
define('EXERCISE_CATEGORY_RANDOM_ORDERED', 2);
513
define('EXERCISE_CATEGORY_RANDOM_DISABLED', 0);
514
515
// Question selection type
516
define('EX_Q_SELECTION_ORDERED', 1);
517
define('EX_Q_SELECTION_RANDOM', 2);
518
define('EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED', 3);
519
define('EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED', 4);
520
define('EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM', 5);
521
define('EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM', 6);
522
define('EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED', 7);
523
define('EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED', 8);
524
define('EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED', 9);
525
define('EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM', 10);
526
527
// Used to save the skill_rel_item table
528
define('ITEM_TYPE_EXERCISE', 1);
529
define('ITEM_TYPE_HOTPOTATOES', 2);
530
define('ITEM_TYPE_LINK', 3);
531
define('ITEM_TYPE_LEARNPATH', 4);
532
define('ITEM_TYPE_GRADEBOOK', 5);
533
define('ITEM_TYPE_STUDENT_PUBLICATION', 6);
534
//define('ITEM_TYPE_FORUM', 7);
535
define('ITEM_TYPE_ATTENDANCE', 8);
536
define('ITEM_TYPE_SURVEY', 9);
537
define('ITEM_TYPE_FORUM_THREAD', 10);
538
define('ITEM_TYPE_PORTFOLIO', 11);
539
540
// Course description blocks.
541
define('ADD_BLOCK', 8);
542
543
// one big string with all question types, for the validator in pear/HTML/QuickForm/Rule/QuestionType
544
define(
545
    'QUESTION_TYPES',
546
    UNIQUE_ANSWER.':'.
547
    MULTIPLE_ANSWER.':'.
548
    FILL_IN_BLANKS.':'.
549
    MATCHING.':'.
550
    FREE_ANSWER.':'.
551
    HOT_SPOT.':'.
552
    HOT_SPOT_ORDER.':'.
553
    HOT_SPOT_DELINEATION.':'.
554
    MULTIPLE_ANSWER_COMBINATION.':'.
555
    UNIQUE_ANSWER_NO_OPTION.':'.
556
    MULTIPLE_ANSWER_TRUE_FALSE.':'.
557
    MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE.':'.
558
    ORAL_EXPRESSION.':'.
559
    GLOBAL_MULTIPLE_ANSWER.':'.
560
    MEDIA_QUESTION.':'.
561
    CALCULATED_ANSWER.':'.
562
    UNIQUE_ANSWER_IMAGE.':'.
563
    DRAGGABLE.':'.
564
    MATCHING_DRAGGABLE.':'.
565
    MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY.':'.
566
    ANNOTATION
567
);
568
569
//Some alias used in the QTI exports
570
define('MCUA', 1);
571
define('TF', 1);
572
define('MCMA', 2);
573
define('FIB', 3);
574
575
// Message
576
define('MESSAGE_STATUS_INVITATION_PENDING', 5);
577
define('MESSAGE_STATUS_INVITATION_ACCEPTED', 6);
578
define('MESSAGE_STATUS_INVITATION_DENIED', 7);
579
define('MESSAGE_STATUS_WALL', 8);
580
581
define('MESSAGE_STATUS_WALL_DELETE', 9);
582
define('MESSAGE_STATUS_WALL_POST', 10);
583
584
define('MESSAGE_STATUS_FORUM', 12);
585
define('MESSAGE_STATUS_PROMOTED', 13);
586
587
// Images
588
define('IMAGE_WALL_SMALL_SIZE', 200);
589
define('IMAGE_WALL_MEDIUM_SIZE', 500);
590
define('IMAGE_WALL_BIG_SIZE', 2000);
591
define('IMAGE_WALL_SMALL', 'small');
592
define('IMAGE_WALL_MEDIUM', 'medium');
593
define('IMAGE_WALL_BIG', 'big');
594
595
// Social PLUGIN PLACES
596
define('SOCIAL_LEFT_PLUGIN', 1);
597
define('SOCIAL_CENTER_PLUGIN', 2);
598
define('SOCIAL_RIGHT_PLUGIN', 3);
599
define('CUT_GROUP_NAME', 50);
600
601
/**
602
 * FormValidator Filter.
603
 */
604
define('NO_HTML', 1);
605
define('STUDENT_HTML', 2);
606
define('TEACHER_HTML', 3);
607
define('STUDENT_HTML_FULLPAGE', 4);
608
define('TEACHER_HTML_FULLPAGE', 5);
609
610
// Timeline
611
define('TIMELINE_STATUS_ACTIVE', '1');
612
define('TIMELINE_STATUS_INACTIVE', '2');
613
614
// Event email template class
615
define('EVENT_EMAIL_TEMPLATE_ACTIVE', 1);
616
define('EVENT_EMAIL_TEMPLATE_INACTIVE', 0);
617
618
// Course home
619
define('SHORTCUTS_HORIZONTAL', 0);
620
define('SHORTCUTS_VERTICAL', 1);
621
622
// Course copy
623
define('FILE_SKIP', 1);
624
define('FILE_RENAME', 2);
625
define('FILE_OVERWRITE', 3);
626
define('UTF8_CONVERT', false); //false by default
627
628
define('DOCUMENT', 'file');
629
define('FOLDER', 'folder');
630
631
define('RESOURCE_ASSET', 'asset');
632
define('RESOURCE_DOCUMENT', 'document');
633
define('RESOURCE_GLOSSARY', 'glossary');
634
define('RESOURCE_EVENT', 'calendar_event');
635
define('RESOURCE_LINK', 'link');
636
define('RESOURCE_COURSEDESCRIPTION', 'course_description');
637
define('RESOURCE_LEARNPATH', 'learnpath');
638
define('RESOURCE_LEARNPATH_CATEGORY', 'learnpath_category');
639
define('RESOURCE_ANNOUNCEMENT', 'announcement');
640
define('RESOURCE_FORUM', 'forum');
641
define('RESOURCE_FORUMTOPIC', 'thread');
642
define('RESOURCE_FORUMPOST', 'post');
643
define('RESOURCE_QUIZ', 'quiz');
644
define('RESOURCE_TEST_CATEGORY', 'test_category');
645
define('RESOURCE_QUIZQUESTION', 'Exercise_Question');
646
define('RESOURCE_TOOL_INTRO', 'Tool introduction');
647
define('RESOURCE_LINKCATEGORY', 'Link_Category');
648
define('RESOURCE_FORUMCATEGORY', 'Forum_Category');
649
define('RESOURCE_SCORM', 'Scorm');
650
define('RESOURCE_SURVEY', 'survey');
651
define('RESOURCE_SURVEYQUESTION', 'survey_question');
652
define('RESOURCE_SURVEYINVITATION', 'survey_invitation');
653
define('RESOURCE_WIKI', 'wiki');
654
define('RESOURCE_THEMATIC', 'thematic');
655
define('RESOURCE_ATTENDANCE', 'attendance');
656
define('RESOURCE_WORK', 'work');
657
define('RESOURCE_SESSION_COURSE', 'session_course');
658
define('RESOURCE_GRADEBOOK', 'gradebook');
659
define('ADD_THEMATIC_PLAN', 6);
660
661
// Max online users to show per page (whoisonline)
662
define('MAX_ONLINE_USERS', 12);
663
664
define('TOOL_AUTHORING', 'toolauthoring');
665
define('TOOL_INTERACTION', 'toolinteraction');
666
define('TOOL_COURSE_PLUGIN', 'toolcourseplugin'); //all plugins that can be enabled in courses
667
define('TOOL_ADMIN', 'tooladmin');
668
define('TOOL_ADMIN_PLATFORM', 'tooladminplatform');
669
define('TOOL_DRH', 'tool_drh');
670
define('TOOL_STUDENT_VIEW', 'toolstudentview');
671
define('TOOL_ADMIN_VISIBLE', 'tooladminvisible');
672
673
// Search settings (from main/inc/lib/search/IndexableChunk.class.php )
674
// some constants to avoid serialize string keys on serialized data array
675
define('SE_COURSE_ID', 0);
676
define('SE_TOOL_ID', 1);
677
define('SE_DATA', 2);
678
define('SE_USER', 3);
679
680
// in some cases we need top differenciate xapian documents of the same tool
681
define('SE_DOCTYPE_EXERCISE_EXERCISE', 0);
682
define('SE_DOCTYPE_EXERCISE_QUESTION', 1);
683
684
// xapian prefixes
685
define('XAPIAN_PREFIX_COURSEID', 'C');
686
define('XAPIAN_PREFIX_TOOLID', 'O');
687
688
// User active field constants
689
define('USER_ACTIVE', 1);
690
define('USER_INACTIVE', 0);
691
define('USER_INACTIVE_AUTOMATIC', -1);
692
define('USER_SOFT_DELETED', -2);
693
694
/**
695
 * Returns a path to a certain resource within Chamilo.
696
 *
697
 * @param string $path A path which type is to be converted. Also, it may be a defined constant for a path.
698
 *
699
 * @return string the requested path or the converted path
700
 *
701
 * Notes about the current behaviour model:
702
 * 1. Windows back-slashes are converted to slashes in the result.
703
 * 2. A semi-absolute web-path is detected by its leading slash. On Linux systems, absolute system paths start with
704
 * a slash too, so an additional check about presence of leading system server base is implemented. For example, the function is
705
 * able to distinguish type difference between /var/www/chamilo/courses/ (SYS) and /chamilo/courses/ (REL).
706
 * 3. The function api_get_path() returns only these three types of paths, which in some sense are absolute. The function has
707
 * no a mechanism for processing relative web/system paths, such as: lesson01.html, ./lesson01.html, ../css/my_styles.css.
708
 * It has not been identified as needed yet.
709
 * 4. Also, resolving the meta-symbols "." and ".." within paths has not been implemented, it is to be identified as needed.
710
 *
711
 * Vchamilo changes : allow using an alternate configuration
712
 * to get vchamilo  instance paths
713
 */
714
function api_get_path($path = '', $configuration = [])
715
{
716
    global $paths;
717
718
    // get proper configuration data if exists
719
    global $_configuration;
720
721
    $emptyConfigurationParam = false;
722
    if (empty($configuration)) {
723
        $configuration = (array) $_configuration;
724
        $emptyConfigurationParam = true;
725
    }
726
727
    $root_sys = Container::getProjectDir();
728
    $root_web = '';
729
    if (isset(Container::$container)) {
730
        $root_web = Container::$container->get('router')->generate(
731
            'index',
732
            [],
733
            UrlGeneratorInterface::ABSOLUTE_URL
734
        );
735
    }
736
737
    /*if (api_get_multiple_access_url()) {
738
        // To avoid that the api_get_access_url() function fails since global.inc.php also calls the main_api.lib.php
739
        if (isset($configuration['access_url']) && !empty($configuration['access_url'])) {
740
            // We look into the DB the function api_get_access_url
741
            $urlInfo = api_get_access_url($configuration['access_url']);
742
            // Avoid default value
743
            $defaultValues = ['http://localhost/', 'https://localhost/'];
744
            if (!empty($urlInfo['url']) && !in_array($urlInfo['url'], $defaultValues)) {
745
                $root_web = 1 == $urlInfo['active'] ? $urlInfo['url'] : $configuration['root_web'];
746
            }
747
        }
748
    }*/
749
750
    $paths = [
751
        WEB_PATH => $root_web,
752
        SYMFONY_SYS_PATH => $root_sys,
753
        SYS_PATH => $root_sys.'public/',
754
        REL_PATH => '',
755
        CONFIGURATION_PATH => 'app/config/',
756
        LIBRARY_PATH => $root_sys.'public/main/inc/lib/',
757
758
        REL_COURSE_PATH => '',
759
        REL_CODE_PATH => '/main/',
760
761
        SYS_CODE_PATH => $root_sys.'public/main/',
762
        SYS_CSS_PATH => $root_sys.'public/build/css/',
763
        SYS_PLUGIN_PATH => $root_sys.'public/plugin/',
764
        SYS_ARCHIVE_PATH => $root_sys.'var/cache/',
765
        SYS_TEST_PATH => $root_sys.'tests/',
766
        SYS_TEMPLATE_PATH => $root_sys.'public/main/template/',
767
        SYS_PUBLIC_PATH => $root_sys.'public/',
768
        SYS_FONTS_PATH => $root_sys.'public/fonts/',
769
770
        WEB_CODE_PATH => $root_web.'main/',
771
        WEB_PLUGIN_ASSET_PATH => $root_web.'plugins/',
772
        WEB_COURSE_PATH => $root_web.'course/',
773
        WEB_IMG_PATH => $root_web.'img/',
774
        WEB_CSS_PATH => $root_web.'build/css/',
775
        WEB_AJAX_PATH => $root_web.'main/inc/ajax/',
776
        WEB_LIBRARY_PATH => $root_web.'main/inc/lib/',
777
        WEB_LIBRARY_JS_PATH => $root_web.'main/inc/lib/javascript/',
778
        WEB_PLUGIN_PATH => $root_web.'plugin/',
779
        WEB_PUBLIC_PATH => $root_web,
780
    ];
781
782
    $root_rel = '';
783
784
    global $virtualChamilo;
785
    if (!empty($virtualChamilo)) {
786
        $paths[SYS_ARCHIVE_PATH] = api_add_trailing_slash($virtualChamilo[SYS_ARCHIVE_PATH]);
787
        //$paths[SYS_UPLOAD_PATH] = api_add_trailing_slash($virtualChamilo[SYS_UPLOAD_PATH]);
788
        //$paths[$root_web][WEB_UPLOAD_PATH] = api_add_trailing_slash($virtualChamilo[WEB_UPLOAD_PATH]);
789
        $paths[WEB_ARCHIVE_PATH] = api_add_trailing_slash($virtualChamilo[WEB_ARCHIVE_PATH]);
790
        //$paths[$root_web][WEB_COURSE_PATH] = api_add_trailing_slash($virtualChamilo[WEB_COURSE_PATH]);
791
792
        // WEB_UPLOAD_PATH should be handle by apache htaccess in the vhost
793
794
        // RewriteEngine On
795
        // RewriteRule /app/upload/(.*)$ http://localhost/other/upload/my-chamilo111-net/$1 [QSA,L]
796
797
        //$paths[$root_web][WEB_UPLOAD_PATH] = api_add_trailing_slash($virtualChamilo[WEB_UPLOAD_PATH]);
798
        //$paths[$root_web][REL_PATH] = $virtualChamilo[REL_PATH];
799
        //$paths[$root_web][REL_COURSE_PATH] = $virtualChamilo[REL_COURSE_PATH];
800
    }
801
802
    $path = trim($path);
803
804
    // Retrieving a common-purpose path.
805
    if (isset($paths[$path])) {
806
        return $paths[$path];
807
    }
808
809
    return false;
810
}
811
812
/**
813
 * Adds to a given path a trailing slash if it is necessary (adds "/" character at the end of the string).
814
 *
815
 * @param string $path the input path
816
 *
817
 * @return string returns the modified path
818
 */
819
function api_add_trailing_slash($path)
820
{
821
    return '/' === substr($path, -1) ? $path : $path.'/';
822
}
823
824
/**
825
 * Removes from a given path the trailing slash if it is necessary (removes "/" character from the end of the string).
826
 *
827
 * @param string $path the input path
828
 *
829
 * @return string returns the modified path
830
 */
831
function api_remove_trailing_slash($path)
832
{
833
    return '/' === substr($path, -1) ? substr($path, 0, -1) : $path;
834
}
835
836
/**
837
 * Checks the RFC 3986 syntax of a given URL.
838
 *
839
 * @param string $url      the URL to be checked
840
 * @param bool   $absolute whether the URL is absolute (beginning with a scheme such as "http:")
841
 *
842
 * @return string|false Returns the URL if it is valid, FALSE otherwise.
843
 *                      This function is an adaptation from the function valid_url(), Drupal CMS.
844
 *
845
 * @see http://drupal.org
846
 * Note: The built-in function filter_var($urs, FILTER_VALIDATE_URL) has a bug for some versions of PHP.
847
 * @see http://bugs.php.net/51192
848
 */
849
function api_valid_url($url, $absolute = false)
850
{
851
    if ($absolute) {
852
        if (preg_match("
853
            /^                                                      # Start at the beginning of the text
854
            (?:ftp|https?|feed):\/\/                                # Look for ftp, http, https or feed schemes
855
            (?:                                                     # Userinfo (optional) which is typically
856
                (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)*    # a username or a username and password
857
                (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@        # combination
858
            )?
859
            (?:
860
                (?:[a-z0-9\-\.]|%[0-9a-f]{2})+                      # A domain name or a IPv4 address
861
                |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\])       # or a well formed IPv6 address
862
            )
863
            (?::[0-9]+)?                                            # Server port number (optional)
864
            (?:[\/|\?]
865
                (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional)
866
            *)?
867
            $/xi", $url)) {
868
            return $url;
869
        }
870
871
        return false;
872
    } else {
873
        return preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url) ? $url : false;
874
    }
875
}
876
877
/**
878
 * Checks whether a given string looks roughly like an email address.
879
 *
880
 * @param string $address the e-mail address to be checked
881
 *
882
 * @return mixed returns the e-mail if it is valid, FALSE otherwise
883
 */
884
function api_valid_email($address)
885
{
886
    return filter_var($address, FILTER_VALIDATE_EMAIL);
887
}
888
889
/**
890
 * Function used to protect a course script.
891
 * The function blocks access when
892
 * - there is no $_SESSION["_course"] defined; or
893
 * - $is_allowed_in_course is set to false (this depends on the course
894
 * visibility and user status).
895
 *
896
 * This is only the first proposal, test and improve!
897
 *
898
 * @param bool Option to print headers when displaying error message. Default: false
899
 * @param bool whether session admins should be allowed or not
900
 * @param string $checkTool check if tool is available for users (user, group)
901
 *
902
 * @return bool True if the user has access to the current course or is out of a course context, false otherwise
903
 *
904
 * @todo replace global variable
905
 *
906
 * @author Roan Embrechts
907
 */
908
function api_protect_course_script($print_headers = false, $allow_session_admins = false, string $checkTool = '', $cid = null): bool
909
{
910
    $course_info = api_get_course_info();
911
    if (empty($course_info) && isset($_REQUEST['cid'])) {
912
        $course_info = api_get_course_info_by_id((int) $_REQUEST['cid']);
913
    }
914
915
    if (isset($cid)) {
916
        $course_info = api_get_course_info_by_id($cid);
917
    }
918
919
    if (empty($course_info)) {
920
        api_not_allowed($print_headers);
921
922
        return false;
923
    }
924
925
    if (api_is_drh()) {
926
        return true;
927
    }
928
929
    // Session admin has access to course
930
    $sessionAccess = ('true' === api_get_setting('session.session_admins_access_all_content'));
931
    if ($sessionAccess) {
932
        $allow_session_admins = true;
933
    }
934
935
    if (api_is_platform_admin($allow_session_admins)) {
936
        return true;
937
    }
938
939
    $isAllowedInCourse = api_is_allowed_in_course();
940
    $is_visible = false;
941
    if (isset($course_info) && isset($course_info['visibility'])) {
942
        switch ($course_info['visibility']) {
943
            default:
944
            case Course::CLOSED:
945
                // Completely closed: the course is only accessible to the teachers. - 0
946
                if ($isAllowedInCourse && api_get_user_id() && !api_is_anonymous()) {
947
                    $is_visible = true;
948
                }
949
                break;
950
            case Course::REGISTERED:
951
                // Private - access authorized to course members only - 1
952
                if ($isAllowedInCourse && api_get_user_id() && !api_is_anonymous()) {
953
                    $is_visible = true;
954
                }
955
                break;
956
            case Course::OPEN_PLATFORM:
957
                // Open - access allowed for users registered on the platform - 2
958
                if ($isAllowedInCourse && api_get_user_id() && !api_is_anonymous()) {
959
                    $is_visible = true;
960
                }
961
                break;
962
            case Course::OPEN_WORLD:
963
                //Open - access allowed for the whole world - 3
964
                $is_visible = true;
965
                break;
966
            case Course::HIDDEN:
967
                //Completely closed: the course is only accessible to the teachers. - 0
968
                if (api_is_platform_admin()) {
969
                    $is_visible = true;
970
                }
971
                break;
972
        }
973
974
        //If password is set and user is not registered to the course then the course is not visible
975
        if (false === $isAllowedInCourse &&
976
            isset($course_info['registration_code']) &&
977
            !empty($course_info['registration_code'])
978
        ) {
979
            $is_visible = false;
980
        }
981
    }
982
983
    if (!empty($checkTool)) {
984
        if (!api_is_allowed_to_edit(true, true, true)) {
985
            $toolInfo = api_get_tool_information_by_name($checkTool);
986
            if (!empty($toolInfo) && isset($toolInfo['visibility']) && 0 == $toolInfo['visibility']) {
987
                api_not_allowed(true);
988
989
                return false;
990
            }
991
        }
992
    }
993
994
    // Check session visibility
995
    $session_id = api_get_session_id();
996
997
    if (!empty($session_id)) {
998
        // $isAllowedInCourse was set in local.inc.php
999
        if (!$isAllowedInCourse) {
1000
            $is_visible = false;
1001
        }
1002
        // Check if course is inside session.
1003
        if (!SessionManager::relation_session_course_exist($session_id, $course_info['real_id'])) {
1004
            $is_visible = false;
1005
        }
1006
    }
1007
1008
    if (!$is_visible) {
1009
        api_not_allowed($print_headers);
1010
1011
        return false;
1012
    }
1013
1014
    $pluginHelper = Container::$container->get(PluginHelper::class);
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1014
    /** @scrutinizer ignore-call */ 
1015
    $pluginHelper = Container::$container->get(PluginHelper::class);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1015
1016
    if ($pluginHelper->isPluginEnabled('Positioning')) {
1017
        $plugin = $pluginHelper->loadLegacyPlugin('Positioning');
1018
1019
        if ($plugin && $plugin->get('block_course_if_initial_exercise_not_attempted') === 'true') {
1020
            $currentPath = $_SERVER['REQUEST_URI'];
1021
1022
            $allowedPatterns = [
1023
                '#^/course/\d+/home#',
1024
                '#^/plugin/Positioning/#',
1025
                '#^/main/course_home/#',
1026
                '#^/main/exercise/#',
1027
                '#^/main/inc/ajax/exercise.ajax.php#',
1028
            ];
1029
1030
            $isWhitelisted = false;
1031
            foreach ($allowedPatterns as $pattern) {
1032
                if (preg_match($pattern, $currentPath)) {
1033
                    $isWhitelisted = true;
1034
                    break;
1035
                }
1036
            }
1037
1038
            if (!$isWhitelisted) {
1039
                $initialData = $plugin->getInitialExercise($course_info['real_id'], $session_id);
1040
1041
                if (!empty($initialData['exercise_id'])) {
1042
                    $results = Event::getExerciseResultsByUser(
0 ignored issues
show
Bug introduced by
The method getExerciseResultsByUser() does not exist on Event. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1042
                    /** @scrutinizer ignore-call */ 
1043
                    $results = Event::getExerciseResultsByUser(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1043
                        api_get_user_id(),
1044
                        (int) $initialData['exercise_id'],
1045
                        $course_info['real_id'],
1046
                        $session_id
1047
                    );
1048
1049
                    if (empty($results)) {
1050
                        api_not_allowed($print_headers);
1051
                        return false;
1052
                    }
1053
                }
1054
            }
1055
        }
1056
    }
1057
1058
    api_block_inactive_user();
1059
1060
    return true;
1061
}
1062
1063
/**
1064
 * Function used to protect an admin script.
1065
 *
1066
 * The function blocks access when the user has no platform admin rights
1067
 * with an error message printed on default output
1068
 *
1069
 * @param bool Whether to allow session admins as well
1070
 * @param bool Whether to allow HR directors as well
1071
 * @param string An optional message (already passed through get_lang)
1072
 *
1073
 * @return bool True if user is allowed, false otherwise.
1074
 *              The function also outputs an error message in case not allowed
1075
 *
1076
 * @author Roan Embrechts (original author)
1077
 */
1078
function api_protect_admin_script($allow_sessions_admins = false, $allow_drh = false, $message = null)
1079
{
1080
    if (!api_is_platform_admin($allow_sessions_admins, $allow_drh)) {
1081
        api_not_allowed(true, $message);
1082
1083
        return false;
1084
    }
1085
    api_block_inactive_user();
1086
1087
    return true;
1088
}
1089
1090
/**
1091
 * Blocks inactive users with a currently active session from accessing more pages "live".
1092
 *
1093
 * @return bool Returns true if the feature is disabled or the user account is still enabled.
1094
 *              Returns false (and shows a message) if the feature is enabled *and* the user is disabled.
1095
 */
1096
function api_block_inactive_user()
1097
{
1098
    $data = true;
1099
    if ('true' !== api_get_setting('security.security_block_inactive_users_immediately')) {
1100
        return $data;
1101
    }
1102
1103
    $userId = api_get_user_id();
1104
    $homeUrl = api_get_path(WEB_PATH);
1105
    if (0 == $userId) {
1106
        return $data;
1107
    }
1108
1109
    $sql = "SELECT active FROM ".Database::get_main_table(TABLE_MAIN_USER)."
1110
            WHERE id = $userId";
1111
1112
    $result = Database::query($sql);
1113
    if (Database::num_rows($result) > 0) {
1114
        $result_array = Database::fetch_array($result);
1115
        $data = (bool) $result_array['active'];
1116
    }
1117
    if (false == $data) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1118
        $tpl = new Template(null, true, true, false, true, false, true, 0);
1119
        $tpl->assign('hide_login_link', 1);
1120
1121
        //api_not_allowed(true, get_lang('Account inactive'));
1122
        // we were not in a course, return to home page
1123
        $msg = Display::return_message(
1124
            get_lang('Account inactive'),
1125
            'error',
1126
            false
1127
        );
1128
1129
        $msg .= '<p class="text-center">
1130
                 <a class="btn btn--plain" href="'.$homeUrl.'">'.get_lang('Back to Home Page.').'</a></p>';
1131
1132
        $tpl->assign('content', $msg);
1133
        $tpl->display_one_col_template();
1134
        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...
1135
    }
1136
1137
    return $data;
1138
}
1139
1140
/**
1141
 * Function used to protect a teacher script.
1142
 * The function blocks access when the user has no teacher rights.
1143
 *
1144
 * @return bool True if the current user can access the script, false otherwise
1145
 *
1146
 * @author Yoselyn Castillo
1147
 */
1148
function api_protect_teacher_script()
1149
{
1150
    if (!api_is_allowed_to_edit()) {
1151
        api_not_allowed(true);
1152
1153
        return false;
1154
    }
1155
1156
    return true;
1157
}
1158
1159
/**
1160
 * Function used to prevent anonymous users from accessing a script.
1161
 *
1162
 * @param bool $printHeaders
1163
 *
1164
 * @return bool
1165
 */
1166
function api_block_anonymous_users($printHeaders = true)
1167
{
1168
    $isAuth = Container::getAuthorizationChecker()->isGranted('IS_AUTHENTICATED');
1169
1170
    if (false === $isAuth) {
1171
        api_not_allowed($printHeaders);
1172
1173
        return false;
1174
    }
1175
1176
    api_block_inactive_user();
1177
1178
    return true;
1179
}
1180
1181
/**
1182
 * Returns a rough evaluation of the browser's name and version based on very
1183
 * simple regexp.
1184
 *
1185
 * @return array with the navigator name and version ['name' => '...', 'version' => '...']
1186
 */
1187
function api_get_navigator()
1188
{
1189
    $navigator = 'Unknown';
1190
    $version = 0;
1191
1192
    if (!isset($_SERVER['HTTP_USER_AGENT'])) {
1193
        return ['name' => 'Unknown', 'version' => '0.0.0'];
1194
    }
1195
1196
    if (false !== strpos($_SERVER['HTTP_USER_AGENT'], 'Opera')) {
1197
        $navigator = 'Opera';
1198
        [, $version] = explode('Opera', $_SERVER['HTTP_USER_AGENT']);
1199
    } elseif (false !== strpos($_SERVER['HTTP_USER_AGENT'], 'Edge')) {
1200
        $navigator = 'Edge';
1201
        [, $version] = explode('Edge', $_SERVER['HTTP_USER_AGENT']);
1202
    } elseif (false !== strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
1203
        $navigator = 'Internet Explorer';
1204
        [, $version] = explode('MSIE ', $_SERVER['HTTP_USER_AGENT']);
1205
    } elseif (false !== strpos($_SERVER['HTTP_USER_AGENT'], 'Chrome')) {
1206
        $navigator = 'Chrome';
1207
        [, $version] = explode('Chrome', $_SERVER['HTTP_USER_AGENT']);
1208
    } elseif (false !== stripos($_SERVER['HTTP_USER_AGENT'], 'Safari')) {
1209
        $navigator = 'Safari';
1210
        if (false !== stripos($_SERVER['HTTP_USER_AGENT'], 'Version/')) {
1211
            // If this Safari does have the "Version/" string in its user agent
1212
            // then use that as a version indicator rather than what's after
1213
            // "Safari/" which is rather a "build number" or something
1214
            [, $version] = explode('Version/', $_SERVER['HTTP_USER_AGENT']);
1215
        } else {
1216
            [, $version] = explode('Safari/', $_SERVER['HTTP_USER_AGENT']);
1217
        }
1218
    } elseif (false !== strpos($_SERVER['HTTP_USER_AGENT'], 'Firefox')) {
1219
        $navigator = 'Firefox';
1220
        [, $version] = explode('Firefox', $_SERVER['HTTP_USER_AGENT']);
1221
    } elseif (false !== strpos($_SERVER['HTTP_USER_AGENT'], 'Netscape')) {
1222
        $navigator = 'Netscape';
1223
        if (false !== stripos($_SERVER['HTTP_USER_AGENT'], 'Netscape/')) {
1224
            [, $version] = explode('Netscape', $_SERVER['HTTP_USER_AGENT']);
1225
        } else {
1226
            [, $version] = explode('Navigator', $_SERVER['HTTP_USER_AGENT']);
1227
        }
1228
    } elseif (false !== strpos($_SERVER['HTTP_USER_AGENT'], 'Konqueror')) {
1229
        $navigator = 'Konqueror';
1230
        [, $version] = explode('Konqueror', $_SERVER['HTTP_USER_AGENT']);
1231
    } elseif (false !== stripos($_SERVER['HTTP_USER_AGENT'], 'applewebkit')) {
1232
        $navigator = 'AppleWebKit';
1233
        [, $version] = explode('Version/', $_SERVER['HTTP_USER_AGENT']);
1234
    } elseif (false !== strpos($_SERVER['HTTP_USER_AGENT'], 'Gecko')) {
1235
        $navigator = 'Mozilla';
1236
        [, $version] = explode('; rv:', $_SERVER['HTTP_USER_AGENT']);
1237
    }
1238
1239
    // Now cut extra stuff around (mostly *after*) the version number
1240
    $version = preg_replace('/^([\/\s])?([\d\.]+)?.*/', '\2', $version);
1241
1242
    if (false === strpos($version, '.')) {
1243
        $version = number_format(doubleval($version), 1);
1244
    }
1245
1246
    return ['name' => $navigator, 'version' => $version];
1247
}
1248
1249
/**
1250
 * This function returns the id of the user which is stored in the $_user array.
1251
 *
1252
 * example: The function can be used to check if a user is logged in
1253
 *          if (api_get_user_id())
1254
 *
1255
 * @return int the id of the current user, 0 if is empty
1256
 */
1257
function api_get_user_id()
1258
{
1259
    $userInfo = Session::read('_user');
1260
    if ($userInfo && isset($userInfo['user_id'])) {
1261
        return (int) $userInfo['user_id'];
1262
    }
1263
1264
    return 0;
1265
}
1266
1267
/**
1268
 * Formats user information into a standard array
1269
 * This function should be only used inside api_get_user_info().
1270
 *
1271
 * @param array Non-standard user array
0 ignored issues
show
Documentation Bug introduced by
The doc comment Non-standard at position 0 could not be parsed: Unknown type name 'Non-standard' at position 0 in Non-standard.
Loading history...
1272
 * @param bool $add_password
1273
 * @param bool $loadAvatars  turn off to improve performance
1274
 *
1275
 * @return array Standard user array
1276
 */
1277
function _api_format_user($user, $add_password = false, $loadAvatars = true)
1278
{
1279
    $result = [];
1280
1281
    if (!isset($user['id'])) {
1282
        return [];
1283
    }
1284
1285
    $result['firstname'] = null;
1286
    $result['lastname'] = null;
1287
1288
    if (isset($user['firstname']) && isset($user['lastname'])) {
1289
        // with only lowercase
1290
        $result['firstname'] = $user['firstname'];
1291
        $result['lastname'] = $user['lastname'];
1292
    } elseif (isset($user['firstName']) && isset($user['lastName'])) {
1293
        // with uppercase letters
1294
        $result['firstname'] = isset($user['firstName']) ? $user['firstName'] : null;
1295
        $result['lastname'] = isset($user['lastName']) ? $user['lastName'] : null;
1296
    }
1297
1298
    if (isset($user['email'])) {
1299
        $result['mail'] = isset($user['email']) ? $user['email'] : null;
1300
        $result['email'] = isset($user['email']) ? $user['email'] : null;
1301
    } else {
1302
        $result['mail'] = isset($user['mail']) ? $user['mail'] : null;
1303
        $result['email'] = isset($user['mail']) ? $user['mail'] : null;
1304
    }
1305
1306
    $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
1307
    $result['complete_name_with_username'] = $result['complete_name'];
1308
1309
    if (!empty($user['username']) && 'false' === api_get_setting('profile.hide_username_with_complete_name')) {
1310
        $result['complete_name_with_username'] = $result['complete_name'].' ('.$user['username'].')';
1311
    }
1312
1313
    $showEmail = 'true' === api_get_setting('show_email_addresses');
1314
    if (!empty($user['email'])) {
1315
        $result['complete_name_with_email_forced'] = $result['complete_name'].' ('.$user['email'].')';
1316
        if ($showEmail) {
1317
            $result['complete_name_with_email'] = $result['complete_name'].' ('.$user['email'].')';
1318
        }
1319
    } else {
1320
        $result['complete_name_with_email'] = $result['complete_name'];
1321
        $result['complete_name_with_email_forced'] = $result['complete_name'];
1322
    }
1323
1324
    // Kept for historical reasons
1325
    $result['firstName'] = $result['firstname'];
1326
    $result['lastName'] = $result['lastname'];
1327
1328
    $attributes = [
1329
        'phone',
1330
        'address',
1331
        'picture_uri',
1332
        'official_code',
1333
        'status',
1334
        'active',
1335
        'auth_sources',
1336
        'username',
1337
        'theme',
1338
        'language',
1339
        'locale',
1340
        'creator_id',
1341
        'created_at',
1342
        'hr_dept_id',
1343
        'expiration_date',
1344
        'last_login',
1345
        'user_is_online',
1346
        'profile_completed',
1347
    ];
1348
1349
    if ('true' === api_get_setting('extended_profile')) {
1350
        $attributes[] = 'competences';
1351
        $attributes[] = 'diplomas';
1352
        $attributes[] = 'teach';
1353
        $attributes[] = 'openarea';
1354
    }
1355
1356
    foreach ($attributes as $attribute) {
1357
        $result[$attribute] = $user[$attribute] ?? null;
1358
    }
1359
1360
    $user_id = (int) $user['id'];
1361
    // Maintain the user_id index for backwards compatibility
1362
    $result['user_id'] = $result['id'] = $user_id;
1363
1364
    $hasCertificates = Certificate::getCertificateByUser($user_id);
1365
    $result['has_certificates'] = 0;
1366
    if (!empty($hasCertificates)) {
1367
        $result['has_certificates'] = 1;
1368
    }
1369
1370
    $result['icon_status'] = '';
1371
    $result['icon_status_medium'] = '';
1372
    $result['is_admin'] = UserManager::is_admin($user_id);
1373
1374
    // Getting user avatar.
1375
    if ($loadAvatars) {
1376
        $result['avatar'] = '';
1377
        $result['avatar_no_query'] = '';
1378
        $result['avatar_small'] = '';
1379
        $result['avatar_medium'] = '';
1380
1381
        if (empty($user['avatar'])) {
1382
            $originalFile = UserManager::getUserPicture(
1383
                $user_id,
1384
                USER_IMAGE_SIZE_ORIGINAL,
1385
                null,
1386
                $result
1387
            );
1388
            $result['avatar'] = $originalFile;
1389
            $avatarString = explode('?', $result['avatar']);
1390
            $result['avatar_no_query'] = reset($avatarString);
1391
        } else {
1392
            $result['avatar'] = $user['avatar'];
1393
            $avatarString = explode('?', $user['avatar']);
1394
            $result['avatar_no_query'] = reset($avatarString);
1395
        }
1396
1397
        if (!isset($user['avatar_small'])) {
1398
            $smallFile = UserManager::getUserPicture(
1399
                $user_id,
1400
                USER_IMAGE_SIZE_SMALL,
1401
                null,
1402
                $result
1403
            );
1404
            $result['avatar_small'] = $smallFile;
1405
        } else {
1406
            $result['avatar_small'] = $user['avatar_small'];
1407
        }
1408
1409
        if (!isset($user['avatar_medium'])) {
1410
            $mediumFile = UserManager::getUserPicture(
1411
                $user_id,
1412
                USER_IMAGE_SIZE_MEDIUM,
1413
                null,
1414
                $result
1415
            );
1416
            $result['avatar_medium'] = $mediumFile;
1417
        } else {
1418
            $result['avatar_medium'] = $user['avatar_medium'];
1419
        }
1420
1421
        $urlImg = api_get_path(WEB_IMG_PATH);
1422
        $iconStatus = '';
1423
        $iconStatusMedium = '';
1424
        $label = '';
1425
1426
        switch ($result['status']) {
1427
            case STUDENT:
1428
                if ($result['has_certificates']) {
1429
                    $iconStatus = $urlImg.'icons/svg/identifier_graduated.svg';
1430
                    $label = get_lang('Graduated');
1431
                } else {
1432
                    $iconStatus = $urlImg.'icons/svg/identifier_student.svg';
1433
                    $label = get_lang('Learner');
1434
                }
1435
                break;
1436
            case COURSEMANAGER:
1437
                if ($result['is_admin']) {
1438
                    $iconStatus = $urlImg.'icons/svg/identifier_admin.svg';
1439
                    $label = get_lang('Admin');
1440
                } else {
1441
                    $iconStatus = $urlImg.'icons/svg/identifier_teacher.svg';
1442
                    $label = get_lang('Teacher');
1443
                }
1444
                break;
1445
            case STUDENT_BOSS:
1446
                $iconStatus = $urlImg.'icons/svg/identifier_teacher.svg';
1447
                $label = get_lang('Student boss');
1448
                break;
1449
        }
1450
1451
        if (!empty($iconStatus)) {
1452
            $iconStatusMedium = '<img src="'.$iconStatus.'" width="32px" height="32px">';
1453
            $iconStatus = '<img src="'.$iconStatus.'" width="22px" height="22px">';
1454
        }
1455
1456
        $result['icon_status'] = $iconStatus;
1457
        $result['icon_status_label'] = $label;
1458
        $result['icon_status_medium'] = $iconStatusMedium;
1459
    }
1460
1461
    if (isset($user['user_is_online'])) {
1462
        $result['user_is_online'] = true == $user['user_is_online'] ? 1 : 0;
1463
    }
1464
    if (isset($user['user_is_online_in_chat'])) {
1465
        $result['user_is_online_in_chat'] = (int) $user['user_is_online_in_chat'];
1466
    }
1467
1468
    if ($add_password) {
1469
        $result['password'] = $user['password'];
1470
    }
1471
1472
    if (isset($result['profile_completed'])) {
1473
        $result['profile_completed'] = $user['profile_completed'];
1474
    }
1475
1476
    $result['profile_url'] = api_get_path(WEB_CODE_PATH).'social/profile.php?u='.$user_id;
1477
1478
    // Send message link
1479
    $sendMessage = api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_popup&user_id='.$user_id;
1480
    $result['complete_name_with_message_link'] = Display::url(
1481
        $result['complete_name_with_username'],
1482
        $sendMessage,
1483
        ['class' => 'ajax']
1484
    );
1485
1486
    if (isset($user['extra'])) {
1487
        $result['extra'] = $user['extra'];
1488
    }
1489
1490
    return $result;
1491
}
1492
1493
/**
1494
 * Finds all the information about a user.
1495
 * If no parameter is passed you find all the information about the current user.
1496
 *
1497
 * @param int  $user_id
1498
 * @param bool $checkIfUserOnline
1499
 * @param bool $showPassword
1500
 * @param bool $loadExtraData
1501
 * @param bool $loadOnlyVisibleExtraData Get the user extra fields that are visible
1502
 * @param bool $loadAvatars              turn off to improve performance and if avatars are not needed
1503
 * @param bool $updateCache              update apc cache if exists
1504
 *
1505
 * @return mixed $user_info user_id, lastname, firstname, username, email, etc or false on error
1506
 *
1507
 * @author Patrick Cool <[email protected]>
1508
 * @author Julio Montoya
1509
 *
1510
 * @version 21 September 2004
1511
 */
1512
function api_get_user_info(
1513
    $user_id = 0,
1514
    $checkIfUserOnline = false,
1515
    $showPassword = false,
1516
    $loadExtraData = false,
1517
    $loadOnlyVisibleExtraData = false,
1518
    $loadAvatars = true,
1519
    $updateCache = false
1520
) {
1521
    // Make sure user_id is safe
1522
    $user_id = (int) $user_id;
1523
    $user = false;
1524
    if (empty($user_id)) {
1525
        $userFromSession = Session::read('_user');
1526
        if (isset($userFromSession) && !empty($userFromSession)) {
1527
            return $userFromSession;
1528
            /*
1529
            return _api_format_user(
1530
                $userFromSession,
1531
                $showPassword,
1532
                $loadAvatars
1533
            );*/
1534
        }
1535
1536
        return false;
1537
    }
1538
1539
    $sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_USER)."
1540
            WHERE id = $user_id";
1541
    $result = Database::query($sql);
1542
    if (Database::num_rows($result) > 0) {
1543
        $result_array = Database::fetch_array($result);
1544
        $result_array['auth_sources'] = api_get_user_entity($result_array['id'])->getAuthSourcesAuthentications();
1545
        $result_array['user_is_online_in_chat'] = 0;
1546
        if ($checkIfUserOnline) {
1547
            $use_status_in_platform = user_is_online($user_id);
1548
            $result_array['user_is_online'] = $use_status_in_platform;
1549
            $user_online_in_chat = 0;
1550
            if ($use_status_in_platform) {
1551
                $user_status = UserManager::get_extra_user_data_by_field(
1552
                    $user_id,
1553
                    'user_chat_status',
1554
                    false,
1555
                    true
1556
                );
1557
                if (1 == (int) $user_status['user_chat_status']) {
1558
                    $user_online_in_chat = 1;
1559
                }
1560
            }
1561
            $result_array['user_is_online_in_chat'] = $user_online_in_chat;
1562
        }
1563
1564
        if ($loadExtraData) {
1565
            $fieldValue = new ExtraFieldValue('user');
1566
            $result_array['extra'] = $fieldValue->getAllValuesForAnItem(
1567
                $user_id,
1568
                $loadOnlyVisibleExtraData
1569
            );
1570
        }
1571
        $user = _api_format_user($result_array, $showPassword, $loadAvatars);
1572
    }
1573
1574
    return $user;
1575
}
1576
1577
function api_get_user_info_from_entity(
1578
    User $user,
1579
    $checkIfUserOnline = false,
1580
    $showPassword = false,
1581
    $loadExtraData = false,
1582
    $loadOnlyVisibleExtraData = false,
1583
    $loadAvatars = true,
1584
    $loadCertificate = false
1585
) {
1586
    if (!$user instanceof UserInterface) {
1587
        return false;
1588
    }
1589
1590
    // Make sure user_id is safe
1591
    $user_id = (int) $user->getId();
1592
1593
    if (empty($user_id)) {
1594
        $userFromSession = Session::read('_user');
1595
1596
        if (isset($userFromSession) && !empty($userFromSession)) {
1597
            return $userFromSession;
1598
        }
1599
1600
        return false;
1601
    }
1602
1603
    $result = [];
1604
    $result['user_is_online_in_chat'] = 0;
1605
    if ($checkIfUserOnline) {
1606
        $use_status_in_platform = user_is_online($user_id);
1607
        $result['user_is_online'] = $use_status_in_platform;
1608
        $user_online_in_chat = 0;
1609
        if ($use_status_in_platform) {
1610
            $user_status = UserManager::get_extra_user_data_by_field(
1611
                $user_id,
1612
                'user_chat_status',
1613
                false,
1614
                true
1615
            );
1616
            if (1 == (int) $user_status['user_chat_status']) {
1617
                $user_online_in_chat = 1;
1618
            }
1619
        }
1620
        $result['user_is_online_in_chat'] = $user_online_in_chat;
1621
    }
1622
1623
    if ($loadExtraData) {
1624
        $fieldValue = new ExtraFieldValue('user');
1625
        $result['extra'] = $fieldValue->getAllValuesForAnItem(
1626
            $user_id,
1627
            $loadOnlyVisibleExtraData
1628
        );
1629
    }
1630
1631
    $result['username'] = $user->getUsername();
1632
    $result['status'] = $user->getStatus();
1633
    $result['firstname'] = $user->getFirstname();
1634
    $result['lastname'] = $user->getLastname();
1635
    $result['email'] = $result['mail'] = $user->getEmail();
1636
    $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
1637
    $result['complete_name_with_username'] = $result['complete_name'];
1638
1639
    if (!empty($result['username']) && 'false' === api_get_setting('profile.hide_username_with_complete_name')) {
1640
        $result['complete_name_with_username'] = $result['complete_name'].' ('.$result['username'].')';
1641
    }
1642
1643
    $showEmail = 'true' === api_get_setting('show_email_addresses');
1644
    if (!empty($result['email'])) {
1645
        $result['complete_name_with_email_forced'] = $result['complete_name'].' ('.$result['email'].')';
1646
        if ($showEmail) {
1647
            $result['complete_name_with_email'] = $result['complete_name'].' ('.$result['email'].')';
1648
        }
1649
    } else {
1650
        $result['complete_name_with_email'] = $result['complete_name'];
1651
        $result['complete_name_with_email_forced'] = $result['complete_name'];
1652
    }
1653
1654
    // Kept for historical reasons
1655
    $result['firstName'] = $result['firstname'];
1656
    $result['lastName'] = $result['lastname'];
1657
1658
    $attributes = [
1659
        'picture_uri',
1660
        'last_login',
1661
        'user_is_online',
1662
    ];
1663
1664
    $result['phone'] = $user->getPhone();
1665
    $result['address'] = $user->getAddress();
1666
    $result['official_code'] = $user->getOfficialCode();
1667
    $result['active'] = $user->isActive();
1668
    $result['auth_sources'] = $user->getAuthSourcesAuthentications();
1669
    $result['language'] = $user->getLocale();
1670
    $result['creator_id'] = $user->getCreatorId();
1671
    $result['created_at'] = $user->getCreatedAt()->format('Y-m-d H:i:s');
1672
    $result['hr_dept_id'] = $user->getHrDeptId();
1673
    $result['expiration_date'] = '';
1674
    if ($user->getExpirationDate()) {
1675
        $result['expiration_date'] = $user->getExpirationDate()->format('Y-m-d H:i:s');
1676
    }
1677
1678
    $result['last_login'] = null;
1679
    if ($user->getLastLogin()) {
1680
        $result['last_login'] = $user->getLastLogin()->format('Y-m-d H:i:s');
1681
    }
1682
1683
    $result['competences'] = $user->getCompetences();
1684
    $result['diplomas'] = $user->getDiplomas();
1685
    $result['teach'] = $user->getTeach();
1686
    $result['openarea'] = $user->getOpenarea();
1687
    $user_id = (int) $user->getId();
1688
1689
    // Maintain the user_id index for backwards compatibility
1690
    $result['user_id'] = $result['id'] = $user_id;
1691
1692
    if ($loadCertificate) {
1693
        $hasCertificates = Certificate::getCertificateByUser($user_id);
1694
        $result['has_certificates'] = 0;
1695
        if (!empty($hasCertificates)) {
1696
            $result['has_certificates'] = 1;
1697
        }
1698
    }
1699
1700
    $result['icon_status'] = '';
1701
    $result['icon_status_medium'] = '';
1702
    $result['is_admin'] = UserManager::is_admin($user_id);
1703
1704
    // Getting user avatar.
1705
    if ($loadAvatars) {
1706
        $result['avatar'] = '';
1707
        $result['avatar_no_query'] = '';
1708
        $result['avatar_small'] = '';
1709
        $result['avatar_medium'] = '';
1710
        $urlImg = '/';
1711
        $iconStatus = '';
1712
        $iconStatusMedium = '';
1713
1714
        switch ($user->getStatus()) {
1715
            case STUDENT:
1716
                if (isset($result['has_certificates']) && $result['has_certificates']) {
1717
                    $iconStatus = $urlImg.'icons/svg/identifier_graduated.svg';
1718
                } else {
1719
                    $iconStatus = $urlImg.'icons/svg/identifier_student.svg';
1720
                }
1721
                break;
1722
            case COURSEMANAGER:
1723
                if ($result['is_admin']) {
1724
                    $iconStatus = $urlImg.'icons/svg/identifier_admin.svg';
1725
                } else {
1726
                    $iconStatus = $urlImg.'icons/svg/identifier_teacher.svg';
1727
                }
1728
                break;
1729
            case STUDENT_BOSS:
1730
                $iconStatus = $urlImg.'icons/svg/identifier_teacher.svg';
1731
                break;
1732
        }
1733
1734
        if (!empty($iconStatus)) {
1735
            $iconStatusMedium = '<img src="'.$iconStatus.'" width="32px" height="32px">';
1736
            $iconStatus = '<img src="'.$iconStatus.'" width="22px" height="22px">';
1737
        }
1738
1739
        $result['icon_status'] = $iconStatus;
1740
        $result['icon_status_medium'] = $iconStatusMedium;
1741
    }
1742
1743
    if (isset($result['user_is_online'])) {
1744
        $result['user_is_online'] = true == $result['user_is_online'] ? 1 : 0;
1745
    }
1746
    if (isset($result['user_is_online_in_chat'])) {
1747
        $result['user_is_online_in_chat'] = $result['user_is_online_in_chat'];
1748
    }
1749
1750
    $result['password'] = '';
1751
    if ($showPassword) {
1752
        $result['password'] = $user->getPassword();
1753
    }
1754
1755
    if (isset($result['profile_completed'])) {
1756
        $result['profile_completed'] = $result['profile_completed'];
1757
    }
1758
1759
    $result['profile_url'] = api_get_path(WEB_CODE_PATH).'social/profile.php?u='.$user_id;
1760
1761
    // Send message link
1762
    $sendMessage = api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_popup&user_id='.$user_id;
1763
    $result['complete_name_with_message_link'] = Display::url(
1764
        $result['complete_name_with_username'],
1765
        $sendMessage,
1766
        ['class' => 'ajax']
1767
    );
1768
1769
    if (isset($result['extra'])) {
1770
        $result['extra'] = $result['extra'];
1771
    }
1772
1773
    return $result;
1774
}
1775
1776
function api_get_lp_entity(int $id): ?CLp
1777
{
1778
    return Database::getManager()->getRepository(CLp::class)->find($id);
1779
}
1780
1781
function api_get_user_entity(int $userId = 0): ?User
1782
{
1783
    $userId = $userId ?: api_get_user_id();
1784
    $repo = Container::getUserRepository();
1785
1786
    return $repo->find($userId);
1787
}
1788
1789
function api_get_current_user(): ?User
1790
{
1791
    $isLoggedIn = Container::getAuthorizationChecker()->isGranted('IS_AUTHENTICATED_REMEMBERED');
1792
    if (false === $isLoggedIn) {
1793
        return null;
1794
    }
1795
1796
    $token = Container::getTokenStorage()->getToken();
1797
1798
    if (null !== $token) {
1799
        return $token->getUser();
1800
    }
1801
1802
    return null;
1803
}
1804
1805
/**
1806
 * Finds all the information about a user from username instead of user id.
1807
 *
1808
 * @param string $username
1809
 *
1810
 * @return mixed $user_info array user_id, lastname, firstname, username, email or false on error
1811
 *
1812
 * @author Yannick Warnier <[email protected]>
1813
 */
1814
function api_get_user_info_from_username($username)
1815
{
1816
    if (empty($username)) {
1817
        return false;
1818
    }
1819
    $username = trim($username);
1820
1821
    $sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_USER)."
1822
            WHERE username='".Database::escape_string($username)."'";
1823
    $result = Database::query($sql);
1824
    if (Database::num_rows($result) > 0) {
1825
        $resultArray = Database::fetch_array($result);
1826
1827
        return _api_format_user($resultArray);
1828
    }
1829
1830
    return false;
1831
}
1832
1833
/**
1834
 * Get first user with an email.
1835
 *
1836
 * @param string $email
1837
 *
1838
 * @return array|bool
1839
 */
1840
function api_get_user_info_from_email($email = '')
1841
{
1842
    if (empty($email)) {
1843
        return false;
1844
    }
1845
    $sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_USER)."
1846
            WHERE email ='".Database::escape_string($email)."' LIMIT 1";
1847
    $result = Database::query($sql);
1848
    if (Database::num_rows($result) > 0) {
1849
        $resultArray = Database::fetch_array($result);
1850
1851
        return _api_format_user($resultArray);
1852
    }
1853
1854
    return false;
1855
}
1856
1857
/**
1858
 * @return string
1859
 */
1860
function api_get_course_id()
1861
{
1862
    return Session::read('_cid', null);
1863
}
1864
1865
/**
1866
 * Returns the current course id (integer).
1867
 *
1868
 * @param ?string $code Optional course code
1869
 *
1870
 * @return int
1871
 */
1872
function api_get_course_int_id(?string $code = null): int
1873
{
1874
    if (!empty($code)) {
1875
        $code = Database::escape_string($code);
1876
        $row = Database::select(
1877
            'id',
1878
            Database::get_main_table(TABLE_MAIN_COURSE),
1879
            ['where' => ['code = ?' => [$code]]],
1880
            'first'
1881
        );
1882
1883
        if (is_array($row) && isset($row['id'])) {
1884
            return $row['id'];
1885
        } else {
1886
            return 0;
1887
        }
1888
    }
1889
1890
    $cid = Session::read('_real_cid', 0);
1891
    if (empty($cid) && isset($_REQUEST['cid'])) {
1892
        $cid = (int) $_REQUEST['cid'];
1893
    }
1894
1895
    return $cid;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $cid could return the type null which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
1896
}
1897
1898
/**
1899
 * Gets a course setting from the current course_setting table. Try always using integer values.
1900
 *
1901
 * @param string       $settingName The name of the setting we want from the table
1902
 * @param Course|array $courseInfo
1903
 * @param bool         $force       force checking the value in the database
1904
 *
1905
 * @return mixed The value of that setting in that table. Return -1 if not found.
1906
 */
1907
function api_get_course_setting($settingName, $courseInfo = null, $force = false)
1908
{
1909
    if (empty($courseInfo)) {
1910
        $courseInfo = api_get_course_info();
1911
    }
1912
1913
    if (empty($courseInfo) || empty($settingName)) {
1914
        return -1;
1915
    }
1916
1917
    if ($courseInfo instanceof Course) {
1918
        $courseId = $courseInfo->getId();
1919
    } else {
1920
        $courseId = isset($courseInfo['real_id']) && !empty($courseInfo['real_id']) ? $courseInfo['real_id'] : 0;
1921
    }
1922
1923
    if (empty($courseId)) {
1924
        return -1;
1925
    }
1926
1927
    static $courseSettingInfo = [];
1928
1929
    if ($force) {
1930
        $courseSettingInfo = [];
1931
    }
1932
1933
    if (!isset($courseSettingInfo[$courseId])) {
1934
        $table = Database::get_course_table(TABLE_COURSE_SETTING);
1935
        $settingName = Database::escape_string($settingName);
1936
1937
        $sql = "SELECT variable, value FROM $table
1938
                WHERE c_id = $courseId ";
1939
        $res = Database::query($sql);
1940
        if (Database::num_rows($res) > 0) {
1941
            $result = Database::store_result($res, 'ASSOC');
1942
            $courseSettingInfo[$courseId] = array_column($result, 'value', 'variable');
1943
1944
            if (isset($courseSettingInfo[$courseId]['email_alert_manager_on_new_quiz'])) {
1945
                $value = $courseSettingInfo[$courseId]['email_alert_manager_on_new_quiz'];
1946
                if (!is_null($value)) {
1947
                    $result = explode(',', $value);
1948
                    $courseSettingInfo[$courseId]['email_alert_manager_on_new_quiz'] = $result;
1949
                }
1950
            }
1951
        }
1952
    }
1953
1954
    if (isset($courseSettingInfo[$courseId]) && isset($courseSettingInfo[$courseId][$settingName])) {
1955
        return $courseSettingInfo[$courseId][$settingName];
1956
    }
1957
1958
    return -1;
1959
}
1960
1961
function api_get_course_plugin_setting($plugin, $settingName, $courseInfo = [])
1962
{
1963
    $value = api_get_course_setting($settingName, $courseInfo, true);
1964
1965
    if (-1 === $value) {
1966
        // Check global settings
1967
        $value = api_get_plugin_setting($plugin, $settingName);
1968
        if ('true' === $value) {
1969
            return 1;
1970
        }
1971
        if ('false' === $value) {
1972
            return 0;
1973
        }
1974
        if (null === $value) {
1975
            return -1;
1976
        }
1977
    }
1978
1979
    return $value;
1980
}
1981
1982
/**
1983
 * Gets an anonymous user ID.
1984
 *
1985
 * For some tools that need tracking, like the learnpath tool, it is necessary
1986
 * to have a usable user-id to enable some kind of tracking, even if not
1987
 * perfect. An anonymous ID is taken from the users table by looking for a
1988
 * status of "6" (anonymous).
1989
 *
1990
 * @return int User ID of the anonymous user, or O if no anonymous user found
1991
 */
1992
function api_get_anonymous_id()
1993
{
1994
    // Find if another anon is connected now
1995
    $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1996
    $tableU = Database::get_main_table(TABLE_MAIN_USER);
1997
    $ip = Database::escape_string(api_get_real_ip());
1998
    $max = (int) api_get_setting('admin.max_anonymous_users');
1999
    if ($max >= 2) {
2000
        $sql = "SELECT * FROM $table as TEL
2001
                JOIN $tableU as U
2002
                ON U.id = TEL.login_user_id
2003
                WHERE TEL.user_ip = '$ip'
2004
                    AND U.status = ".ANONYMOUS."
2005
                    AND U.id != 2 ";
2006
2007
        $result = Database::query($sql);
2008
        if (empty(Database::num_rows($result))) {
2009
            $login = uniqid('anon_');
2010
            $anonList = UserManager::get_user_list(['status' => ANONYMOUS], ['created_at ASC']);
2011
            if (count($anonList) >= $max) {
2012
                foreach ($anonList as $userToDelete) {
2013
                    UserManager::delete_user($userToDelete['user_id']);
2014
                    break;
2015
                }
2016
            }
2017
2018
            return UserManager::create_user(
0 ignored issues
show
Bug Best Practice introduced by
The expression return UserManager::crea...lhost', $login, $login) could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
2019
                $login,
2020
                'anon',
2021
                ANONYMOUS,
2022
                ' anonymous@localhost',
2023
                $login,
2024
                $login
2025
            );
2026
        } else {
2027
            $row = Database::fetch_assoc($result);
2028
2029
            return $row['id'];
2030
        }
2031
    }
2032
2033
    $table = Database::get_main_table(TABLE_MAIN_USER);
2034
    $sql = "SELECT id
2035
            FROM $table
2036
            WHERE status = ".ANONYMOUS." ";
2037
    $res = Database::query($sql);
2038
    if (Database::num_rows($res) > 0) {
2039
        $row = Database::fetch_assoc($res);
2040
2041
        return $row['id'];
2042
    }
2043
2044
    // No anonymous user was found.
2045
    return 0;
2046
}
2047
2048
/**
2049
 * @param int $courseId
2050
 * @param int $sessionId
2051
 * @param int $groupId
2052
 *
2053
 * @return string
2054
 */
2055
function api_get_cidreq_params($courseId, $sessionId = 0, $groupId = 0)
2056
{
2057
    $courseId = !empty($courseId) ? (int) $courseId : 0;
2058
    $sessionId = !empty($sessionId) ? (int) $sessionId : 0;
2059
    $groupId = !empty($groupId) ? (int) $groupId : 0;
2060
2061
    $url = 'cid='.$courseId;
2062
    $url .= '&sid='.$sessionId;
2063
    $url .= '&gid='.$groupId;
2064
2065
    return $url;
2066
}
2067
2068
/**
2069
 * Returns the current course url part including session, group, and gradebook params.
2070
 *
2071
 * @param bool   $addSessionId
2072
 * @param bool   $addGroupId
2073
 * @param string $origin
2074
 *
2075
 * @return string Course & session references to add to a URL
2076
 */
2077
function api_get_cidreq($addSessionId = true, $addGroupId = true, $origin = '')
2078
{
2079
    $courseId = api_get_course_int_id();
2080
    if (0 === $courseId && isset($_REQUEST['cid'])) {
2081
        $courseId = (int) $_REQUEST['cid'];
2082
    }
2083
    $url = empty($courseId) ? '' : 'cid='.$courseId;
2084
    $origin = empty($origin) ? api_get_origin() : Security::remove_XSS($origin);
2085
2086
    if ($addSessionId) {
2087
        if (!empty($url)) {
2088
            $sessionId = api_get_session_id();
2089
            if (0 === $sessionId && isset($_REQUEST['sid'])) {
2090
                $sessionId = (int) $_REQUEST['sid'];
2091
            }
2092
            $url .= 0 === $sessionId ? '&sid=0' : '&sid='.$sessionId;
2093
        }
2094
    }
2095
2096
    if ($addGroupId) {
2097
        if (!empty($url)) {
2098
            $url .= 0 == api_get_group_id() ? '&gid=0' : '&gid='.api_get_group_id();
2099
        }
2100
    }
2101
2102
    if (!empty($url)) {
2103
        $url .= '&gradebook='.(int) api_is_in_gradebook();
2104
        if (false !== $origin) {
2105
            $url .= '&origin=' . $origin;
2106
        }
2107
    }
2108
2109
    return $url;
2110
}
2111
2112
/**
2113
 * Get if we visited a gradebook page.
2114
 *
2115
 * @return bool
2116
 */
2117
function api_is_in_gradebook()
2118
{
2119
    return Session::read('in_gradebook', false);
2120
}
2121
2122
/**
2123
 * Set that we are in a page inside a gradebook.
2124
 */
2125
function api_set_in_gradebook()
2126
{
2127
    Session::write('in_gradebook', true);
2128
}
2129
2130
/**
2131
 * Remove gradebook session.
2132
 */
2133
function api_remove_in_gradebook()
2134
{
2135
    Session::erase('in_gradebook');
2136
}
2137
2138
/**
2139
 * Returns the current course info array see api_format_course_array()
2140
 * If the course_code is given, the returned array gives info about that
2141
 * particular course, if none given it gets the course info from the session.
2142
 *
2143
 * @param string $courseCode
2144
 *
2145
 * @return array
2146
 */
2147
function api_get_course_info($courseCode = null)
2148
{
2149
    if (!empty($courseCode)) {
2150
        $course = Container::getCourseRepository()->findOneByCode($courseCode);
2151
2152
        return api_format_course_array($course);
2153
    }
2154
2155
    $course = Session::read('_course');
2156
    if ('-1' == $course) {
2157
        $course = [];
2158
    }
2159
2160
    if (empty($course) && isset($_REQUEST['cid'])) {
2161
        $course = api_get_course_info_by_id((int) $_REQUEST['cid']);
2162
    }
2163
2164
    return $course;
2165
}
2166
2167
/**
2168
 * @param int $courseId
2169
 */
2170
function api_get_course_entity($courseId = 0): ?Course
2171
{
2172
    if (empty($courseId)) {
2173
        $courseId = api_get_course_int_id();
2174
    }
2175
2176
    if (empty($courseId)) {
2177
        return null;
2178
    }
2179
2180
    return Container::getCourseRepository()->find($courseId);
2181
}
2182
2183
/**
2184
 * @param int $id
2185
 */
2186
function api_get_session_entity($id = 0): ?SessionEntity
2187
{
2188
    if (empty($id)) {
2189
        $id = api_get_session_id();
2190
    }
2191
2192
    if (empty($id)) {
2193
        return null;
2194
    }
2195
2196
    return Container::getSessionRepository()->find($id);
2197
}
2198
2199
/**
2200
 * @param int $id
2201
 */
2202
function api_get_group_entity($id = 0): ?CGroup
2203
{
2204
    if (empty($id)) {
2205
        $id = api_get_group_id();
2206
    }
2207
2208
    return Container::getGroupRepository()->find($id);
2209
}
2210
2211
/**
2212
 * @param int $id
2213
 */
2214
function api_get_url_entity($id = 0): ?AccessUrl
2215
{
2216
    if (empty($id)) {
2217
        $id = api_get_current_access_url_id();
2218
    }
2219
2220
    return Container::getAccessUrlRepository()->find($id);
2221
}
2222
2223
/**
2224
 * Returns the current course info array.
2225
2226
 * Now if the course_code is given, the returned array gives info about that
2227
 * particular course, not specially the current one.
2228
 *
2229
 * @param int $id Numeric ID of the course
2230
 *
2231
 * @return array The course info as an array formatted by api_format_course_array, including category.title
2232
 */
2233
function api_get_course_info_by_id(?int $id = 0)
2234
{
2235
    if (empty($id)) {
2236
        $course = Session::read('_course', []);
2237
2238
        return $course;
2239
    }
2240
2241
    $course = Container::getCourseRepository()->find($id);
2242
    if (empty($course)) {
2243
        return [];
2244
    }
2245
2246
    return api_format_course_array($course);
2247
}
2248
2249
/**
2250
 * Reformat the course array (output by api_get_course_info()) in order, mostly,
2251
 * to switch from 'code' to 'id' in the array.
2252
 *
2253
 * @return array
2254
 *
2255
 * @todo eradicate the false "id"=code field of the $_course array and use the int id
2256
 */
2257
function api_format_course_array(Course $course = null)
2258
{
2259
    if (empty($course)) {
2260
        return [];
2261
    }
2262
2263
    $courseData = [];
2264
    $courseData['id'] = $courseData['real_id'] = $course->getId();
2265
2266
    // Added
2267
    $courseData['code'] = $courseData['sysCode'] = $course->getCode();
2268
    $courseData['name'] = $courseData['title'] = $course->getTitle(); // 'name' only used for backwards compatibility - should be removed in the long run
2269
    $courseData['official_code'] = $courseData['visual_code'] = $course->getVisualCode();
2270
    $courseData['creation_date'] = $course->getCreationDate()->format('Y-m-d H:i:s');
2271
    $courseData['titular'] = $course->getTutorName();
2272
    $courseData['language'] = $courseData['course_language'] = $course->getCourseLanguage();
2273
    $courseData['extLink']['url'] = $courseData['department_url'] = $course->getDepartmentUrl();
2274
    $courseData['extLink']['name'] = $courseData['department_name'] = $course->getDepartmentName();
2275
2276
    $courseData['visibility'] = $course->getVisibility();
2277
    $courseData['subscribe_allowed'] = $courseData['subscribe'] = $course->getSubscribe();
2278
    $courseData['unsubscribe'] = $course->getUnsubscribe();
2279
    $courseData['activate_legal'] = $course->getActivateLegal();
2280
    $courseData['legal'] = $course->getLegal();
2281
    $courseData['show_score'] = $course->getShowScore(); //used in the work tool
2282
    $courseData['video_url'] = $course->getVideoUrl();
2283
    $courseData['sticky'] = (int) $course->isSticky();
2284
2285
    $coursePath = '/course/';
2286
    $webCourseHome = $coursePath.$courseData['real_id'].'/home';
2287
2288
    // Course password
2289
    $courseData['registration_code'] = $course->getRegistrationCode();
2290
    $courseData['disk_quota'] = $course->getDiskQuota();
2291
    $courseData['course_public_url'] = $webCourseHome;
2292
    $courseData['about_url'] = $coursePath.$courseData['real_id'].'/about';
2293
    $courseData['add_teachers_to_sessions_courses'] = $course->isAddTeachersToSessionsCourses();
2294
2295
    $image = Display::getMdiIcon(
2296
        ObjectIcon::COURSE,
2297
        'ch-tool-icon',
2298
        null,
2299
        ICON_SIZE_BIG
2300
    );
2301
2302
    $illustration = Container::getIllustrationRepository()->getIllustrationUrl($course);
2303
    if (!empty($illustration)) {
2304
        $image = $illustration;
2305
    }
2306
2307
    $courseData['course_image'] = $image.'?filter=course_picture_small';
2308
    $courseData['course_image_large'] = $image.'?filter=course_picture_medium';
2309
2310
    if ('true' === api_get_setting('course.show_course_duration') && null !== $course->getDuration()) {
2311
        $courseData['duration'] = $course->getDuration();
2312
    }
2313
2314
    return $courseData;
2315
}
2316
2317
/**
2318
 * Returns a difficult to guess password.
2319
 */
2320
function api_generate_password(int $length = 8, $useRequirements = true): string
2321
{
2322
    if ($length < 2) {
2323
        $length = 2;
2324
    }
2325
2326
    $charactersLowerCase = 'abcdefghijkmnopqrstuvwxyz';
2327
    $charactersUpperCase = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
2328
    $charactersSpecials = '!@#$%^&*()_+-=[]{}|;:,.<>?';
2329
    $minNumbers = 2;
2330
    $length = $length - $minNumbers;
2331
    $minLowerCase = round($length / 2);
2332
    $minUpperCase = $length - $minLowerCase;
2333
    $minSpecials = 1; // Default minimum special characters
2334
2335
    $password = '';
2336
    $passwordRequirements = $useRequirements ? Security::getPasswordRequirements() : [];
2337
2338
    $factory = new RandomLib\Factory();
2339
    $generator = $factory->getGenerator(new SecurityLib\Strength(SecurityLib\Strength::MEDIUM));
2340
2341
    if (!empty($passwordRequirements)) {
2342
        $length = $passwordRequirements['min']['length'];
2343
        $minNumbers = $passwordRequirements['min']['numeric'];
2344
        $minLowerCase = $passwordRequirements['min']['lowercase'];
2345
        $minUpperCase = $passwordRequirements['min']['uppercase'];
2346
        $minSpecials = $passwordRequirements['min']['specials'];
2347
2348
        $rest = $length - $minNumbers - $minLowerCase - $minUpperCase - $minSpecials;
2349
        // Add the rest to fill the length requirement
2350
        if ($rest > 0) {
2351
            $password .= $generator->generateString($rest, $charactersLowerCase.$charactersUpperCase);
2352
        }
2353
    }
2354
2355
    // Min digits default 2
2356
    for ($i = 0; $i < $minNumbers; $i++) {
2357
        $password .= $generator->generateInt(2, 9);
2358
    }
2359
2360
    // Min lowercase
2361
    $password .= $generator->generateString($minLowerCase, $charactersLowerCase);
2362
2363
    // Min uppercase
2364
    $password .= $generator->generateString($minUpperCase, $charactersUpperCase);
2365
2366
    // Min special characters
2367
    $password .= $generator->generateString($minSpecials, $charactersSpecials);
2368
2369
    // Shuffle the password to ensure randomness
2370
    $password = str_shuffle($password);
2371
2372
    return $password;
2373
}
2374
2375
/**
2376
 * Checks a password to see wether it is OK to use.
2377
 *
2378
 * @param string $password
2379
 *
2380
 * @return bool if the password is acceptable, false otherwise
2381
 *              Notes about what a password "OK to use" is:
2382
 *              1. The password should be at least 5 characters long.
2383
 *              2. Only English letters (uppercase or lowercase, it doesn't matter) and digits are allowed.
2384
 *              3. The password should contain at least 3 letters.
2385
 *              4. It should contain at least 2 digits.
2386
 *              Settings will change if the configuration value is set: password_requirements
2387
 */
2388
function api_check_password($password)
2389
{
2390
    $passwordRequirements = Security::getPasswordRequirements();
2391
2392
    $minLength = $passwordRequirements['min']['length'];
2393
    $minNumbers = $passwordRequirements['min']['numeric'];
2394
    // Optional
2395
    $minLowerCase = $passwordRequirements['min']['lowercase'];
2396
    $minUpperCase = $passwordRequirements['min']['uppercase'];
2397
2398
    $minLetters = $minLowerCase + $minUpperCase;
2399
    $passwordLength = api_strlen($password);
2400
2401
    $conditions = [
2402
        'min_length' => $passwordLength >= $minLength,
2403
    ];
2404
2405
    $digits = 0;
2406
    $lowerCase = 0;
2407
    $upperCase = 0;
2408
2409
    for ($i = 0; $i < $passwordLength; $i++) {
2410
        $currentCharacterCode = api_ord(api_substr($password, $i, 1));
2411
        if ($currentCharacterCode >= 65 && $currentCharacterCode <= 90) {
2412
            $upperCase++;
2413
        }
2414
2415
        if ($currentCharacterCode >= 97 && $currentCharacterCode <= 122) {
2416
            $lowerCase++;
2417
        }
2418
        if ($currentCharacterCode >= 48 && $currentCharacterCode <= 57) {
2419
            $digits++;
2420
        }
2421
    }
2422
2423
    // Min number of digits
2424
    $conditions['min_numeric'] = $digits >= $minNumbers;
2425
2426
    if (!empty($minUpperCase)) {
2427
        // Uppercase
2428
        $conditions['min_uppercase'] = $upperCase >= $minUpperCase;
2429
    }
2430
2431
    if (!empty($minLowerCase)) {
2432
        // Lowercase
2433
        $conditions['min_lowercase'] = $upperCase >= $minLowerCase;
2434
    }
2435
2436
    // Min letters
2437
    $letters = $upperCase + $lowerCase;
2438
    $conditions['min_letters'] = $letters >= $minLetters;
2439
2440
    $isPasswordOk = true;
2441
    foreach ($conditions as $condition) {
2442
        if (false === $condition) {
2443
            $isPasswordOk = false;
2444
            break;
2445
        }
2446
    }
2447
2448
    if (false === $isPasswordOk) {
2449
        $output = get_lang('The new password does not match the minimum security requirements').'<br />';
2450
        $output .= Security::getPasswordRequirementsToString($conditions);
2451
2452
        Display::addFlash(Display::return_message($output, 'warning', false));
2453
    }
2454
2455
    return $isPasswordOk;
2456
}
2457
2458
/**
2459
 * Gets the current Chamilo (not PHP/cookie) session ID.
2460
 *
2461
 * @return int O if no active session, the session ID otherwise
2462
 */
2463
function api_get_session_id()
2464
{
2465
    return (int) Session::read('sid', 0);
2466
}
2467
2468
/**
2469
 * Gets the current Chamilo (not social network) group ID.
2470
 *
2471
 * @return int O if no active session, the session ID otherwise
2472
 */
2473
function api_get_group_id()
2474
{
2475
    return Session::read('gid', 0);
2476
}
2477
2478
/**
2479
 * Gets the current or given session name.
2480
 *
2481
 * @param   int     Session ID (optional)
2482
 *
2483
 * @return string The session name, or null if not found
2484
 */
2485
function api_get_session_name($session_id = 0)
2486
{
2487
    if (empty($session_id)) {
2488
        $session_id = api_get_session_id();
2489
        if (empty($session_id)) {
2490
            return null;
2491
        }
2492
    }
2493
    $t = Database::get_main_table(TABLE_MAIN_SESSION);
2494
    $s = "SELECT title FROM $t WHERE id = ".(int) $session_id;
2495
    $r = Database::query($s);
2496
    $c = Database::num_rows($r);
2497
    if ($c > 0) {
2498
        //technically, there can be only one, but anyway we take the first
2499
        $rec = Database::fetch_array($r);
2500
2501
        return $rec['title'];
2502
    }
2503
2504
    return null;
2505
}
2506
2507
/**
2508
 * Gets the session info by id.
2509
 *
2510
 * @param int $id Session ID
2511
 *
2512
 * @return array information of the session
2513
 */
2514
function api_get_session_info($id)
2515
{
2516
    return SessionManager::fetch($id);
2517
}
2518
2519
/**
2520
 * Gets the session visibility by session id.
2521
 *
2522
 * @deprecated Use Session::setAccessVisibilityByUser() instead.
2523
 *
2524
 * @param int  $session_id
2525
 * @param int  $courseId
2526
 * @param bool $ignore_visibility_for_admins
2527
 *
2528
 * @return int
2529
 *             0 = session still available,
2530
 *             SESSION_VISIBLE_READ_ONLY = 1,
2531
 *             SESSION_VISIBLE = 2,
2532
 *             SESSION_INVISIBLE = 3
2533
 */
2534
function api_get_session_visibility(
2535
    $session_id,
2536
    $courseId = null,
2537
    $ignore_visibility_for_admins = true,
2538
    $userId = 0
2539
) {
2540
    if (api_is_platform_admin()) {
2541
        if ($ignore_visibility_for_admins) {
2542
            return SESSION_AVAILABLE;
2543
        }
2544
    }
2545
    $userId = empty($userId) ? api_get_user_id() : (int) $userId;
2546
2547
    $now = time();
2548
    if (empty($session_id)) {
2549
        return 0; // Means that the session is still available.
2550
    }
2551
2552
    $session_id = (int) $session_id;
2553
    $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
2554
2555
    $result = Database::query("SELECT * FROM $tbl_session WHERE id = $session_id");
2556
2557
    if (Database::num_rows($result) <= 0) {
2558
        return SESSION_INVISIBLE;
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2558
        return /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE;
Loading history...
2559
    }
2560
2561
    $row = Database::fetch_assoc($result);
2562
    $visibility = $row['visibility'];
2563
2564
    // I don't care the session visibility.
2565
    if (empty($row['access_start_date']) && empty($row['access_end_date'])) {
2566
        // Session duration per student.
2567
        if (isset($row['duration']) && !empty($row['duration'])) {
2568
            $duration = $row['duration'] * 24 * 60 * 60;
2569
            $courseAccess = CourseManager::getFirstCourseAccessPerSessionAndUser($session_id, $userId);
2570
2571
            // If there is a session duration but there is no previous
2572
            // access by the user, then the session is still available
2573
            if (0 == count($courseAccess)) {
2574
                return SESSION_AVAILABLE;
2575
            }
2576
2577
            $currentTime = time();
2578
            $firstAccess = isset($courseAccess['login_course_date'])
2579
                ? api_strtotime($courseAccess['login_course_date'], 'UTC')
2580
                : 0;
2581
            $userDurationData = SessionManager::getUserSession($userId, $session_id);
2582
            $userDuration = isset($userDurationData['duration'])
2583
                ? (intval($userDurationData['duration']) * 24 * 60 * 60)
2584
                : 0;
2585
2586
            $totalDuration = $firstAccess + $duration + $userDuration;
2587
2588
            return $totalDuration > $currentTime ? SESSION_AVAILABLE : SESSION_VISIBLE_READ_ONLY;
2589
        }
2590
2591
        return SESSION_AVAILABLE;
2592
    }
2593
2594
    // If start date was set.
2595
    if (!empty($row['access_start_date'])) {
2596
        $visibility = $now > api_strtotime($row['access_start_date'], 'UTC') ? SESSION_AVAILABLE : SESSION_INVISIBLE;
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2596
        $visibility = $now > api_strtotime($row['access_start_date'], 'UTC') ? SESSION_AVAILABLE : /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE;
Loading history...
2597
    } else {
2598
        // If there's no start date, assume it's available until the end date
2599
        $visibility = SESSION_AVAILABLE;
2600
    }
2601
2602
    // If the end date was set.
2603
    if (!empty($row['access_end_date'])) {
2604
        // Only if date_start said that it was ok
2605
        if (SESSION_AVAILABLE === $visibility) {
2606
            $visibility = $now < api_strtotime($row['access_end_date'], 'UTC')
2607
                ? SESSION_AVAILABLE // Date still available
2608
                : $row['visibility']; // Session ends
2609
        }
2610
    }
2611
2612
    // If I'm a coach the visibility can change in my favor depending in the coach dates.
2613
    $isCoach = api_is_coach($session_id, $courseId);
2614
2615
    if ($isCoach) {
2616
        // Test start date.
2617
        if (!empty($row['coach_access_start_date'])) {
2618
            $start = api_strtotime($row['coach_access_start_date'], 'UTC');
2619
            $visibility = $start < $now ? SESSION_AVAILABLE : SESSION_INVISIBLE;
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2619
            $visibility = $start < $now ? SESSION_AVAILABLE : /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE;
Loading history...
2620
        }
2621
2622
        // Test end date.
2623
        if (!empty($row['coach_access_end_date'])) {
2624
            if (SESSION_AVAILABLE === $visibility) {
2625
                $endDateCoach = api_strtotime($row['coach_access_end_date'], 'UTC');
2626
                $visibility = $endDateCoach >= $now ? SESSION_AVAILABLE : $row['visibility'];
2627
            }
2628
        }
2629
    }
2630
2631
    return $visibility;
2632
}
2633
2634
/**
2635
 * This function returns a (star) session icon if the session is not null and
2636
 * the user is not a student.
2637
 *
2638
 * @param int $sessionId
2639
 * @param int $statusId  User status id - if 5 (student), will return empty
2640
 *
2641
 * @return string Session icon
2642
 */
2643
function api_get_session_image($sessionId, User $user)
2644
{
2645
    $sessionId = (int) $sessionId;
2646
    $image = '';
2647
    if (!$user->isStudent()) {
2648
        // Check whether is not a student
2649
        if ($sessionId > 0) {
2650
            $image = '&nbsp;&nbsp;'.Display::getMdiIcon(
2651
                ObjectIcon::STAR,
2652
                'ch-tool-icon',
2653
                'align:absmiddle;',
2654
                ICON_SIZE_SMALL,
2655
                get_lang('Session-specific resource')
2656
            );
2657
        }
2658
    }
2659
2660
    return $image;
2661
}
2662
2663
/**
2664
 * This function add an additional condition according to the session of the course.
2665
 *
2666
 * @param int    $session_id        session id
2667
 * @param bool   $and               optional, true if more than one condition false if the only condition in the query
2668
 * @param bool   $with_base_content optional, true to accept content with session=0 as well,
2669
 *                                  false for strict session condition
2670
 * @param string $session_field
2671
 *
2672
 * @return string condition of the session
2673
 */
2674
function api_get_session_condition(
2675
    $session_id,
2676
    $and = true,
2677
    $with_base_content = false,
2678
    $session_field = 'session_id'
2679
) {
2680
    $session_id = (int) $session_id;
2681
2682
    if (empty($session_field)) {
2683
        $session_field = 'session_id';
2684
    }
2685
    // Condition to show resources by session
2686
    $condition_add = $and ? ' AND ' : ' WHERE ';
2687
2688
    if ($with_base_content) {
2689
        $condition_session = $condition_add." ( $session_field = $session_id OR $session_field = 0 OR $session_field IS NULL) ";
2690
    } else {
2691
        if (empty($session_id)) {
2692
            $condition_session = $condition_add." ($session_field = $session_id OR $session_field IS NULL)";
2693
        } else {
2694
            $condition_session = $condition_add." $session_field = $session_id ";
2695
        }
2696
    }
2697
2698
    return $condition_session;
2699
}
2700
2701
/**
2702
 * Returns the value of a setting from the web-adjustable admin config settings.
2703
 *
2704
 * WARNING true/false are stored as string, so when comparing you need to check e.g.
2705
 * if (api_get_setting('show_navigation_menu') == 'true') //CORRECT
2706
 * instead of
2707
 * if (api_get_setting('show_navigation_menu') == true) //INCORRECT
2708
 *
2709
 * @param string $variable The variable name
2710
 *
2711
 * @return string|array
2712
 */
2713
function api_get_setting($variable, $isArray = false, $key = null)
2714
{
2715
    $settingsManager = Container::getSettingsManager();
2716
    if (empty($settingsManager)) {
2717
        return '';
2718
    }
2719
    $variable = trim($variable);
2720
    // Normalize setting name: keep full name for lookup, extract short name for switch matching
2721
    $full   = $variable;
2722
    $short  = str_contains($variable, '.') ? substr($variable, strrpos($variable, '.') + 1) : $variable;
2723
2724
    switch ($short) {
2725
        case 'server_type':
2726
            $settingValue = $settingsManager->getSetting($full, true);
2727
            return $settingValue ?: 'prod';
2728
        case 'openid_authentication':
2729
        case 'service_ppt2lp':
2730
        case 'formLogin_hide_unhide_label':
2731
            return false;
2732
        case 'tool_visible_by_default_at_creation':
2733
            $values = $settingsManager->getSetting($full);
2734
            $newResult = [];
2735
            foreach ($values as $parameter) {
2736
                $newResult[$parameter] = 'true';
2737
            }
2738
2739
            return $newResult;
2740
2741
        default:
2742
            $settingValue = $settingsManager->getSetting($full, true);
2743
            if (is_string($settingValue) && $isArray && $settingValue !== '') {
2744
                // Check if the value is a valid JSON string
2745
                $decodedValue = json_decode($settingValue, true);
2746
2747
                // If it's a valid JSON string and the result is an array, return it
2748
                if (is_array($decodedValue)) {
2749
                    return $decodedValue;
2750
                }
2751
                $value = eval('return ' . rtrim($settingValue, ';') . ';');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
2752
                if (is_array($value)) {
2753
                    return $value;
2754
                }
2755
            }
2756
2757
            // If the value is not a JSON array or wasn't returned previously, continue with the normal flow
2758
            if ($key !== null && isset($settingValue[$variable][$key])) {
2759
                return $settingValue[$variable][$key];
2760
            }
2761
2762
            return $settingValue;
2763
    }
2764
}
2765
2766
/**
2767
 * @param string $variable
2768
 * @param string $option
2769
 *
2770
 * @return bool
2771
 */
2772
function api_get_setting_in_list($variable, $option)
2773
{
2774
    $value = api_get_setting($variable);
2775
2776
    return in_array($option, $value);
2777
}
2778
2779
/**
2780
 * Legacy helper: read plugin setting.
2781
 * Now reads from access_url_rel_plugin.configuration (JSON) via PluginHelper.
2782
 * Keeps BC for 'tool_enable' returning 'true'/'false' strings.
2783
 */
2784
function api_get_plugin_setting($plugin, $variable)
2785
{
2786
    $helper = \Chamilo\CoreBundle\Framework\Container::getPluginHelper();
2787
2788
    // Preserve legacy expectation for tool_enable as string 'true'/'false'
2789
    if ($variable === 'tool_enable') {
2790
        return $helper->isPluginEnabled((string) $plugin) ? 'true' : 'false';
2791
    }
2792
2793
    $value = $helper->getPluginConfigValue((string) $plugin, (string) $variable, null);
2794
2795
    // BC: many legacy callers expect strings; normalize booleans to 'true'/'false'
2796
    if (\is_bool($value)) {
2797
        return $value ? 'true' : 'false';
2798
    }
2799
2800
    // If the value is serialized in old code paths, keep it as-is.
2801
    // For arrays/objects coming from JSON config, return them directly.
2802
    return $value;
2803
}
2804
2805
/**
2806
 * Returns the value of a setting from the web-adjustable admin config settings.
2807
 */
2808
function api_get_settings_params($params)
2809
{
2810
    $table = Database::get_main_table(TABLE_MAIN_SETTINGS);
2811
2812
    return Database::select('*', $table, ['where' => $params]);
2813
}
2814
2815
/**
2816
 * @param array $params example: [id = ? => '1']
2817
 *
2818
 * @return array
2819
 */
2820
function api_get_settings_params_simple($params)
2821
{
2822
    $table = Database::get_main_table(TABLE_MAIN_SETTINGS);
2823
2824
    return Database::select('*', $table, ['where' => $params], 'one');
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::select(...re' => $params), 'one') also could return the type integer which is incompatible with the documented return type array.
Loading history...
2825
}
2826
2827
/**
2828
 * Returns the value of a setting from the web-adjustable admin config settings.
2829
 */
2830
function api_delete_settings_params($params)
2831
{
2832
    $table = Database::get_main_table(TABLE_MAIN_SETTINGS);
2833
2834
    return Database::delete($table, $params);
2835
}
2836
2837
/**
2838
 * Returns an escaped version of $_SERVER['PHP_SELF'] to avoid XSS injection.
2839
 *
2840
 * @return string Escaped version of $_SERVER['PHP_SELF']
2841
 */
2842
function api_get_self()
2843
{
2844
    return htmlentities($_SERVER['PHP_SELF']);
2845
}
2846
2847
/**
2848
 * Checks whether current user is a platform administrator.
2849
 *
2850
 * @param bool $allowSessionAdmins Whether session admins should be considered admins or not
2851
 * @param bool $allowDrh           Whether HR directors should be considered admins or not
2852
 *
2853
 * @return bool true if the user has platform admin rights,
2854
 *              false otherwise
2855
 *
2856
 * @see usermanager::is_admin(user_id) for a user-id specific function
2857
 */
2858
function api_is_platform_admin($allowSessionAdmins = false, $allowDrh = false)
2859
{
2860
    $currentUser = api_get_current_user();
2861
2862
    if (null === $currentUser) {
2863
        return false;
2864
    }
2865
2866
    $isAdmin = $currentUser->isAdmin() || $currentUser->isSuperAdmin();
2867
2868
    if ($isAdmin) {
2869
        return true;
2870
    }
2871
2872
    if ($allowSessionAdmins && $currentUser->isSessionAdmin()) {
2873
        return true;
2874
    }
2875
2876
    if ($allowDrh && $currentUser->isHRM()) {
2877
        return true;
2878
    }
2879
2880
    return false;
2881
}
2882
2883
/**
2884
 * Checks whether the user given as user id is in the admin table.
2885
 *
2886
 * @param int $user_id If none provided, will use current user
2887
 * @param int $url     URL ID. If provided, also check if the user is active on given URL
2888
 *
2889
 * @return bool True if the user is admin, false otherwise
2890
 */
2891
function api_is_platform_admin_by_id($user_id = null, $url = null)
2892
{
2893
    $user_id = (int) $user_id;
2894
    if (empty($user_id)) {
2895
        $user_id = api_get_user_id();
2896
    }
2897
    $admin_table = Database::get_main_table(TABLE_MAIN_ADMIN);
2898
    $sql = "SELECT * FROM $admin_table WHERE user_id = $user_id";
2899
    $res = Database::query($sql);
2900
    $is_admin = 1 === Database::num_rows($res);
2901
    if (!$is_admin || !isset($url)) {
2902
        return $is_admin;
2903
    }
2904
    // We get here only if $url is set
2905
    $url = (int) $url;
2906
    $url_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
2907
    $sql = "SELECT * FROM $url_user_table
2908
            WHERE access_url_id = $url AND user_id = $user_id";
2909
    $res = Database::query($sql);
2910
2911
    return 1 === Database::num_rows($res);
2912
}
2913
2914
/**
2915
 * Checks whether current user is allowed to create courses.
2916
 *
2917
 * @return bool true if the user has course creation rights,
2918
 *              false otherwise
2919
 */
2920
function api_is_allowed_to_create_course()
2921
{
2922
    if (api_is_platform_admin()) {
2923
        return true;
2924
    }
2925
2926
    // Teachers can only create courses
2927
    if (api_is_teacher()) {
2928
        if ('true' === api_get_setting('workflows.allow_users_to_create_courses')) {
2929
            return true;
2930
        } else {
2931
            return false;
2932
        }
2933
    }
2934
2935
    return Session::read('is_allowedCreateCourse');
2936
}
2937
2938
/**
2939
 * Checks whether the current user is a course administrator.
2940
 *
2941
 * @return bool True if current user is a course administrator
2942
 */
2943
function api_is_course_admin()
2944
{
2945
    if (api_is_platform_admin()) {
2946
        return true;
2947
    }
2948
2949
    $user = api_get_current_user();
2950
    if ($user) {
2951
        if (
2952
            $user->hasRole('ROLE_CURRENT_COURSE_SESSION_TEACHER') ||
2953
            $user->hasRole('ROLE_CURRENT_COURSE_TEACHER')
2954
        ) {
2955
            return true;
2956
        }
2957
    }
2958
2959
    return false;
2960
}
2961
2962
/**
2963
 * Checks whether the current user is a course coach
2964
 * Based on the presence of user in session_rel_user.relation_type (as session general coach, value 3).
2965
 *
2966
 * @return bool True if current user is a course coach
2967
 */
2968
function api_is_session_general_coach()
2969
{
2970
    return Session::read('is_session_general_coach');
2971
}
2972
2973
/**
2974
 * Checks whether the current user is a course tutor
2975
 * Based on the presence of user in session_rel_course_rel_user.user_id with status = 2.
2976
 *
2977
 * @return bool True if current user is a course tutor
2978
 */
2979
function api_is_course_tutor()
2980
{
2981
    return Session::read('is_courseTutor');
2982
}
2983
2984
/**
2985
 * @param int $user_id
2986
 * @param int $courseId
2987
 * @param int $session_id
2988
 *
2989
 * @return bool
2990
 */
2991
function api_is_course_session_coach($user_id, $courseId, $session_id)
2992
{
2993
    $session_table = Database::get_main_table(TABLE_MAIN_SESSION);
2994
    $session_rel_course_rel_user_table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
2995
2996
    $user_id = (int) $user_id;
2997
    $session_id = (int) $session_id;
2998
    $courseId = (int) $courseId;
2999
3000
    $sql = "SELECT DISTINCT session.id
3001
            FROM $session_table
3002
            INNER JOIN $session_rel_course_rel_user_table session_rc_ru
3003
            ON session.id = session_rc_ru.session_id
3004
            WHERE
3005
                session_rc_ru.user_id = '".$user_id."'  AND
3006
                session_rc_ru.c_id = '$courseId' AND
3007
                session_rc_ru.status = ".SessionEntity::COURSE_COACH." AND
3008
                session_rc_ru.session_id = '$session_id'";
3009
    $result = Database::query($sql);
3010
3011
    return Database::num_rows($result) > 0;
3012
}
3013
3014
/**
3015
 * Checks whether the current user is a course or session coach.
3016
 */
3017
function api_is_coach(int $session_id = 0, ?int $courseId = null, bool $check_student_view = true, int $userId = 0): bool
3018
{
3019
    $userId = empty($userId) ? api_get_user_id() :  $userId;
3020
3021
    if (empty($session_id)) {
3022
        $session_id = api_get_session_id();
3023
    }
3024
3025
    // The student preview was on
3026
    if ($check_student_view && api_is_student_view_active()) {
3027
        return false;
3028
    }
3029
3030
    if (empty($courseId)) {
3031
        $courseId = api_get_course_int_id();
3032
    }
3033
3034
    $session_table = Database::get_main_table(TABLE_MAIN_SESSION);
3035
    $session_rel_course_rel_user_table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3036
    $tblSessionRelUser = Database::get_main_table(TABLE_MAIN_SESSION_USER);
3037
    $sessionIsCoach = [];
3038
3039
    if (!empty($courseId)) {
3040
        $sql = "SELECT DISTINCT s.id, title, access_start_date, access_end_date
3041
                FROM $session_table s
3042
                INNER JOIN $session_rel_course_rel_user_table session_rc_ru
3043
                ON session_rc_ru.session_id = s.id AND session_rc_ru.user_id = '".$userId."'
3044
                WHERE
3045
                    session_rc_ru.c_id = '$courseId' AND
3046
                    session_rc_ru.status =".SessionEntity::COURSE_COACH." AND
3047
                    session_rc_ru.session_id = '$session_id'";
3048
        $result = Database::query($sql);
3049
        $sessionIsCoach = Database::store_result($result);
3050
    }
3051
3052
    if (!empty($session_id)) {
3053
        $sql = "SELECT DISTINCT s.id
3054
                FROM $session_table AS s
3055
                INNER JOIN $tblSessionRelUser sru
3056
                ON s.id = sru.session_id
3057
                WHERE
3058
                    sru.user_id = $userId AND
3059
                    s.id = $session_id AND
3060
                    sru.relation_type = ".SessionEntity::GENERAL_COACH."
3061
                ORDER BY s.access_start_date, s.access_end_date, s.title";
3062
        $result = Database::query($sql);
3063
        if (!empty($sessionIsCoach)) {
3064
            $sessionIsCoach = array_merge(
3065
                $sessionIsCoach,
3066
                Database::store_result($result)
3067
            );
3068
        } else {
3069
            $sessionIsCoach = Database::store_result($result);
3070
        }
3071
    }
3072
3073
    return count($sessionIsCoach) > 0;
3074
}
3075
3076
function api_user_has_role(string $role, ?User $user = null): bool
3077
{
3078
    if (null === $user) {
3079
        $user = api_get_current_user();
3080
    }
3081
3082
    if (null === $user) {
3083
        return false;
3084
    }
3085
3086
    return $user->hasRole($role);
3087
}
3088
3089
function api_is_allowed_in_course(): bool
3090
{
3091
    if (api_is_platform_admin()) {
3092
        return true;
3093
    }
3094
3095
    $user = api_get_current_user();
3096
    if ($user instanceof User) {
3097
        if ($user->hasRole('ROLE_CURRENT_COURSE_SESSION_STUDENT') ||
3098
            $user->hasRole('ROLE_CURRENT_COURSE_SESSION_TEACHER') ||
3099
            $user->hasRole('ROLE_CURRENT_COURSE_STUDENT') ||
3100
            $user->hasRole('ROLE_CURRENT_COURSE_TEACHER')
3101
        ) {
3102
            return true;
3103
        }
3104
    }
3105
3106
    return false;
3107
}
3108
3109
/**
3110
 * Checks whether current user is a student boss.
3111
 */
3112
function api_is_student_boss(?User $user = null): bool
3113
{
3114
    return api_user_has_role('ROLE_STUDENT_BOSS', $user);
3115
}
3116
3117
/**
3118
 * Checks whether the current user is a session administrator.
3119
 *
3120
 * @return bool True if current user is a course administrator
3121
 */
3122
function api_is_session_admin(?User $user = null)
3123
{
3124
    return api_user_has_role('ROLE_SESSION_MANAGER', $user);
3125
}
3126
3127
/**
3128
 * Checks whether the current user is a human resources manager.
3129
 *
3130
 * @return bool True if current user is a human resources manager
3131
 */
3132
function api_is_drh()
3133
{
3134
    return api_user_has_role('ROLE_HR');
3135
}
3136
3137
/**
3138
 * Checks whether the current user is a student.
3139
 *
3140
 * @return bool True if current user is a human resources manager
3141
 */
3142
function api_is_student()
3143
{
3144
    return api_user_has_role('ROLE_STUDENT');
3145
}
3146
3147
/**
3148
 * Checks whether the current user has the status 'teacher'.
3149
 *
3150
 * @return bool True if current user is a human resources manager
3151
 */
3152
function api_is_teacher()
3153
{
3154
    return api_user_has_role('ROLE_TEACHER');
3155
}
3156
3157
/**
3158
 * Checks whether the current user is a invited user.
3159
 *
3160
 * @return bool
3161
 */
3162
function api_is_invitee()
3163
{
3164
    return api_user_has_role('ROLE_INVITEE');
3165
}
3166
3167
/**
3168
 * This function checks whether a session is assigned into a category.
3169
 *
3170
 * @param int       - session id
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
3171
 * @param string    - category name
3172
 *
3173
 * @return bool - true if is found, otherwise false
3174
 */
3175
function api_is_session_in_category($session_id, $category_name)
3176
{
3177
    $session_id = (int) $session_id;
3178
    $category_name = Database::escape_string($category_name);
3179
    $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
3180
    $tbl_session_category = Database::get_main_table(TABLE_MAIN_SESSION_CATEGORY);
3181
3182
    $sql = "SELECT 1
3183
            FROM $tbl_session
3184
            WHERE $session_id IN (
3185
                SELECT s.id FROM $tbl_session s, $tbl_session_category sc
3186
                WHERE
3187
                  s.session_category_id = sc.id AND
3188
                  sc.name LIKE '%$category_name'
3189
            )";
3190
    $rs = Database::query($sql);
3191
3192
    if (Database::num_rows($rs) > 0) {
3193
        return true;
3194
    }
3195
3196
    return false;
3197
}
3198
3199
/**
3200
 * Displays options for switching between student view and course manager view.
3201
 *
3202
 * Changes in version 1.2 (Patrick Cool)
3203
 * Student view switch now behaves as a real switch. It maintains its current state until the state
3204
 * is changed explicitly
3205
 *
3206
 * Changes in version 1.1 (Patrick Cool)
3207
 * student view now works correctly in subfolders of the document tool
3208
 * student view works correctly in the new links tool
3209
 *
3210
 * Example code for using this in your tools:
3211
 * //if ($is_courseAdmin && api_get_setting('student_view_enabled') == 'true') {
3212
 * //   display_tool_view_option($isStudentView);
3213
 * //}
3214
 * //and in later sections, use api_is_allowed_to_edit()
3215
 *
3216
 * @author Roan Embrechts
3217
 * @author Patrick Cool
3218
 * @author Julio Montoya, changes added in Chamilo
3219
 *
3220
 * @version 1.2
3221
 *
3222
 * @todo rewrite code so it is easier to understand
3223
 */
3224
function api_display_tool_view_option()
3225
{
3226
    if ('true' != api_get_setting('student_view_enabled')) {
3227
        return '';
3228
    }
3229
3230
    $sourceurl = '';
3231
    $is_framed = false;
3232
    // Exceptions apply for all multi-frames pages
3233
    if (false !== strpos($_SERVER['REQUEST_URI'], 'chat/chat_banner.php')) {
3234
        // The chat is a multiframe bit that doesn't work too well with the student_view, so do not show the link
3235
        return '';
3236
    }
3237
3238
    // Uncomment to remove student view link from document view page
3239
    if (false !== strpos($_SERVER['REQUEST_URI'], 'lp/lp_header.php')) {
3240
        if (empty($_GET['lp_id'])) {
3241
            return '';
3242
        }
3243
        $sourceurl = substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], '?'));
3244
        $sourceurl = str_replace(
3245
            'lp/lp_header.php',
3246
            'lp/lp_controller.php?'.api_get_cidreq().'&action=view&lp_id='.intval($_GET['lp_id']).'&isStudentView='.('studentview' == $_SESSION['studentview'] ? 'false' : 'true'),
3247
            $sourceurl
3248
        );
3249
        //showinframes doesn't handle student view anyway...
3250
        //return '';
3251
        $is_framed = true;
3252
    }
3253
3254
    // Check whether the $_SERVER['REQUEST_URI'] contains already url parameters (thus a questionmark)
3255
    if (!$is_framed) {
3256
        if (false === strpos($_SERVER['REQUEST_URI'], '?')) {
3257
            $sourceurl = api_get_self().'?'.api_get_cidreq();
3258
        } else {
3259
            $sourceurl = $_SERVER['REQUEST_URI'];
3260
        }
3261
    }
3262
3263
    $output_string = '';
3264
    if (!empty($_SESSION['studentview'])) {
3265
        if ('studentview' == $_SESSION['studentview']) {
3266
            // We have to remove the isStudentView=true from the $sourceurl
3267
            $sourceurl = str_replace('&isStudentView=true', '', $sourceurl);
3268
            $sourceurl = str_replace('&isStudentView=false', '', $sourceurl);
3269
            $output_string .= '<a class="btn btn--primary btn-sm" href="'.$sourceurl.'&isStudentView=false" target="_self">'.
3270
                Display::getMdiIcon('eye').' '.get_lang('Switch to teacher view').'</a>';
3271
        } elseif ('teacherview' == $_SESSION['studentview']) {
3272
            // Switching to teacherview
3273
            $sourceurl = str_replace('&isStudentView=true', '', $sourceurl);
3274
            $sourceurl = str_replace('&isStudentView=false', '', $sourceurl);
3275
            $output_string .= '<a class="btn btn--plain btn-sm" href="'.$sourceurl.'&isStudentView=true" target="_self">'.
3276
                Display::getMdiIcon('eye').' '.get_lang('Switch to student view').'</a>';
3277
        }
3278
    } else {
3279
        $output_string .= '<a class="btn btn--plain btn-sm" href="'.$sourceurl.'&isStudentView=true" target="_self">'.
3280
            Display::getMdiIcon('eye').' '.get_lang('Switch to student view').'</a>';
3281
    }
3282
    $output_string = Security::remove_XSS($output_string);
3283
    $html = Display::tag('div', $output_string, ['class' => 'view-options']);
3284
3285
    return $html;
3286
}
3287
3288
/**
3289
 * Determines whether the current user is allowed to edit the current context.
3290
 *
3291
 * This includes checks for platform admin, course admin, tutor, coach,
3292
 * session coach, and optionally verifies if the user is in student view mode.
3293
 * If not in a course context, it falls back to a role-based permission system.
3294
 *
3295
 * @param bool $tutor             Allow if the user is a tutor.
3296
 * @param bool $coach             Allow if the user is a coach and setting allows it.
3297
 * @param bool $session_coach     Allow if the user is a session coach.
3298
 * @param bool $check_student_view Check if student view mode is active.
3299
 *
3300
 * @return bool True if the user is allowed to edit, false otherwise.
3301
 */
3302
function api_is_allowed_to_edit(
3303
    $tutor = false,
3304
    $coach = false,
3305
    $session_coach = false,
3306
    $check_student_view = true
3307
) {
3308
    $allowSessionAdminEdit = 'true' === api_get_setting('session.session_admins_edit_courses_content');
3309
    $sessionId = api_get_session_id();
3310
    $sessionVisibility = api_get_session_visibility($sessionId);
0 ignored issues
show
Deprecated Code introduced by
The function api_get_session_visibility() has been deprecated: Use Session::setAccessVisibilityByUser() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3310
    $sessionVisibility = /** @scrutinizer ignore-deprecated */ api_get_session_visibility($sessionId);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
3311
    $studentView = api_is_student_view_active();
3312
    $isAllowed = false;
3313
3314
    // If platform admin, allow unless student view is active
3315
    if (api_is_platform_admin($allowSessionAdminEdit)) {
3316
        if ($check_student_view && $studentView) {
3317
            $isAllowed = false;
3318
        } else {
3319
            return true;
3320
        }
3321
    }
3322
3323
    // Respect session course read-only mode from extra field
3324
    if ($sessionId && 'true' === api_get_setting('session.session_courses_read_only_mode')) {
3325
        $efv = new ExtraFieldValue('course');
3326
        $lock = $efv->get_values_by_handler_and_field_variable(
3327
            api_get_course_int_id(),
3328
            'session_courses_read_only_mode'
3329
        );
3330
        if (!empty($lock['value'])) {
3331
            return false;
3332
        }
3333
    }
3334
3335
    $isCourseAdmin = api_is_course_admin();
3336
    $isCoach = api_is_coach(0, null, $check_student_view);
3337
3338
    if (!$isCourseAdmin && $tutor) {
3339
        $isCourseAdmin = api_is_course_tutor();
3340
    }
3341
3342
    if (!$isCourseAdmin && $coach) {
3343
        if (SESSION_VISIBLE_READ_ONLY == $sessionVisibility) {
3344
            $isCoach = false;
3345
        }
3346
        if ('true' === api_get_setting('allow_coach_to_edit_course_session')) {
3347
            $isCourseAdmin = $isCoach;
3348
        }
3349
    }
3350
3351
    if (!$isCourseAdmin && $session_coach) {
3352
        $isCourseAdmin = $isCoach;
3353
    }
3354
3355
    // Handle student view mode
3356
    if ('true' === api_get_setting('student_view_enabled')) {
3357
        if (!empty($sessionId)) {
3358
            if (SESSION_VISIBLE_READ_ONLY == $sessionVisibility) {
3359
                $isCoach = false;
3360
            }
3361
            if ('true' === api_get_setting('allow_coach_to_edit_course_session')) {
3362
                $isAllowed = $isCoach;
3363
            }
3364
3365
            if ($check_student_view) {
3366
                $isAllowed = $isAllowed && !$studentView;
3367
            }
3368
        } else {
3369
            $isAllowed = $isCourseAdmin;
3370
            if ($check_student_view) {
3371
                $isAllowed = $isCourseAdmin && !$studentView;
3372
            }
3373
        }
3374
3375
        if ($isAllowed) {
3376
            return true;
3377
        }
3378
    } else {
3379
        if ($isCourseAdmin) {
3380
            return true;
3381
        }
3382
    }
3383
3384
    // Final fallback: permission-based system (only if nothing before returned true)
3385
    $courseId = api_get_course_id();
3386
    $inCourse = !empty($courseId) && $courseId != -1;
3387
3388
    if (!$inCourse) {
3389
        $userRoles = api_get_user_roles();
3390
        $feature = api_detect_feature_context();
3391
        $permission = $feature.':edit';
3392
3393
        return api_get_permission($permission, $userRoles);
3394
    }
3395
3396
    return $isAllowed;
3397
}
3398
3399
/**
3400
 * Returns the current main feature (module) based on the current script path.
3401
 * Used to determine permissions for non-course tools.
3402
 */
3403
function api_detect_feature_context(): string
3404
{
3405
    $script = $_SERVER['SCRIPT_NAME'] ?? '';
3406
    $script = basename($script);
3407
3408
    $map = [
3409
        'user_list.php' => 'user',
3410
        'user_add.php' => 'user',
3411
        'user_edit.php' => 'user',
3412
        'session_list.php' => 'session',
3413
        'session_add.php' => 'session',
3414
        'session_edit.php' => 'session',
3415
        'skill_list.php' => 'skill',
3416
        'skill_edit.php' => 'skill',
3417
        'badge_list.php' => 'badge',
3418
        'settings.php' => 'settings',
3419
        'course_list.php' => 'course',
3420
    ];
3421
3422
    if (isset($map[$script])) {
3423
        return $map[$script];
3424
    }
3425
3426
    if (preg_match('#/main/([a-z_]+)/#i', $_SERVER['SCRIPT_NAME'], $matches)) {
3427
        return $matches[1];
3428
    }
3429
3430
    return 'unknown';
3431
}
3432
3433
/**
3434
 * Returns true if user is a course coach of at least one course in session.
3435
 *
3436
 * @param int $sessionId
3437
 *
3438
 * @return bool
3439
 */
3440
function api_is_coach_of_course_in_session($sessionId)
3441
{
3442
    if (api_is_platform_admin()) {
3443
        return true;
3444
    }
3445
3446
    $userId = api_get_user_id();
3447
    $courseList = UserManager::get_courses_list_by_session(
3448
        $userId,
3449
        $sessionId
3450
    );
3451
3452
    // Session visibility.
3453
    $visibility = api_get_session_visibility(
0 ignored issues
show
Deprecated Code introduced by
The function api_get_session_visibility() has been deprecated: Use Session::setAccessVisibilityByUser() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3453
    $visibility = /** @scrutinizer ignore-deprecated */ api_get_session_visibility(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
3454
        $sessionId,
3455
        null,
3456
        false
3457
    );
3458
3459
    if (SESSION_VISIBLE != $visibility && !empty($courseList)) {
3460
        // Course Coach session visibility.
3461
        $blockedCourseCount = 0;
3462
        $closedVisibilityList = [
3463
            COURSE_VISIBILITY_CLOSED,
3464
            COURSE_VISIBILITY_HIDDEN,
3465
        ];
3466
3467
        foreach ($courseList as $course) {
3468
            // Checking session visibility
3469
            $sessionCourseVisibility = api_get_session_visibility(
0 ignored issues
show
Deprecated Code introduced by
The function api_get_session_visibility() has been deprecated: Use Session::setAccessVisibilityByUser() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3469
            $sessionCourseVisibility = /** @scrutinizer ignore-deprecated */ api_get_session_visibility(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
3470
                $sessionId,
3471
                $course['real_id']
3472
            );
3473
3474
            $courseIsVisible = !in_array(
3475
                $course['visibility'],
3476
                $closedVisibilityList
3477
            );
3478
            if (false === $courseIsVisible || SESSION_INVISIBLE == $sessionCourseVisibility) {
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3478
            if (false === $courseIsVisible || /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE == $sessionCourseVisibility) {
Loading history...
3479
                $blockedCourseCount++;
3480
            }
3481
        }
3482
3483
        // If all courses are blocked then no show in the list.
3484
        if ($blockedCourseCount === count($courseList)) {
3485
            $visibility = SESSION_INVISIBLE;
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3485
            $visibility = /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE;
Loading history...
3486
        } else {
3487
            $visibility = SESSION_VISIBLE;
3488
        }
3489
    }
3490
3491
    switch ($visibility) {
3492
        case SESSION_VISIBLE_READ_ONLY:
3493
        case SESSION_VISIBLE:
3494
        case SESSION_AVAILABLE:
3495
            return true;
3496
            break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
3497
        case SESSION_INVISIBLE:
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3497
        case /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE:
Loading history...
3498
            return false;
3499
    }
3500
3501
    return false;
3502
}
3503
3504
/**
3505
 * Checks if a student can edit contents in a session depending
3506
 * on the session visibility.
3507
 *
3508
 * @param bool $tutor Whether to check if the user has the tutor role
3509
 * @param bool $coach Whether to check if the user has the coach role
3510
 *
3511
 * @return bool true: the user has the rights to edit, false: he does not
3512
 */
3513
function api_is_allowed_to_session_edit($tutor = false, $coach = false)
3514
{
3515
    if (api_is_allowed_to_edit($tutor, $coach)) {
3516
        // If I'm a teacher, I will return true in order to not affect the normal behaviour of Chamilo tools.
3517
        return true;
3518
    } else {
3519
        $sessionId = api_get_session_id();
3520
3521
        if (0 == $sessionId) {
3522
            // I'm not in a session so i will return true to not affect the normal behaviour of Chamilo tools.
3523
            return true;
3524
        } else {
3525
            // I'm in a session and I'm a student
3526
            // Get the session visibility
3527
            $session_visibility = api_get_session_visibility($sessionId);
0 ignored issues
show
Deprecated Code introduced by
The function api_get_session_visibility() has been deprecated: Use Session::setAccessVisibilityByUser() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3527
            $session_visibility = /** @scrutinizer ignore-deprecated */ api_get_session_visibility($sessionId);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
3528
            // if 5 the session is still available
3529
            switch ($session_visibility) {
3530
                case SESSION_VISIBLE_READ_ONLY: // 1
3531
                    return false;
3532
                case SESSION_VISIBLE:           // 2
3533
                    return true;
3534
                case SESSION_INVISIBLE:         // 3
0 ignored issues
show
introduced by
The constant SESSION_INVISIBLE has been deprecated: Use Session::INVISIBLE ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3534
                case /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE:         // 3
Loading history...
3535
                    return false;
3536
                case SESSION_AVAILABLE:         //5
3537
                    return true;
3538
            }
3539
        }
3540
    }
3541
3542
    return false;
3543
}
3544
3545
/**
3546
 * Current user is anon?
3547
 *
3548
 * @return bool true if this user is anonymous, false otherwise
3549
 */
3550
function api_is_anonymous()
3551
{
3552
    return !Container::getAuthorizationChecker()->isGranted('IS_AUTHENTICATED');
3553
}
3554
3555
/**
3556
 * Displays message "You are not allowed here..." and exits the entire script.
3557
 */
3558
function api_not_allowed(
3559
    bool $printHeaders = false,
3560
    string $message = null,
3561
    int $responseCode = 0,
3562
    string $severity = 'warning'
3563
): never {
3564
    throw new NotAllowedException(
3565
        $message ?: get_lang('You are not allowed to see this page. Either your connection has expired or you are trying to access a page for which you do not have the sufficient privileges.'),
3566
        $severity,
3567
        403,
3568
        [],
3569
        $responseCode,
3570
        null
3571
    );
3572
}
3573
3574
/**
3575
 * @param string $languageIsoCode
3576
 *
3577
 * @return string
3578
 */
3579
function languageToCountryIsoCode($languageIsoCode)
3580
{
3581
    $allow = ('true' === api_get_setting('language.language_flags_by_country'));
3582
3583
    // @todo save in DB
3584
    switch ($languageIsoCode) {
3585
        case 'ar':
3586
            $country = 'ae';
3587
            break;
3588
        case 'bs':
3589
            $country = 'ba';
3590
            break;
3591
        case 'ca':
3592
            $country = 'es';
3593
            if ($allow) {
3594
                $country = 'catalan';
3595
            }
3596
            break;
3597
        case 'cs':
3598
            $country = 'cz';
3599
            break;
3600
        case 'da':
3601
            $country = 'dk';
3602
            break;
3603
        case 'el':
3604
            $country = 'ae';
3605
            break;
3606
        case 'en':
3607
            $country = 'gb';
3608
            break;
3609
        case 'eu': // Euskera
3610
            $country = 'es';
3611
            if ($allow) {
3612
                $country = 'basque';
3613
            }
3614
            break;
3615
        case 'gl': // galego
3616
            $country = 'es';
3617
            if ($allow) {
3618
                $country = 'galician';
3619
            }
3620
            break;
3621
        case 'he':
3622
            $country = 'il';
3623
            break;
3624
        case 'ja':
3625
            $country = 'jp';
3626
            break;
3627
        case 'ka':
3628
            $country = 'ge';
3629
            break;
3630
        case 'ko':
3631
            $country = 'kr';
3632
            break;
3633
        case 'ms':
3634
            $country = 'my';
3635
            break;
3636
        case 'pt-BR':
3637
            $country = 'br';
3638
            break;
3639
        case 'qu':
3640
            $country = 'pe';
3641
            break;
3642
        case 'sl':
3643
            $country = 'si';
3644
            break;
3645
        case 'sv':
3646
            $country = 'se';
3647
            break;
3648
        case 'uk': // Ukraine
3649
            $country = 'ua';
3650
            break;
3651
        case 'zh-TW':
3652
        case 'zh':
3653
            $country = 'cn';
3654
            break;
3655
        default:
3656
            $country = $languageIsoCode;
3657
            break;
3658
    }
3659
    $country = strtolower($country);
3660
3661
    return $country;
3662
}
3663
3664
/**
3665
 * Returns a list of all the languages that are made available by the admin.
3666
 *
3667
 * @return array An array with all languages. Structure of the array is
3668
 *               array['name'] = An array with the name of every language
3669
 *               array['folder'] = An array with the corresponding names of the language-folders in the filesystem
3670
 */
3671
function api_get_languages()
3672
{
3673
    $table = Database::get_main_table(TABLE_MAIN_LANGUAGE);
3674
    $sql = "SELECT * FROM $table WHERE available='1'
3675
            ORDER BY original_name ASC";
3676
    $result = Database::query($sql);
3677
    $languages = [];
3678
    while ($row = Database::fetch_assoc($result)) {
3679
        $languages[$row['isocode']] = $row['original_name'];
3680
    }
3681
3682
    return $languages;
3683
}
3684
3685
/**
3686
 * Returns the platform default language isocode.
3687
 * Tries 'language.platform_language' (isocode). If empty, falls back to 'platformLanguage' (english_name) and resolves to isocode.
3688
 */
3689
function api_get_platform_default_isocode(): ?string
3690
{
3691
    // Preferred: isocode stored under 'language.platform_language'
3692
    $iso = trim((string) api_get_setting('language.platform_language'));
3693
    if ($iso !== '') {
3694
        return $iso;
3695
    }
3696
3697
    // Fallback: older config storing english_name
3698
    $englishName = trim((string) api_get_setting('platformLanguage'));
3699
    if ($englishName === '') {
3700
        return null;
3701
    }
3702
3703
    $langTable = Database::get_main_table(TABLE_MAIN_LANGUAGE);
3704
    $safe = Database::escape_string($englishName);
3705
    $sql  = "SELECT isocode FROM $langTable WHERE english_name = '$safe' LIMIT 1";
3706
    $res  = Database::query($sql);
3707
    if ($row = Database::fetch_assoc($res)) {
3708
        return $row['isocode'];
3709
    }
3710
3711
    return null;
3712
}
3713
3714
/**
3715
 * Returns available languages (available=1) plus the platform default language even if not available.
3716
 * Ensures the platform default never disappears from dropdowns.
3717
 *
3718
 * @return array<string,string> isocode => original_name
3719
 */
3720
function api_get_languages_with_platform_default(): array
3721
{
3722
    $langTable = Database::get_main_table(TABLE_MAIN_LANGUAGE);
3723
3724
    $sql = "SELECT isocode, original_name
3725
            FROM $langTable
3726
            WHERE available = '1'
3727
            ORDER BY english_name ASC";
3728
    $res = Database::query($sql);
3729
3730
    $languages = [];
3731
    while ($row = Database::fetch_assoc($res)) {
3732
        $languages[$row['isocode']] = $row['original_name'];
3733
    }
3734
3735
    // Ensure platform default is present even if not available
3736
    $defaultIso = api_get_platform_default_isocode();
3737
    if ($defaultIso && !isset($languages[$defaultIso])) {
3738
        $safe   = Database::escape_string($defaultIso);
3739
        $sqlDef = "SELECT isocode, original_name FROM $langTable WHERE isocode = '$safe' LIMIT 1";
3740
        $resDef = Database::query($sqlDef);
3741
        if ($row = Database::fetch_assoc($resDef)) {
3742
            $languages[$row['isocode']] = $row['original_name'];
3743
        }
3744
    }
3745
3746
    return $languages;
3747
}
3748
3749
/**
3750
 * Returns the id (the database id) of a language.
3751
 *
3752
 * @param   string  language name (the corresponding name of the language-folder in the filesystem)
3753
 *
3754
 * @return int id of the language
3755
 */
3756
function api_get_language_id($language)
3757
{
3758
    $tbl_language = Database::get_main_table(TABLE_MAIN_LANGUAGE);
3759
    if (empty($language)) {
3760
        return null;
3761
    }
3762
3763
    // We check the language by iscocode
3764
    $langInfo = api_get_language_from_iso($language);
3765
    if (null !== $langInfo && !empty($langInfo->getId())) {
3766
        return $langInfo->getId();
3767
    }
3768
3769
    $language = Database::escape_string($language);
3770
    $sql = "SELECT id FROM $tbl_language
3771
            WHERE english_name = '$language' LIMIT 1";
3772
    $result = Database::query($sql);
3773
    $row = Database::fetch_array($result);
3774
3775
    return $row['id'];
3776
}
3777
3778
/**
3779
 * Get the language information by its id.
3780
 *
3781
 * @param int $languageId
3782
 *
3783
 * @throws Exception
3784
 *
3785
 * @return array
3786
 */
3787
function api_get_language_info($languageId)
3788
{
3789
    if (empty($languageId)) {
3790
        return [];
3791
    }
3792
3793
    $language = Database::getManager()->find(Language::class, $languageId);
3794
3795
    if (!$language) {
3796
        return [];
3797
    }
3798
3799
    return [
3800
        'id' => $language->getId(),
3801
        'original_name' => $language->getOriginalName(),
3802
        'english_name' => $language->getEnglishName(),
3803
        'isocode' => $language->getIsocode(),
3804
        'available' => $language->getAvailable(),
3805
        'parent_id' => $language->getParent() ? $language->getParent()->getId() : null,
3806
    ];
3807
}
3808
3809
/**
3810
 * @param string $code
3811
 *
3812
 * @return Language
3813
 */
3814
function api_get_language_from_iso($code)
3815
{
3816
    $em = Database::getManager();
3817
3818
    return $em->getRepository(Language::class)->findOneBy(['isocode' => $code]);
3819
}
3820
3821
/**
3822
 * Shortcut to ThemeHelper::getVisualTheme()
3823
 */
3824
function api_get_visual_theme(): string
3825
{
3826
    $themeHelper = Container::$container->get(ThemeHelper::class);
3827
3828
    return $themeHelper->getVisualTheme();
3829
}
3830
3831
/**
3832
 * Returns a list of CSS themes currently available in the CSS folder
3833
 * The folder must have a default.css file.
3834
 *
3835
 * @param bool $getOnlyThemeFromVirtualInstance Used by the vchamilo plugin
3836
 *
3837
 * @return array list of themes directories from the css folder
3838
 *               Note: Directory names (names of themes) in the file system should contain ASCII-characters only
3839
 */
3840
function api_get_themes($getOnlyThemeFromVirtualInstance = false)
3841
{
3842
    // This configuration value is set by the vchamilo plugin
3843
    $virtualTheme = api_get_configuration_value('virtual_css_theme_folder');
3844
3845
    $readCssFolder = function ($dir) use ($virtualTheme) {
3846
        $finder = new Finder();
3847
        $themes = $finder->directories()->in($dir)->depth(0)->sortByName();
3848
        $list = [];
3849
        /** @var Symfony\Component\Finder\SplFileInfo $theme */
3850
        foreach ($themes as $theme) {
3851
            $folder = $theme->getFilename();
3852
            // A theme folder is consider if there's a default.css file
3853
            if (!file_exists($theme->getPathname().'/default.css')) {
3854
                continue;
3855
            }
3856
            $name = ucwords(str_replace('_', ' ', $folder));
3857
            if ($folder == $virtualTheme) {
3858
                continue;
3859
            }
3860
            $list[$folder] = $name;
3861
        }
3862
3863
        return $list;
3864
    };
3865
3866
    $dir = Container::getProjectDir().'var/themes/';
3867
    $list = $readCssFolder($dir);
3868
3869
    if (!empty($virtualTheme)) {
3870
        $newList = $readCssFolder($dir.'/'.$virtualTheme);
3871
        if ($getOnlyThemeFromVirtualInstance) {
3872
            return $newList;
3873
        }
3874
        $list = $list + $newList;
3875
        asort($list);
3876
    }
3877
3878
    return $list;
3879
}
3880
3881
/**
3882
 * Find the largest sort value in a given user_course_category
3883
 * This function is used when we are moving a course to a different category
3884
 * and also when a user subscribes to courses (the new course is added at the end of the main category.
3885
 *
3886
 * @param int $courseCategoryId the id of the user_course_category
3887
 * @param int $userId
3888
 *
3889
 * @return int the value of the highest sort of the user_course_category
3890
 */
3891
function api_max_sort_value($courseCategoryId, $userId)
3892
{
3893
    $user = api_get_user_entity($userId);
3894
    $userCourseCategory = Database::getManager()->getRepository(UserCourseCategory::class)->find($courseCategoryId);
3895
3896
    return null === $user ? 0 : $user->getMaxSortValue($userCourseCategory);
3897
}
3898
3899
/**
3900
 * Transforms a number of seconds in hh:mm:ss format.
3901
 *
3902
 * @author Julian Prud'homme
3903
 *
3904
 * @param int    $seconds      number of seconds
3905
 * @param string $space
3906
 * @param bool   $showSeconds
3907
 * @param bool   $roundMinutes
3908
 *
3909
 * @return string the formatted time
3910
 */
3911
function api_time_to_hms($seconds, $space = ':', $showSeconds = true, $roundMinutes = false)
3912
{
3913
    // $seconds = -1 means that we have wrong data in the db.
3914
    if (-1 == $seconds) {
3915
        return
3916
            get_lang('Unknown').
3917
            Display::getMdiIcon(
3918
                ActionIcon::INFORMATION,
3919
                'ch-tool-icon',
3920
                'align: absmiddle; hspace: 3px',
3921
                ICON_SIZE_SMALL,
3922
                get_lang('The datas about this user were registered when the calculation of time spent on the platform wasn\'t possible.')
3923
            );
3924
    }
3925
3926
    // How many hours ?
3927
    $hours = floor($seconds / 3600);
3928
3929
    // How many minutes ?
3930
    $min = floor(($seconds - ($hours * 3600)) / 60);
3931
3932
    if ($roundMinutes) {
3933
        if ($min >= 45) {
3934
            $min = 45;
3935
        }
3936
3937
        if ($min >= 30 && $min <= 44) {
3938
            $min = 30;
3939
        }
3940
3941
        if ($min >= 15 && $min <= 29) {
3942
            $min = 15;
3943
        }
3944
3945
        if ($min >= 0 && $min <= 14) {
3946
            $min = 0;
3947
        }
3948
    }
3949
3950
    // How many seconds
3951
    $sec = floor($seconds - ($hours * 3600) - ($min * 60));
3952
3953
    if ($hours < 10) {
3954
        $hours = "0$hours";
3955
    }
3956
3957
    if ($sec < 10) {
3958
        $sec = "0$sec";
3959
    }
3960
3961
    if ($min < 10) {
3962
        $min = "0$min";
3963
    }
3964
3965
    $seconds = '';
3966
    if ($showSeconds) {
3967
        $seconds = $space.$sec;
3968
    }
3969
3970
    return $hours.$space.$min.$seconds;
3971
}
3972
3973
/**
3974
 * Returns the permissions to be assigned to every newly created directory by the web-server.
3975
 * The return value is based on the platform administrator's setting
3976
 * "Administration > Configuration settings > Security > Permissions for new directories".
3977
 *
3978
 * @return int returns the permissions in the format "Owner-Group-Others, Read-Write-Execute", as an integer value
3979
 */
3980
function api_get_permissions_for_new_directories()
3981
{
3982
    static $permissions;
3983
    if (!isset($permissions)) {
3984
        $permissions = trim(api_get_setting('permissions_for_new_directories'));
3985
        // The default value 0777 is according to that in the platform administration panel after fresh system installation.
3986
        $permissions = octdec(!empty($permissions) ? $permissions : '0777');
3987
    }
3988
3989
    return $permissions;
3990
}
3991
3992
/**
3993
 * Returns the permissions to be assigned to every newly created directory by the web-server.
3994
 * The return value is based on the platform administrator's setting
3995
 * "Administration > Configuration settings > Security > Permissions for new files".
3996
 *
3997
 * @return int returns the permissions in the format
3998
 *             "Owner-Group-Others, Read-Write-Execute", as an integer value
3999
 */
4000
function api_get_permissions_for_new_files()
4001
{
4002
    static $permissions;
4003
    if (!isset($permissions)) {
4004
        $permissions = trim(api_get_setting('permissions_for_new_files'));
4005
        // The default value 0666 is according to that in the platform
4006
        // administration panel after fresh system installation.
4007
        $permissions = octdec(!empty($permissions) ? $permissions : '0666');
4008
    }
4009
4010
    return $permissions;
4011
}
4012
4013
/**
4014
 * Deletes a file, or a folder and its contents.
4015
 *
4016
 * @author      Aidan Lister <[email protected]>
4017
 *
4018
 * @version     1.0.3
4019
 *
4020
 * @param string $dirname Directory to delete
4021
 * @param       bool     Deletes only the content or not
4022
 * @param bool $strict if one folder/file fails stop the loop
4023
 *
4024
 * @return bool Returns TRUE on success, FALSE on failure
4025
 *
4026
 * @see http://aidanlister.com/2004/04/recursively-deleting-a-folder-in-php/
4027
 *
4028
 * @author      Yannick Warnier, adaptation for the Chamilo LMS, April, 2008
4029
 * @author      Ivan Tcholakov, a sanity check about Directory class creation has been added, September, 2009
4030
 */
4031
function rmdirr($dirname, $delete_only_content_in_folder = false, $strict = false)
4032
{
4033
    $res = true;
4034
    // A sanity check.
4035
    if (!file_exists($dirname)) {
4036
        return false;
4037
    }
4038
    $php_errormsg = '';
4039
    // Simple delete for a file.
4040
    if (is_file($dirname) || is_link($dirname)) {
4041
        $res = unlink($dirname);
4042
        if (false === $res) {
4043
            error_log(__FILE__.' line '.__LINE__.': '.((bool) ini_get('track_errors') ? $php_errormsg : 'Error not recorded because track_errors is off in your php.ini'), 0);
4044
        }
4045
4046
        return $res;
4047
    }
4048
4049
    // Loop through the folder.
4050
    $dir = dir($dirname);
4051
    // A sanity check.
4052
    $is_object_dir = is_object($dir);
4053
    if ($is_object_dir) {
4054
        while (false !== $entry = $dir->read()) {
4055
            // Skip pointers.
4056
            if ('.' == $entry || '..' == $entry) {
4057
                continue;
4058
            }
4059
4060
            // Recurse.
4061
            if ($strict) {
4062
                $result = rmdirr("$dirname/$entry");
4063
                if (false == $result) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
4064
                    $res = false;
4065
                    break;
4066
                }
4067
            } else {
4068
                rmdirr("$dirname/$entry");
4069
            }
4070
        }
4071
    }
4072
4073
    // Clean up.
4074
    if ($is_object_dir) {
4075
        $dir->close();
4076
    }
4077
4078
    if (false == $delete_only_content_in_folder) {
4079
        $res = rmdir($dirname);
4080
        if (false === $res) {
4081
            error_log(__FILE__.' line '.__LINE__.': '.((bool) ini_get('track_errors') ? $php_errormsg : 'error not recorded because track_errors is off in your php.ini'), 0);
4082
        }
4083
    }
4084
4085
    return $res;
4086
}
4087
4088
// TODO: This function is to be simplified. File access modes to be implemented.
4089
/**
4090
 * function adapted from a php.net comment
4091
 * copy recursively a folder.
4092
 *
4093
 * @param the source folder
4094
 * @param the dest folder
4095
 * @param an array of excluded file_name (without extension)
4096
 * @param copied_files the returned array of copied files
4097
 * @param string $source
4098
 * @param string $dest
4099
 */
4100
function copyr($source, $dest, $exclude = [], $copied_files = [])
4101
{
4102
    if (empty($dest)) {
4103
        return false;
4104
    }
4105
    // Simple copy for a file
4106
    if (is_file($source)) {
4107
        $path_info = pathinfo($source);
4108
        if (!in_array($path_info['filename'], $exclude)) {
4109
            copy($source, $dest);
4110
        }
4111
4112
        return true;
4113
    } elseif (!is_dir($source)) {
4114
        //then source is not a dir nor a file, return
4115
        return false;
4116
    }
4117
4118
    // Make destination directory.
4119
    if (!is_dir($dest)) {
4120
        mkdir($dest, api_get_permissions_for_new_directories());
4121
    }
4122
4123
    // Loop through the folder.
4124
    $dir = dir($source);
4125
    while (false !== $entry = $dir->read()) {
4126
        // Skip pointers
4127
        if ('.' == $entry || '..' == $entry) {
4128
            continue;
4129
        }
4130
4131
        // Deep copy directories.
4132
        if ($dest !== "$source/$entry") {
4133
            $files = copyr("$source/$entry", "$dest/$entry", $exclude, $copied_files);
4134
        }
4135
    }
4136
    // Clean up.
4137
    $dir->close();
4138
4139
    return true;
4140
}
4141
4142
/**
4143
 * @todo: Using DIRECTORY_SEPARATOR is not recommended, this is an obsolete approach.
4144
 * Documentation header to be added here.
4145
 *
4146
 * @param string $pathname
4147
 * @param string $base_path_document
4148
 * @param int    $session_id
4149
 *
4150
 * @return mixed True if directory already exists, false if a file already exists at
4151
 *               the destination and null if everything goes according to plan
4152
 */
4153
function copy_folder_course_session(
4154
    $pathname,
4155
    $base_path_document,
4156
    $session_id,
4157
    $course_info,
4158
    $document,
4159
    $source_course_id,
4160
    array $originalFolderNameList = [],
4161
    string $originalBaseName = ''
4162
) {
4163
    $table = Database::get_course_table(TABLE_DOCUMENT);
4164
    $session_id = intval($session_id);
4165
    $source_course_id = intval($source_course_id);
4166
4167
    // Check whether directory already exists.
4168
    if (empty($pathname) || is_dir($pathname)) {
4169
        return true;
4170
    }
4171
4172
    // Ensure that a file with the same name does not already exist.
4173
    if (is_file($pathname)) {
4174
        trigger_error('copy_folder_course_session(): File exists', E_USER_WARNING);
4175
4176
        return false;
4177
    }
4178
4179
    $baseNoDocument = str_replace('document', '', $originalBaseName);
4180
    $folderTitles = explode('/', $baseNoDocument);
4181
    $folderTitles = array_filter($folderTitles);
4182
4183
    $table = Database::get_course_table(TABLE_DOCUMENT);
4184
    $session_id = (int) $session_id;
4185
    $source_course_id = (int) $source_course_id;
4186
4187
    $course_id = $course_info['real_id'];
4188
    $folders = explode(DIRECTORY_SEPARATOR, str_replace($base_path_document.DIRECTORY_SEPARATOR, '', $pathname));
4189
    $new_pathname = $base_path_document;
4190
    $path = '';
4191
4192
    foreach ($folders as $index => $folder) {
4193
        $new_pathname .= DIRECTORY_SEPARATOR.$folder;
4194
        $path .= DIRECTORY_SEPARATOR.$folder;
4195
4196
        if (!file_exists($new_pathname)) {
4197
            $path = Database::escape_string($path);
4198
4199
            $sql = "SELECT * FROM $table
4200
                    WHERE
4201
                        c_id = $source_course_id AND
4202
                        path = '$path' AND
4203
                        filetype = 'folder' AND
4204
                        session_id = '$session_id'";
4205
            $rs1 = Database::query($sql);
4206
            $num_rows = Database::num_rows($rs1);
4207
4208
            if (0 == $num_rows) {
4209
                mkdir($new_pathname, api_get_permissions_for_new_directories());
4210
                $title = basename($new_pathname);
4211
4212
                if (isset($folderTitles[$index + 1])) {
4213
                    $checkPath = $folderTitles[$index +1];
4214
4215
                    if (isset($originalFolderNameList[$checkPath])) {
4216
                        $title = $originalFolderNameList[$checkPath];
4217
                    }
4218
                }
4219
4220
                // Insert new folder with destination session_id.
4221
                $params = [
4222
                    'c_id' => $course_id,
4223
                    'path' => $path,
4224
                    'comment' => $document->comment,
4225
                    'title' => $title,
4226
                    'filetype' => 'folder',
4227
                    'size' => '0',
4228
                    'session_id' => $session_id,
4229
                ];
4230
                Database::insert($table, $params);
4231
            }
4232
        }
4233
    } // en foreach
4234
}
4235
4236
// TODO: chmodr() is a better name. Some corrections are needed. Documentation header to be added here.
4237
/**
4238
 * @param string $path
4239
 */
4240
function api_chmod_R($path, $filemode)
4241
{
4242
    if (!is_dir($path)) {
4243
        return chmod($path, $filemode);
4244
    }
4245
4246
    $handler = opendir($path);
4247
    while ($file = readdir($handler)) {
4248
        if ('.' != $file && '..' != $file) {
4249
            $fullpath = "$path/$file";
4250
            if (!is_dir($fullpath)) {
4251
                if (!chmod($fullpath, $filemode)) {
4252
                    return false;
4253
                }
4254
            } else {
4255
                if (!api_chmod_R($fullpath, $filemode)) {
4256
                    return false;
4257
                }
4258
            }
4259
        }
4260
    }
4261
4262
    closedir($handler);
4263
4264
    return chmod($path, $filemode);
4265
}
4266
4267
// TODO: Where the following function has been copy/pased from? There is no information about author and license. Style, coding conventions...
4268
/**
4269
 * Parse info file format. (e.g: file.info).
4270
 *
4271
 * Files should use an ini-like format to specify values.
4272
 * White-space generally doesn't matter, except inside values.
4273
 * e.g.
4274
 *
4275
 * @verbatim
4276
 *   key = value
4277
 *   key = "value"
4278
 *   key = 'value'
4279
 *   key = "multi-line
4280
 *
4281
 *   value"
4282
 *   key = 'multi-line
4283
 *
4284
 *   value'
4285
 *   key
4286
 *   =
4287
 *   'value'
4288
 * @endverbatim
4289
 *
4290
 * Arrays are created using a GET-like syntax:
4291
 *
4292
 * @verbatim
4293
 *   key[] = "numeric array"
4294
 *   key[index] = "associative array"
4295
 *   key[index][] = "nested numeric array"
4296
 *   key[index][index] = "nested associative array"
4297
 * @endverbatim
4298
 *
4299
 * PHP constants are substituted in, but only when used as the entire value:
4300
 *
4301
 * Comments should start with a semi-colon at the beginning of a line.
4302
 *
4303
 * This function is NOT for placing arbitrary module-specific settings. Use
4304
 * variable_get() and variable_set() for that.
4305
 *
4306
 * Information stored in the module.info file:
4307
 * - name: The real name of the module for display purposes.
4308
 * - description: A brief description of the module.
4309
 * - dependencies: An array of shortnames of other modules this module depends on.
4310
 * - package: The name of the package of modules this module belongs to.
4311
 *
4312
 * Example of .info file:
4313
 * <code>
4314
 * @verbatim
4315
 *   name = Forum
4316
 *   description = Enables threaded discussions about general topics.
4317
 *   dependencies[] = taxonomy
4318
 *   dependencies[] = comment
4319
 *   package = Core - optional
4320
 *   version = VERSION
4321
 * @endverbatim
4322
 * </code>
4323
 *
4324
 * @param string $filename
4325
 *                         The file we are parsing. Accepts file with relative or absolute path.
4326
 *
4327
 * @return
4328
 *   The info array
4329
 */
4330
function api_parse_info_file($filename)
4331
{
4332
    $info = [];
4333
4334
    if (!file_exists($filename)) {
4335
        return $info;
4336
    }
4337
4338
    $data = file_get_contents($filename);
4339
    if (preg_match_all('
4340
        @^\s*                           # Start at the beginning of a line, ignoring leading whitespace
4341
        ((?:
4342
          [^=;\[\]]|                    # Key names cannot contain equal signs, semi-colons or square brackets,
4343
          \[[^\[\]]*\]                  # unless they are balanced and not nested
4344
        )+?)
4345
        \s*=\s*                         # Key/value pairs are separated by equal signs (ignoring white-space)
4346
        (?:
4347
          ("(?:[^"]|(?<=\\\\)")*")|     # Double-quoted string, which may contain slash-escaped quotes/slashes
4348
          (\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes
4349
          ([^\r\n]*?)                   # Non-quoted string
4350
        )\s*$                           # Stop at the next end of a line, ignoring trailing whitespace
4351
        @msx', $data, $matches, PREG_SET_ORDER)) {
4352
        $key = $value1 = $value2 = $value3 = '';
4353
        foreach ($matches as $match) {
4354
            // Fetch the key and value string.
4355
            $i = 0;
4356
            foreach (['key', 'value1', 'value2', 'value3'] as $var) {
4357
                $$var = isset($match[++$i]) ? $match[$i] : '';
4358
            }
4359
            $value = stripslashes(substr($value1, 1, -1)).stripslashes(substr($value2, 1, -1)).$value3;
4360
4361
            // Parse array syntax.
4362
            $keys = preg_split('/\]?\[/', rtrim($key, ']'));
4363
            $last = array_pop($keys);
4364
            $parent = &$info;
4365
4366
            // Create nested arrays.
4367
            foreach ($keys as $key) {
4368
                if ('' == $key) {
4369
                    $key = count($parent);
4370
                }
4371
                if (!isset($parent[$key]) || !is_array($parent[$key])) {
4372
                    $parent[$key] = [];
4373
                }
4374
                $parent = &$parent[$key];
4375
            }
4376
4377
            // Handle PHP constants.
4378
            if (defined($value)) {
4379
                $value = constant($value);
4380
            }
4381
4382
            // Insert actual value.
4383
            if ('' == $last) {
4384
                $last = count($parent);
4385
            }
4386
            $parent[$last] = $value;
4387
        }
4388
    }
4389
4390
    return $info;
4391
}
4392
4393
/**
4394
 * Gets Chamilo version from the configuration files.
4395
 *
4396
 * @return string A string of type "1.8.4", or an empty string if the version could not be found
4397
 */
4398
function api_get_version()
4399
{
4400
    return (string) api_get_configuration_value('system_version');
4401
}
4402
4403
/**
4404
 * Gets the software name (the name/brand of the Chamilo-based customized system).
4405
 *
4406
 * @return string
4407
 */
4408
function api_get_software_name()
4409
{
4410
    $name = api_get_env_variable('SOFTWARE_NAME', 'Chamilo');
4411
    return $name;
4412
}
4413
4414
function api_get_status_list()
4415
{
4416
    $list = [];
4417
    // Table of status
4418
    $list[COURSEMANAGER] = 'teacher'; // 1
4419
    $list[SESSIONADMIN] = 'session_admin'; // 3
4420
    $list[DRH] = 'drh'; // 4
4421
    $list[STUDENT] = 'user'; // 5
4422
    $list[ANONYMOUS] = 'anonymous'; // 6
4423
    $list[INVITEE] = 'invited'; // 20
4424
4425
    return $list;
4426
}
4427
4428
/**
4429
 * Checks whether status given in parameter exists in the platform.
4430
 *
4431
 * @param mixed the status (can be either int either string)
4432
 *
4433
 * @return bool if the status exists, else returns false
4434
 */
4435
function api_status_exists($status_asked)
4436
{
4437
    $list = api_get_status_list();
4438
4439
    return in_array($status_asked, $list) ? true : isset($list[$status_asked]);
4440
}
4441
4442
/**
4443
 * Checks whether status given in parameter exists in the platform. The function
4444
 * returns the status ID or false if it does not exist, but given the fact there
4445
 * is no "0" status, the return value can be checked against
4446
 * if(api_status_key()) to know if it exists.
4447
 *
4448
 * @param   mixed   The status (can be either int or string)
4449
 *
4450
 * @return mixed Status ID if exists, false otherwise
4451
 */
4452
function api_status_key($status)
4453
{
4454
    $list = api_get_status_list();
4455
4456
    return isset($list[$status]) ? $status : array_search($status, $list);
4457
}
4458
4459
/**
4460
 * Gets the status langvars list.
4461
 *
4462
 * @return string[] the list of status with their translations
4463
 */
4464
function api_get_status_langvars()
4465
{
4466
    return [
4467
        COURSEMANAGER => get_lang('Teacher'),
4468
        SESSIONADMIN => get_lang('Sessions administrator'),
4469
        DRH => get_lang('Human Resources Manager'),
4470
        STUDENT => get_lang('Learner'),
4471
        ANONYMOUS => get_lang('Anonymous'),
4472
        STUDENT_BOSS => get_lang('Student boss'),
4473
        INVITEE => get_lang('Invited'),
4474
    ];
4475
}
4476
4477
/**
4478
 * The function that retrieves all the possible settings for a certain config setting.
4479
 *
4480
 * @author Patrick Cool <[email protected]>, Ghent University
4481
 */
4482
function api_get_settings_options($var)
4483
{
4484
    $table_settings_options = Database::get_main_table(TABLE_MAIN_SETTINGS_OPTIONS);
4485
    $var = Database::escape_string($var);
4486
    $sql = "SELECT * FROM $table_settings_options
4487
            WHERE variable = '$var'
4488
            ORDER BY id";
4489
    $result = Database::query($sql);
4490
    $settings_options_array = [];
4491
    while ($row = Database::fetch_assoc($result)) {
4492
        $settings_options_array[] = $row;
4493
    }
4494
4495
    return $settings_options_array;
4496
}
4497
4498
/**
4499
 * @param array $params
4500
 */
4501
function api_set_setting_option($params)
4502
{
4503
    $table = Database::get_main_table(TABLE_MAIN_SETTINGS_OPTIONS);
4504
    if (empty($params['id'])) {
4505
        Database::insert($table, $params);
4506
    } else {
4507
        Database::update($table, $params, ['id = ? ' => $params['id']]);
4508
    }
4509
}
4510
4511
/**
4512
 * @param array $params
4513
 */
4514
function api_set_setting_simple($params)
4515
{
4516
    $table = Database::get_main_table(TABLE_MAIN_SETTINGS);
4517
    $url_id = api_get_current_access_url_id();
4518
4519
    if (empty($params['id'])) {
4520
        $params['access_url'] = $url_id;
4521
        Database::insert($table, $params);
4522
    } else {
4523
        Database::update($table, $params, ['id = ? ' => [$params['id']]]);
4524
    }
4525
}
4526
4527
/**
4528
 * @param int $id
4529
 */
4530
function api_delete_setting_option($id)
4531
{
4532
    $table = Database::get_main_table(TABLE_MAIN_SETTINGS_OPTIONS);
4533
    if (!empty($id)) {
4534
        Database::delete($table, ['id = ? ' => $id]);
4535
    }
4536
}
4537
4538
/**
4539
 * Sets a platform configuration setting to a given value.
4540
 *
4541
 * @param string    The variable we want to update
4542
 * @param string    The value we want to record
4543
 * @param string    The sub-variable if any (in most cases, this will remain null)
4544
 * @param string    The category if any (in most cases, this will remain null)
4545
 * @param int       The access_url for which this parameter is valid
4546
 * @param string $cat
4547
 *
4548
 * @return bool|null
4549
 */
4550
function api_set_setting($var, $value, $subvar = null, $cat = null, $access_url = 1)
4551
{
4552
    if (empty($var)) {
4553
        return false;
4554
    }
4555
    $t_settings = Database::get_main_table(TABLE_MAIN_SETTINGS);
4556
    $var = Database::escape_string($var);
4557
    $value = Database::escape_string($value);
4558
    $access_url = (int) $access_url;
4559
    if (empty($access_url)) {
4560
        $access_url = 1;
4561
    }
4562
    $select = "SELECT id FROM $t_settings WHERE variable = '$var' ";
4563
    if (!empty($subvar)) {
4564
        $subvar = Database::escape_string($subvar);
4565
        $select .= " AND subkey = '$subvar'";
4566
    }
4567
    if (!empty($cat)) {
4568
        $cat = Database::escape_string($cat);
4569
        $select .= " AND category = '$cat'";
4570
    }
4571
    if ($access_url > 1) {
4572
        $select .= " AND access_url = $access_url";
4573
    } else {
4574
        $select .= " AND access_url = 1 ";
4575
    }
4576
4577
    $res = Database::query($select);
4578
    if (Database::num_rows($res) > 0) {
4579
        // Found item for this access_url.
4580
        $row = Database::fetch_array($res);
4581
        $sql = "UPDATE $t_settings SET selected_value = '$value'
4582
                WHERE id = ".$row['id'];
4583
        Database::query($sql);
4584
    } else {
4585
        // Item not found for this access_url, we have to check if it exist with access_url = 1
4586
        $select = "SELECT * FROM $t_settings
4587
                   WHERE variable = '$var' AND access_url = 1 ";
4588
        // Just in case
4589
        if (1 == $access_url) {
4590
            if (!empty($subvar)) {
4591
                $select .= " AND subkey = '$subvar'";
4592
            }
4593
            if (!empty($cat)) {
4594
                $select .= " AND category = '$cat'";
4595
            }
4596
            $res = Database::query($select);
4597
            if (Database::num_rows($res) > 0) {
4598
                // We have a setting for access_url 1, but none for the current one, so create one.
4599
                $row = Database::fetch_array($res);
4600
                $insert = "INSERT INTO $t_settings (variable, subkey, type,category, selected_value, title, comment, scope, subkeytext, access_url)
4601
                        VALUES
4602
                        ('".$row['variable']."',".(!empty($row['subkey']) ? "'".$row['subkey']."'" : "NULL").",".
4603
                    "'".$row['type']."','".$row['category']."',".
4604
                    "'$value','".$row['title']."',".
4605
                    "".(!empty($row['comment']) ? "'".$row['comment']."'" : "NULL").",".(!empty($row['scope']) ? "'".$row['scope']."'" : "NULL").",".
4606
                    "".(!empty($row['subkeytext']) ? "'".$row['subkeytext']."'" : "NULL").",$access_url)";
4607
                Database::query($insert);
4608
            } else {
4609
                // Such a setting does not exist.
4610
                //error_log(__FILE__.':'.__LINE__.': Attempting to update setting '.$var.' ('.$subvar.') which does not exist at all', 0);
4611
            }
4612
        } else {
4613
            // Other access url.
4614
            if (!empty($subvar)) {
4615
                $select .= " AND subkey = '$subvar'";
4616
            }
4617
            if (!empty($cat)) {
4618
                $select .= " AND category = '$cat'";
4619
            }
4620
            $res = Database::query($select);
4621
4622
            if (Database::num_rows($res) > 0) {
4623
                // We have a setting for access_url 1, but none for the current one, so create one.
4624
                $row = Database::fetch_array($res);
4625
                if (1 == $row['access_url_changeable']) {
4626
                    $insert = "INSERT INTO $t_settings (variable,subkey, type,category, selected_value,title, comment,scope, subkeytext,access_url, access_url_changeable) VALUES
4627
                            ('".$row['variable']."',".
4628
                        (!empty($row['subkey']) ? "'".$row['subkey']."'" : "NULL").",".
4629
                        "'".$row['type']."','".$row['category']."',".
4630
                        "'$value','".$row['title']."',".
4631
                        "".(!empty($row['comment']) ? "'".$row['comment']."'" : "NULL").",".
4632
                        (!empty($row['scope']) ? "'".$row['scope']."'" : "NULL").",".
4633
                        "".(!empty($row['subkeytext']) ? "'".$row['subkeytext']."'" : "NULL").",$access_url,".$row['access_url_changeable'].")";
4634
                    Database::query($insert);
4635
                }
4636
            } else { // Such a setting does not exist.
4637
                //error_log(__FILE__.':'.__LINE__.': Attempting to update setting '.$var.' ('.$subvar.') which does not exist at all. The access_url is: '.$access_url.' ',0);
4638
            }
4639
        }
4640
    }
4641
}
4642
4643
/**
4644
 * Sets a whole category of settings to one specific value.
4645
 *
4646
 * @param string    Category
4647
 * @param string    Value
4648
 * @param int       Access URL. Optional. Defaults to 1
4649
 * @param array     Optional array of filters on field type
4650
 * @param string $category
4651
 * @param string $value
4652
 *
4653
 * @return bool
4654
 */
4655
function api_set_settings_category($category, $value = null, $access_url = 1, $fieldtype = [])
4656
{
4657
    if (empty($category)) {
4658
        return false;
4659
    }
4660
    $category = Database::escape_string($category);
4661
    $t_s = Database::get_main_table(TABLE_MAIN_SETTINGS);
4662
    $access_url = (int) $access_url;
4663
    if (empty($access_url)) {
4664
        $access_url = 1;
4665
    }
4666
    if (isset($value)) {
4667
        $value = Database::escape_string($value);
4668
        $sql = "UPDATE $t_s SET selected_value = '$value'
4669
                WHERE category = '$category' AND access_url = $access_url";
4670
        if (is_array($fieldtype) && count($fieldtype) > 0) {
4671
            $sql .= " AND ( ";
4672
            $i = 0;
4673
            foreach ($fieldtype as $type) {
4674
                if ($i > 0) {
4675
                    $sql .= ' OR ';
4676
                }
4677
                $type = Database::escape_string($type);
4678
                $sql .= " type='".$type."' ";
4679
                $i++;
4680
            }
4681
            $sql .= ")";
4682
        }
4683
        $res = Database::query($sql);
4684
4685
        return false !== $res;
4686
    } else {
4687
        $sql = "UPDATE $t_s SET selected_value = NULL
4688
                WHERE category = '$category' AND access_url = $access_url";
4689
        if (is_array($fieldtype) && count($fieldtype) > 0) {
4690
            $sql .= " AND ( ";
4691
            $i = 0;
4692
            foreach ($fieldtype as $type) {
4693
                if ($i > 0) {
4694
                    $sql .= ' OR ';
4695
                }
4696
                $type = Database::escape_string($type);
4697
                $sql .= " type='".$type."' ";
4698
                $i++;
4699
            }
4700
            $sql .= ")";
4701
        }
4702
        $res = Database::query($sql);
4703
4704
        return false !== $res;
4705
    }
4706
}
4707
4708
/**
4709
 * Gets all available access urls in an array (as in the database).
4710
 *
4711
 * @return array An array of database records
4712
 */
4713
function api_get_access_urls($from = 0, $to = 1000000, $order = 'url', $direction = 'ASC')
4714
{
4715
    $table = Database::get_main_table(TABLE_MAIN_ACCESS_URL);
4716
    $from = (int) $from;
4717
    $to = (int) $to;
4718
    $order = Database::escape_string($order);
4719
    $direction = Database::escape_string($direction);
4720
    $direction = !in_array(strtolower(trim($direction)), ['asc', 'desc']) ? 'asc' : $direction;
4721
    $sql = "SELECT id, url, description, active, created_by, tms
4722
            FROM $table
4723
            ORDER BY `$order` $direction
4724
            LIMIT $to OFFSET $from";
4725
    $res = Database::query($sql);
4726
4727
    return Database::store_result($res);
4728
}
4729
4730
/**
4731
 * Gets the access url info in an array.
4732
 *
4733
 * @param int  $id            Id of the access url
4734
 * @param bool $returnDefault Set to false if you want the real URL if URL 1 is still 'http://localhost/'
4735
 *
4736
 * @return array All the info (url, description, active, created_by, tms)
4737
 *               from the access_url table
4738
 *
4739
 * @author Julio Montoya
4740
 */
4741
function api_get_access_url($id, $returnDefault = true)
4742
{
4743
    static $staticResult;
4744
    $id = (int) $id;
4745
4746
    if (isset($staticResult[$id])) {
4747
        $result = $staticResult[$id];
4748
    } else {
4749
        // Calling the Database:: library dont work this is handmade.
4750
        $table_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL);
4751
        $sql = "SELECT url, description, active, created_by, tms
4752
                FROM $table_access_url WHERE id = '$id' ";
4753
        $res = Database::query($sql);
4754
        $result = @Database::fetch_array($res);
4755
        $staticResult[$id] = $result;
4756
    }
4757
4758
    // If the result url is 'http://localhost/' (the default) and the root_web
4759
    // (=current url) is different, and the $id is = 1 (which might mean
4760
    // api_get_current_access_url_id() returned 1 by default), then return the
4761
    // root_web setting instead of the current URL
4762
    // This is provided as an option to avoid breaking the storage of URL-specific
4763
    // homepages in home/localhost/
4764
    if (1 === $id && false === $returnDefault) {
4765
        $currentUrl = api_get_current_access_url_id();
4766
        // only do this if we are on the main URL (=1), otherwise we could get
4767
        // information on another URL instead of the one asked as parameter
4768
        if (1 === $currentUrl) {
4769
            $rootWeb = api_get_path(WEB_PATH);
4770
            $default = AccessUrl::DEFAULT_ACCESS_URL;
4771
            if ($result['url'] === $default && $rootWeb != $default) {
4772
                $result['url'] = $rootWeb;
4773
            }
4774
        }
4775
    }
4776
4777
    return $result;
4778
}
4779
4780
/**
4781
 * Gets all the current settings for a specific access url.
4782
 *
4783
 * @param string    The category, if any, that we want to get
4784
 * @param string    Whether we want a simple list (display a category) or
4785
 * a grouped list (group by variable as in settings.php default). Values: 'list' or 'group'
4786
 * @param int       Access URL's ID. Optional. Uses 1 by default, which is the unique URL
4787
 *
4788
 * @return array Array of database results for the current settings of the current access URL
4789
 */
4790
function &api_get_settings($cat = null, $ordering = 'list', $access_url = 1, $url_changeable = 0)
4791
{
4792
    $table = Database::get_main_table(TABLE_MAIN_SETTINGS);
4793
    $access_url = (int) $access_url;
4794
    $where_condition = '';
4795
    if (1 == $url_changeable) {
4796
        $where_condition = " AND access_url_changeable= '1' ";
4797
    }
4798
    if (empty($access_url) || -1 == $access_url) {
4799
        $access_url = 1;
4800
    }
4801
    $sql = "SELECT * FROM $table
4802
            WHERE access_url = $access_url  $where_condition ";
4803
4804
    if (!empty($cat)) {
4805
        $cat = Database::escape_string($cat);
4806
        $sql .= " AND category='$cat' ";
4807
    }
4808
    if ('group' == $ordering) {
4809
        $sql .= " ORDER BY id ASC";
4810
    } else {
4811
        $sql .= " ORDER BY 1,2 ASC";
4812
    }
4813
    $result = Database::query($sql);
4814
    if (null === $result) {
4815
        $result = [];
4816
        return $result;
4817
    }
4818
    $result = Database::store_result($result, 'ASSOC');
4819
4820
    return $result;
4821
}
4822
4823
/**
4824
 * @param string $value       The value we want to record
4825
 * @param string $variable    The variable name we want to insert
4826
 * @param string $subKey      The subkey for the variable we want to insert
4827
 * @param string $type        The type for the variable we want to insert
4828
 * @param string $category    The category for the variable we want to insert
4829
 * @param string $title       The title
4830
 * @param string $comment     The comment
4831
 * @param string $scope       The scope
4832
 * @param string $subKeyText  The subkey text
4833
 * @param int    $accessUrlId The access_url for which this parameter is valid
4834
 * @param int    $visibility  The changeability of this setting for non-master urls
4835
 *
4836
 * @return int The setting ID
4837
 */
4838
function api_add_setting(
4839
    $value,
4840
    $variable,
4841
    $subKey = '',
4842
    $type = 'textfield',
4843
    $category = '',
4844
    $title = '',
4845
    $comment = '',
4846
    $scope = '',
4847
    $subKeyText = '',
4848
    $accessUrlId = 1,
4849
    $visibility = 0
4850
) {
4851
    $em = Database::getManager();
4852
4853
    $settingRepo = $em->getRepository(SettingsCurrent::class);
4854
    $accessUrlId = (int) $accessUrlId ?: 1;
4855
4856
    if (is_array($value)) {
4857
        $value = serialize($value);
4858
    } else {
4859
        $value = trim($value);
4860
    }
4861
4862
    $criteria = ['variable' => $variable, 'url' => $accessUrlId];
4863
4864
    if (!empty($subKey)) {
4865
        $criteria['subkey'] = $subKey;
4866
    }
4867
4868
    // Check if this variable doesn't exist already
4869
    /** @var SettingsCurrent $setting */
4870
    $setting = $settingRepo->findOneBy($criteria);
4871
4872
    if ($setting) {
0 ignored issues
show
introduced by
$setting is of type Chamilo\CoreBundle\Entity\SettingsCurrent, thus it always evaluated to true.
Loading history...
4873
        $setting->setSelectedValue($value);
4874
4875
        $em->persist($setting);
4876
        $em->flush();
4877
4878
        return $setting->getId();
4879
    }
4880
4881
    // Item not found for this access_url, we have to check if the whole thing is missing
4882
    // (in which case we ignore the insert) or if there *is* a record but just for access_url = 1
4883
    $setting = new SettingsCurrent();
4884
    $url = api_get_url_entity();
4885
4886
    $setting
4887
        ->setVariable($variable)
4888
        ->setSelectedValue($value)
4889
        ->setType($type)
4890
        ->setCategory($category)
4891
        ->setSubkey($subKey)
4892
        ->setTitle($title)
4893
        ->setComment($comment)
4894
        ->setScope($scope)
4895
        ->setSubkeytext($subKeyText)
4896
        ->setUrl(api_get_url_entity())
4897
        ->setAccessUrlChangeable($visibility);
4898
4899
    $em->persist($setting);
4900
    $em->flush();
4901
4902
    return $setting->getId();
4903
}
4904
4905
/**
4906
 * Checks wether a user can or can't view the contents of a course.
4907
 *
4908
 * @deprecated use CourseManager::is_user_subscribed_in_course
4909
 *
4910
 * @param int $userid User id or NULL to get it from $_SESSION
4911
 * @param int $cid    course id to check whether the user is allowed
4912
 *
4913
 * @return bool
4914
 */
4915
function api_is_course_visible_for_user($userid = null, $cid = null)
4916
{
4917
    if (null === $userid) {
4918
        $userid = api_get_user_id();
4919
    }
4920
    if (empty($userid) || strval(intval($userid)) != $userid) {
4921
        if (api_is_anonymous()) {
4922
            $userid = api_get_anonymous_id();
4923
        } else {
4924
            return false;
4925
        }
4926
    }
4927
    $cid = Database::escape_string($cid);
4928
4929
    $courseInfo = api_get_course_info($cid);
4930
    $courseId = $courseInfo['real_id'];
4931
    $is_platformAdmin = api_is_platform_admin();
4932
4933
    $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
4934
    $course_cat_table = Database::get_main_table(TABLE_MAIN_CATEGORY);
4935
4936
    $sql = "SELECT
4937
                $course_cat_table.code AS category_code,
4938
                $course_table.visibility,
4939
                $course_table.code,
4940
                $course_cat_table.code
4941
            FROM $course_table
4942
            LEFT JOIN $course_cat_table
4943
                ON $course_table.category_id = $course_cat_table.id
4944
            WHERE
4945
                $course_table.code = '$cid'
4946
            LIMIT 1";
4947
4948
    $result = Database::query($sql);
4949
4950
    if (Database::num_rows($result) > 0) {
4951
        $visibility = Database::fetch_array($result);
4952
        $visibility = $visibility['visibility'];
4953
    } else {
4954
        $visibility = 0;
4955
    }
4956
    // Shortcut permissions in case the visibility is "open to the world".
4957
    if (COURSE_VISIBILITY_OPEN_WORLD === $visibility) {
4958
        return true;
4959
    }
4960
4961
    $tbl_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
4962
4963
    $sql = "SELECT
4964
                is_tutor, status
4965
            FROM $tbl_course_user
4966
            WHERE
4967
                user_id  = '$userid' AND
4968
                relation_type <> '".COURSE_RELATION_TYPE_RRHH."' AND
4969
                c_id = $courseId
4970
            LIMIT 1";
4971
4972
    $result = Database::query($sql);
4973
4974
    if (Database::num_rows($result) > 0) {
4975
        // This user has got a recorded state for this course.
4976
        $cuData = Database::fetch_array($result);
4977
        $is_courseMember = true;
4978
        $is_courseAdmin = (1 == $cuData['status']);
4979
    }
4980
4981
    if (!$is_courseAdmin) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $is_courseAdmin does not seem to be defined for all execution paths leading up to this point.
Loading history...
4982
        // This user has no status related to this course.
4983
        // Is it the session coach or the session admin?
4984
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
4985
        $tbl_session_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
4986
        $tblSessionRelUser = Database::get_main_table(TABLE_MAIN_SESSION_USER);
4987
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4988
4989
        $sql = "SELECT sru_2.user_id AS session_admin_id, sru_1.user_id AS session_coach_id
4990
                FROM $tbl_session AS s
4991
                INNER JOIN $tblSessionRelUser sru_1
4992
                ON (sru_1.session_id = s.id AND sru_1.relation_type = ".SessionEntity::GENERAL_COACH.")
4993
                INNER JOIN $tblSessionRelUser sru_2
4994
                ON (sru_2.session_id = s.id AND sru_2.relation_type = ".SessionEntity::SESSION_ADMIN.")
4995
                INNER JOIN $tbl_session_course src
4996
                ON (src.session_id = s.id AND src.c_id = $courseId)";
4997
4998
        $result = Database::query($sql);
4999
        $row = Database::store_result($result);
5000
        $sessionAdminsId = array_column($row, 'session_admin_id');
5001
        $sessionCoachesId = array_column($row, 'session_coach_id');
5002
5003
        if (in_array($userid, $sessionCoachesId)) {
5004
            $is_courseMember = true;
5005
            $is_courseAdmin = false;
5006
        } elseif (in_array($userid, $sessionAdminsId)) {
5007
            $is_courseMember = false;
5008
            $is_courseAdmin = false;
5009
        } else {
5010
            // Check if the current user is the course coach.
5011
            $sql = "SELECT 1
5012
                    FROM $tbl_session_course
5013
                    WHERE session_rel_course.c_id = '$courseId'
5014
                    AND session_rel_course.id_coach = '$userid'
5015
                    LIMIT 1";
5016
5017
            $result = Database::query($sql);
5018
5019
            //if ($row = Database::fetch_array($result)) {
5020
            if (Database::num_rows($result) > 0) {
5021
                $is_courseMember = true;
5022
                $tbl_user = Database::get_main_table(TABLE_MAIN_USER);
5023
5024
                $sql = "SELECT status FROM $tbl_user
5025
                        WHERE id = $userid
5026
                        LIMIT 1";
5027
5028
                $result = Database::query($sql);
5029
5030
                if (1 == Database::result($result, 0, 0)) {
5031
                    $is_courseAdmin = true;
5032
                } else {
5033
                    $is_courseAdmin = false;
5034
                }
5035
            } else {
5036
                // Check if the user is a student is this session.
5037
                $sql = "SELECT  id
5038
                        FROM $tbl_session_course_user
5039
                        WHERE
5040
                            user_id  = '$userid' AND
5041
                            c_id = '$courseId'
5042
                        LIMIT 1";
5043
5044
                if (Database::num_rows($result) > 0) {
5045
                    // This user haa got a recorded state for this course.
5046
                    while ($row = Database::fetch_array($result)) {
5047
                        $is_courseMember = true;
5048
                        $is_courseAdmin = false;
5049
                    }
5050
                }
5051
            }
5052
        }
5053
    }
5054
5055
    switch ($visibility) {
5056
        case Course::OPEN_WORLD:
5057
            return true;
5058
        case Course::OPEN_PLATFORM:
5059
            return isset($userid);
5060
        case Course::REGISTERED:
5061
        case Course::CLOSED:
5062
            return $is_platformAdmin || $is_courseMember || $is_courseAdmin;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $is_courseMember does not seem to be defined for all execution paths leading up to this point.
Loading history...
5063
        case Course::HIDDEN:
5064
            return $is_platformAdmin;
5065
    }
5066
5067
    return false;
5068
}
5069
5070
/**
5071
 * Returns whether an element (forum, message, survey ...) belongs to a session or not.
5072
 *
5073
 * @param string the tool of the element
5074
 * @param int the element id in database
5075
 * @param int the session_id to compare with element session id
5076
 *
5077
 * @return bool true if the element is in the session, false else
5078
 */
5079
function api_is_element_in_the_session($tool, $element_id, $session_id = null)
5080
{
5081
    if (is_null($session_id)) {
5082
        $session_id = api_get_session_id();
5083
    }
5084
5085
    $element_id = (int) $element_id;
5086
5087
    if (empty($element_id)) {
5088
        return false;
5089
    }
5090
5091
    // Get information to build query depending of the tool.
5092
    switch ($tool) {
5093
        case TOOL_SURVEY:
5094
            $table_tool = Database::get_course_table(TABLE_SURVEY);
5095
            $key_field = 'survey_id';
5096
            break;
5097
        case TOOL_ANNOUNCEMENT:
5098
            $table_tool = Database::get_course_table(TABLE_ANNOUNCEMENT);
5099
            $key_field = 'id';
5100
            break;
5101
        case TOOL_AGENDA:
5102
            $table_tool = Database::get_course_table(TABLE_AGENDA);
5103
            $key_field = 'id';
5104
            break;
5105
        case TOOL_GROUP:
5106
            $table_tool = Database::get_course_table(TABLE_GROUP);
5107
            $key_field = 'id';
5108
            break;
5109
        default:
5110
            return false;
5111
    }
5112
    $course_id = api_get_course_int_id();
5113
5114
    $sql = "SELECT session_id FROM $table_tool
5115
            WHERE c_id = $course_id AND $key_field =  ".$element_id;
5116
    $rs = Database::query($sql);
5117
    if ($element_session_id = Database::result($rs, 0, 0)) {
5118
        if ($element_session_id == intval($session_id)) {
5119
            // The element belongs to the session.
5120
            return true;
5121
        }
5122
    }
5123
5124
    return false;
5125
}
5126
5127
/**
5128
 * Replaces "forbidden" characters in a filename string.
5129
 *
5130
 * @param string $filename
5131
 * @param bool   $treat_spaces_as_hyphens
5132
 *
5133
 * @return string
5134
 */
5135
function api_replace_dangerous_char($filename, $treat_spaces_as_hyphens = true)
5136
{
5137
    // Some non-properly encoded file names can cause the whole file to be
5138
    // skipped when uploaded. Avoid this by detecting the encoding and
5139
    // converting to UTF-8, setting the source as ASCII (a reasonably
5140
    // limited characters set) if nothing could be found (BT#
5141
    $encoding = api_detect_encoding($filename);
5142
    if (empty($encoding)) {
5143
        $encoding = 'ASCII';
5144
        if (!api_is_valid_ascii($filename)) {
5145
            // try iconv and try non standard ASCII a.k.a CP437
5146
            // see BT#15022
5147
            if (function_exists('iconv')) {
5148
                $result = iconv('CP437', 'UTF-8', $filename);
5149
                if (api_is_valid_utf8($result)) {
5150
                    $filename = $result;
5151
                    $encoding = 'UTF-8';
5152
                }
5153
            }
5154
        }
5155
    }
5156
5157
    $filename = api_to_system_encoding($filename, $encoding);
5158
5159
    $url = URLify::filter(
5160
        $filename,
5161
        250,
5162
        '',
5163
        true,
5164
        false,
5165
        false
5166
    );
5167
5168
    // Replace multiple dots at the end.
5169
    $regex = "/\.+$/";
5170
5171
    return preg_replace($regex, '', $url);
5172
}
5173
5174
/**
5175
 * Fixes the $_SERVER['REQUEST_URI'] that is empty in IIS6.
5176
 *
5177
 * @author Ivan Tcholakov, 28-JUN-2006.
5178
 */
5179
function api_request_uri()
5180
{
5181
    if (!empty($_SERVER['REQUEST_URI'])) {
5182
        return $_SERVER['REQUEST_URI'];
5183
    }
5184
    $uri = $_SERVER['SCRIPT_NAME'];
5185
    if (!empty($_SERVER['QUERY_STRING'])) {
5186
        $uri .= '?'.$_SERVER['QUERY_STRING'];
5187
    }
5188
    $_SERVER['REQUEST_URI'] = $uri;
5189
5190
    return $uri;
5191
}
5192
5193
/**
5194
 * Gets the current access_url id of the Chamilo Platform.
5195
 *
5196
 * @return int access_url_id of the current Chamilo Installation
5197
 *
5198
 * @author Julio Montoya <[email protected]>
5199
 * @throws Exception
5200
 */
5201
function api_get_current_access_url_id(): int
5202
{
5203
    if (false === api_get_multiple_access_url()) {
5204
        return 1;
5205
    }
5206
5207
    static $id;
5208
    if (!empty($id)) {
5209
        return $id;
5210
    }
5211
5212
    $table = Database::get_main_table(TABLE_MAIN_ACCESS_URL);
5213
    $path = Database::escape_string(api_get_path(WEB_PATH));
5214
    $sql = "SELECT id FROM $table WHERE url = '".$path."'";
5215
    $result = Database::query($sql);
5216
    if (Database::num_rows($result) > 0) {
5217
        $id = Database::result($result, 0, 0);
5218
        if (false === $id) {
5219
            return -1;
5220
        }
5221
5222
        return (int) $id;
5223
    }
5224
5225
    $id = 1;
5226
5227
    //if the url in WEB_PATH was not found, it can only mean that there is
5228
    // either a configuration problem or the first URL has not been defined yet
5229
    // (by default it is http://localhost/). Thus the more sensible thing we can
5230
    // do is return 1 (the main URL) as the user cannot hack this value anyway
5231
    return 1;
5232
}
5233
5234
/**
5235
 * Gets the registered urls from a given user id.
5236
 *
5237
 * @author Julio Montoya <[email protected]>
5238
 *
5239
 * @param int $user_id
5240
 *
5241
 * @return array
5242
 */
5243
function api_get_access_url_from_user($user_id)
5244
{
5245
    $user_id = (int) $user_id;
5246
    $table_url_rel_user = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
5247
    $table_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL);
5248
    $sql = "SELECT access_url_id
5249
            FROM $table_url_rel_user url_rel_user
5250
            INNER JOIN $table_url u
5251
            ON (url_rel_user.access_url_id = u.id)
5252
            WHERE user_id = ".$user_id;
5253
    $result = Database::query($sql);
5254
    $list = [];
5255
    while ($row = Database::fetch_assoc($result)) {
5256
        $list[] = $row['access_url_id'];
5257
    }
5258
5259
    return $list;
5260
}
5261
5262
/**
5263
 * Checks whether the curent user is in a group or not.
5264
 *
5265
 * @param string        The group id - optional (takes it from session if not given)
5266
 * @param string        The course code - optional (no additional check by course if course code is not given)
5267
 *
5268
 * @return bool
5269
 *
5270
 * @author Ivan Tcholakov
5271
 */
5272
function api_is_in_group($groupIdParam = null, $courseCodeParam = null)
5273
{
5274
    if (!empty($courseCodeParam)) {
5275
        $courseCode = api_get_course_id();
5276
        if (!empty($courseCode)) {
5277
            if ($courseCodeParam != $courseCode) {
5278
                return false;
5279
            }
5280
        } else {
5281
            return false;
5282
        }
5283
    }
5284
5285
    $groupId = api_get_group_id();
5286
5287
    if (isset($groupId) && '' != $groupId) {
5288
        if (!empty($groupIdParam)) {
5289
            return $groupIdParam == $groupId;
5290
        } else {
5291
            return true;
5292
        }
5293
    }
5294
5295
    return false;
5296
}
5297
5298
/**
5299
 * Checks whether a secret key is valid.
5300
 *
5301
 * @param string $original_key_secret - secret key from (webservice) client
5302
 * @param string $security_key        - security key from Chamilo
5303
 *
5304
 * @return bool - true if secret key is valid, false otherwise
5305
 */
5306
function api_is_valid_secret_key($original_key_secret, $security_key)
5307
{
5308
    if (empty($original_key_secret) || empty($security_key)) {
5309
        return false;
5310
    }
5311
5312
    return (string) $original_key_secret === hash('sha512', $security_key);
5313
}
5314
5315
/**
5316
 * Checks whether the server's operating system is Windows (TM).
5317
 *
5318
 * @return bool - true if the operating system is Windows, false otherwise
5319
 */
5320
function api_is_windows_os()
5321
{
5322
    if (function_exists('php_uname')) {
5323
        // php_uname() exists as of PHP 4.0.2, according to the documentation.
5324
        // We expect that this function will always work for Chamilo 1.8.x.
5325
        $os = php_uname();
5326
    }
5327
    // The following methods are not needed, but let them stay, just in case.
5328
    elseif (isset($_ENV['OS'])) {
5329
        // Sometimes $_ENV['OS'] may not be present (bugs?)
5330
        $os = $_ENV['OS'];
5331
    } elseif (defined('PHP_OS')) {
5332
        // PHP_OS means on which OS PHP was compiled, this is why
5333
        // using PHP_OS is the last choice for detection.
5334
        $os = PHP_OS;
5335
    } else {
5336
        return false;
5337
    }
5338
5339
    return 'win' == strtolower(substr((string) $os, 0, 3));
5340
}
5341
5342
/**
5343
 * This function informs whether the sent request is XMLHttpRequest.
5344
 */
5345
function api_is_xml_http_request()
5346
{
5347
    return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && 'xmlhttprequest' == strtolower($_SERVER['HTTP_X_REQUESTED_WITH']);
5348
}
5349
5350
/**
5351
 * Returns a list of Chamilo's tools or
5352
 * checks whether a given identificator is a valid Chamilo's tool.
5353
 *
5354
 * @author Isaac flores paz
5355
 *
5356
 * @param string The tool name to filter
5357
 *
5358
 * @return mixed Filtered string or array
5359
 */
5360
function api_get_tools_lists($my_tool = null)
5361
{
5362
    $tools_list = [
5363
        TOOL_DOCUMENT,
5364
        TOOL_THUMBNAIL,
5365
        TOOL_HOTPOTATOES,
5366
        TOOL_CALENDAR_EVENT,
5367
        TOOL_LINK,
5368
        TOOL_COURSE_DESCRIPTION,
5369
        TOOL_SEARCH,
5370
        TOOL_LEARNPATH,
5371
        TOOL_ANNOUNCEMENT,
5372
        TOOL_FORUM,
5373
        TOOL_THREAD,
5374
        TOOL_POST,
5375
        TOOL_DROPBOX,
5376
        TOOL_QUIZ,
5377
        TOOL_USER,
5378
        TOOL_GROUP,
5379
        TOOL_BLOGS,
5380
        TOOL_CHAT,
5381
        TOOL_STUDENTPUBLICATION,
5382
        TOOL_TRACKING,
5383
        TOOL_HOMEPAGE_LINK,
5384
        TOOL_COURSE_SETTING,
5385
        TOOL_BACKUP,
5386
        TOOL_COPY_COURSE_CONTENT,
5387
        TOOL_RECYCLE_COURSE,
5388
        TOOL_COURSE_HOMEPAGE,
5389
        TOOL_COURSE_RIGHTS_OVERVIEW,
5390
        TOOL_UPLOAD,
5391
        TOOL_COURSE_MAINTENANCE,
5392
        TOOL_SURVEY,
5393
        //TOOL_WIKI,
5394
        TOOL_GLOSSARY,
5395
        TOOL_GRADEBOOK,
5396
        TOOL_NOTEBOOK,
5397
        TOOL_ATTENDANCE,
5398
        TOOL_COURSE_PROGRESS,
5399
    ];
5400
    if (empty($my_tool)) {
5401
        return $tools_list;
5402
    }
5403
5404
    return in_array($my_tool, $tools_list) ? $my_tool : '';
5405
}
5406
5407
/**
5408
 * Checks whether we already approved the last version term and condition.
5409
 *
5410
 * @param int user id
5411
 *
5412
 * @return bool true if we pass false otherwise
5413
 */
5414
function api_check_term_condition($userId)
5415
{
5416
    if ('true' === api_get_setting('allow_terms_conditions')) {
5417
        // Check if exists terms and conditions
5418
        if (0 == LegalManager::count()) {
5419
            return true;
5420
        }
5421
5422
        $extraFieldValue = new ExtraFieldValue('user');
5423
        $data = $extraFieldValue->get_values_by_handler_and_field_variable(
5424
            $userId,
5425
            'legal_accept'
5426
        );
5427
5428
        if (!empty($data) && isset($data['value']) && !empty($data['value'])) {
5429
            $result = $data['value'];
5430
            $user_conditions = explode(':', $result);
5431
            $version = $user_conditions[0];
5432
            $langId = $user_conditions[1];
5433
            $realVersion = LegalManager::get_last_version($langId);
5434
5435
            return $version >= $realVersion;
5436
        }
5437
5438
        return false;
5439
    }
5440
5441
    return false;
5442
}
5443
5444
/**
5445
 * Gets all information of a tool into course.
5446
 *
5447
 * @param int The tool id
5448
 *
5449
 * @return array
5450
 */
5451
function api_get_tool_information_by_name($name)
5452
{
5453
    $t_tool = Database::get_course_table(TABLE_TOOL_LIST);
5454
    $course_id = api_get_course_int_id();
5455
5456
    $sql = "SELECT id FROM tool
5457
            WHERE title = '".Database::escape_string($name)."' ";
5458
    $rs = Database::query($sql);
5459
    $data = Database::fetch_array($rs);
5460
    if ($data) {
5461
        $tool = $data['id'];
5462
        $sql = "SELECT * FROM $t_tool
5463
                WHERE c_id = $course_id  AND tool_id = '".$tool."' ";
5464
        $rs = Database::query($sql);
5465
5466
        return Database::fetch_assoc($rs);
0 ignored issues
show
Bug Best Practice introduced by
The expression return Database::fetch_assoc($rs) also could return the type boolean which is incompatible with the documented return type array.
Loading history...
5467
    }
5468
5469
    return [];
5470
}
5471
5472
/**
5473
 * Function used to protect a "global" admin script.
5474
 * The function blocks access when the user has no global platform admin rights.
5475
 * Global admins are the admins that are registered in the main.admin table
5476
 * AND the users who have access to the "principal" portal.
5477
 * That means that there is a record in the main.access_url_rel_user table
5478
 * with his user id and the access_url_id=1.
5479
 *
5480
 * @author Julio Montoya
5481
 *
5482
 * @param int $user_id
5483
 *
5484
 * @return bool
5485
 */
5486
function api_is_global_platform_admin($user_id = null)
5487
{
5488
    $user_id = (int) $user_id;
5489
    if (empty($user_id)) {
5490
        $user_id = api_get_user_id();
5491
    }
5492
    if (api_is_platform_admin_by_id($user_id)) {
5493
        $urlList = api_get_access_url_from_user($user_id);
5494
        // The admin is registered in the first "main" site with access_url_id = 1
5495
        if (in_array(1, $urlList)) {
5496
            return true;
5497
        }
5498
    }
5499
5500
    return false;
5501
}
5502
5503
/**
5504
 * @param int  $admin_id_to_check
5505
 * @param int  $userId
5506
 * @param bool $allow_session_admin
5507
 *
5508
 * @return bool
5509
 */
5510
function api_global_admin_can_edit_admin(
5511
    $admin_id_to_check,
5512
    $userId = 0,
5513
    $allow_session_admin = false
5514
) {
5515
    if (empty($userId)) {
5516
        $userId = api_get_user_id();
5517
    }
5518
5519
    $iam_a_global_admin = api_is_global_platform_admin($userId);
5520
    $user_is_global_admin = api_is_global_platform_admin($admin_id_to_check);
5521
5522
    if ($iam_a_global_admin) {
5523
        // Global admin can edit everything
5524
        return true;
5525
    }
5526
5527
    // If i'm a simple admin
5528
    $is_platform_admin = api_is_platform_admin_by_id($userId);
5529
5530
    if ($allow_session_admin && !$is_platform_admin) {
5531
        $user = api_get_user_entity($userId);
5532
        $is_platform_admin = $user->isSessionAdmin();
5533
    }
5534
5535
    if ($is_platform_admin) {
5536
        if ($user_is_global_admin) {
5537
            return false;
5538
        } else {
5539
            return true;
5540
        }
5541
    }
5542
5543
    return false;
5544
}
5545
5546
/**
5547
 * @param int  $admin_id_to_check
5548
 * @param int  $userId
5549
 * @param bool $allow_session_admin
5550
 *
5551
 * @return bool|null
5552
 */
5553
function api_protect_super_admin($admin_id_to_check, $userId = null, $allow_session_admin = false)
5554
{
5555
    if (api_global_admin_can_edit_admin($admin_id_to_check, $userId, $allow_session_admin)) {
5556
        return true;
5557
    } else {
5558
        api_not_allowed();
5559
    }
5560
}
5561
5562
/**
5563
 * Function used to protect a global admin script.
5564
 * The function blocks access when the user has no global platform admin rights.
5565
 * See also the api_is_global_platform_admin() function wich defines who's a "global" admin.
5566
 *
5567
 * @author Julio Montoya
5568
 */
5569
function api_protect_global_admin_script()
5570
{
5571
    if (!api_is_global_platform_admin()) {
5572
        api_not_allowed();
5573
5574
        return false;
5575
    }
5576
5577
    return true;
5578
}
5579
5580
/**
5581
 * This function checks if exist path and file browscap.ini
5582
 * In order for this to work, your browscap configuration setting in php.ini
5583
 * must point to the correct location of the browscap.ini file on your system
5584
 * http://php.net/manual/en/function.get-browser.php.
5585
 *
5586
 * @return bool
5587
 *
5588
 * @author Juan Carlos Raña Trabado
5589
 */
5590
function api_check_browscap()
5591
{
5592
    $setting = ini_get('browscap');
5593
    if ($setting) {
5594
        $browser = get_browser($_SERVER['HTTP_USER_AGENT'], true);
5595
        if (strpos($setting, 'browscap.ini') && !empty($browser)) {
5596
            return true;
5597
        }
5598
    }
5599
5600
    return false;
5601
}
5602
5603
/**
5604
 * Returns the <script> HTML tag.
5605
 */
5606
function api_get_js($file)
5607
{
5608
    return '<script src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/'.$file.'"></script>'."\n";
5609
}
5610
5611
function api_get_build_js($file)
5612
{
5613
    return '<script src="'.api_get_path(WEB_PUBLIC_PATH).'build/'.$file.'"></script>'."\n";
5614
}
5615
5616
/**
5617
 * Returns the <script> HTML tag.
5618
 *
5619
 * @return string
5620
 */
5621
function api_get_asset($file)
5622
{
5623
    return '<script src="'.api_get_path(WEB_PUBLIC_PATH).'build/libs/'.$file.'"></script>'."\n";
5624
}
5625
5626
/**
5627
 * Returns the <script> HTML tag.
5628
 *
5629
 * @param string $file
5630
 * @param string $media
5631
 *
5632
 * @return string
5633
 */
5634
function api_get_css_asset($file, $media = 'screen')
5635
{
5636
    return '<link
5637
        href="'.api_get_path(WEB_PUBLIC_PATH).'build/libs/'.$file.'"
5638
        rel="stylesheet" media="'.$media.'" type="text/css" />'."\n";
5639
}
5640
5641
/**
5642
 * Returns the <link> HTML tag.
5643
 *
5644
 * @param string $file
5645
 * @param string $media
5646
 */
5647
function api_get_css($file, $media = 'screen')
5648
{
5649
    return '<link href="'.$file.'" rel="stylesheet" media="'.$media.'" type="text/css" />'."\n";
5650
}
5651
5652
function api_get_bootstrap_and_font_awesome($returnOnlyPath = false, $returnFileLocation = false)
5653
{
5654
    $url = api_get_path(WEB_PUBLIC_PATH).'build/css/bootstrap.css';
5655
5656
    if ($returnOnlyPath) {
5657
        if ($returnFileLocation) {
5658
            return api_get_path(SYS_PUBLIC_PATH).'build/css/bootstrap.css';
5659
        }
5660
5661
        return $url;
5662
    }
5663
5664
    return '<link href="'.$url.'" rel="stylesheet" type="text/css" />'."\n";
5665
}
5666
5667
/**
5668
 * Returns the jquery path.
5669
 *
5670
 * @return string
5671
 */
5672
function api_get_jquery_web_path()
5673
{
5674
    return api_get_path(WEB_PUBLIC_PATH).'assets/jquery/jquery.min.js';
5675
}
5676
5677
/**
5678
 * @return string
5679
 */
5680
function api_get_jquery_ui_js_web_path()
5681
{
5682
    return api_get_path(WEB_PUBLIC_PATH).'assets/jquery-ui/jquery-ui.min.js';
5683
}
5684
5685
/**
5686
 * @return string
5687
 */
5688
function api_get_jquery_ui_css_web_path()
5689
{
5690
    return api_get_path(WEB_PUBLIC_PATH).'assets/jquery-ui/themes/smoothness/jquery-ui.min.css';
5691
}
5692
5693
function api_get_jqgrid_js()
5694
{
5695
    return '<link
5696
        href="'.api_get_path(WEB_PUBLIC_PATH).'build/legacy_free-jqgrid.css" rel="stylesheet" media="screen" type="text/css" />'
5697
        .PHP_EOL
5698
        .api_get_build_js('legacy_free-jqgrid.js');
5699
}
5700
5701
/**
5702
 * Returns the jquery library js and css headers.
5703
 *
5704
 * @param   array   list of jquery libraries supported jquery-ui
5705
 * @param   bool    add the jquery library
5706
 *
5707
 * @return string html tags
5708
 */
5709
function api_get_jquery_libraries_js($libraries)
5710
{
5711
    $js = '';
5712
5713
    //Document multiple upload funcionality
5714
    if (in_array('jquery-upload', $libraries)) {
5715
        $js .= api_get_asset('blueimp-load-image/js/load-image.all.min.js');
5716
        $js .= api_get_asset('blueimp-canvas-to-blob/js/canvas-to-blob.min.js');
5717
        $js .= api_get_asset('jquery-file-upload/js/jquery.iframe-transport.js');
5718
        $js .= api_get_asset('jquery-file-upload/js/jquery.fileupload.js');
5719
        $js .= api_get_asset('jquery-file-upload/js/jquery.fileupload-process.js');
5720
        $js .= api_get_asset('jquery-file-upload/js/jquery.fileupload-image.js');
5721
        $js .= api_get_asset('jquery-file-upload/js/jquery.fileupload-audio.js');
5722
        $js .= api_get_asset('jquery-file-upload/js/jquery.fileupload-video.js');
5723
        $js .= api_get_asset('jquery-file-upload/js/jquery.fileupload-validate.js');
5724
5725
        $js .= api_get_css(api_get_path(WEB_PUBLIC_PATH).'assets/jquery-file-upload/css/jquery.fileupload.css');
5726
        $js .= api_get_css(api_get_path(WEB_PUBLIC_PATH).'assets/jquery-file-upload/css/jquery.fileupload-ui.css');
5727
    }
5728
5729
    // jquery datepicker
5730
    if (in_array('datepicker', $libraries)) {
5731
        $languaje = 'en-GB';
5732
        $platform_isocode = strtolower(api_get_language_isocode());
5733
5734
        $datapicker_langs = [
5735
            'af', 'ar', 'ar-DZ', 'az', 'bg', 'bs', 'ca', 'cs', 'cy-GB', 'da', 'de', 'el', 'en-AU', 'en-GB', 'en-NZ', 'eo', 'es', 'et', 'eu', 'fa', 'fi', 'fo', 'fr', 'fr-CH', 'gl', 'he', 'hi', 'hr', 'hu', 'hy', 'id', 'is', 'it', 'ja', 'ka', 'kk', 'km', 'ko', 'lb', 'lt', 'lv', 'mk', 'ml', 'ms', 'nl', 'nl-BE', 'no', 'pl', 'pt', 'pt-BR', 'rm', 'ro', 'ru', 'sk', 'sl', 'sq', 'sr', 'sr-SR', 'sv', 'ta', 'th', 'tj', 'tr', 'uk', 'vi', 'zh-CN', 'zh-HK', 'zh-TW',
5736
        ];
5737
        if (in_array($platform_isocode, $datapicker_langs)) {
5738
            $languaje = $platform_isocode;
5739
        }
5740
5741
        $js .= api_get_js('jquery-ui/jquery-ui-i18n.min.js');
5742
        $script = '<script>
5743
        $(function(){
5744
            $.datepicker.setDefaults($.datepicker.regional["'.$languaje.'"]);
5745
            $.datepicker.regional["local"] = $.datepicker.regional["'.$languaje.'"];
5746
        });
5747
        </script>
5748
        ';
5749
        $js .= $script;
5750
    }
5751
5752
    return $js;
5753
}
5754
5755
/**
5756
 * Returns the URL to the course or session, removing the complexity of the URL
5757
 * building piece by piece.
5758
 *
5759
 * This function relies on api_get_course_info()
5760
 *
5761
 * @param int $courseId  The course code - optional (takes it from context if not given)
5762
 * @param int $sessionId The session ID  - optional (takes it from context if not given)
5763
 * @param int $groupId   The group ID - optional (takes it from context if not given)
5764
 *
5765
 * @return string The URL to a course, a session, or empty string if nothing works
5766
 *                e.g. https://localhost/courses/ABC/index.php?session_id=3&gidReq=1
5767
 *
5768
 * @author  Julio Montoya
5769
 */
5770
function api_get_course_url($courseId = null, $sessionId = null, $groupId = null)
5771
{
5772
    $url = '';
5773
    // If courseCode not set, get context or []
5774
    if (empty($courseId)) {
5775
        $courseId = api_get_course_int_id();
5776
    }
5777
5778
    // If sessionId not set, get context or 0
5779
    if (empty($sessionId)) {
5780
        $sessionId = api_get_session_id();
5781
    }
5782
5783
    // If groupId not set, get context or 0
5784
    if (empty($groupId)) {
5785
        $groupId = api_get_group_id();
5786
    }
5787
5788
    // Build the URL
5789
    if (!empty($courseId)) {
5790
        $webCourseHome = '/course/'.$courseId.'/home';
5791
        // directory not empty, so we do have a course
5792
        $url = $webCourseHome.'?sid='.$sessionId.'&gid='.$groupId;
5793
    } else {
5794
        if (!empty($sessionId) && 'true' !== api_get_setting('session.remove_session_url')) {
5795
            // if the course was unset and the session was set, send directly to the session
5796
            $url = api_get_path(WEB_CODE_PATH).'session/index.php?session_id='.$sessionId;
5797
        }
5798
    }
5799
5800
    // if not valid combination was found, return an empty string
5801
    return $url;
5802
}
5803
5804
/**
5805
 * Check if there is more than the default URL defined in the access_url table.
5806
 */
5807
function api_get_multiple_access_url(): bool
5808
{
5809
    return Container::getAccessUrlUtil()->isMultiple();
5810
}
5811
5812
/**
5813
 * @deprecated Use AccessUrlUtil::isMultiple
5814
 */
5815
function api_is_multiple_url_enabled(): bool
5816
{
5817
    return api_get_multiple_access_url();
5818
}
5819
5820
/**
5821
 * Returns a md5 unique id.
5822
 *
5823
 * @todo add more parameters
5824
 */
5825
function api_get_unique_id()
5826
{
5827
    return md5(time().uniqid().api_get_user_id().api_get_course_id().api_get_session_id());
5828
}
5829
5830
/**
5831
 * @param int Course id
5832
 * @param int tool id: TOOL_QUIZ, TOOL_FORUM, TOOL_STUDENTPUBLICATION, TOOL_LEARNPATH
5833
 * @param int the item id (tool id, exercise id, lp id)
5834
 *
5835
 * @return bool
5836
 */
5837
function api_resource_is_locked_by_gradebook($item_id, $link_type, $course_code = null)
5838
{
5839
    if (api_is_platform_admin()) {
5840
        return false;
5841
    }
5842
    if ('true' === api_get_setting('gradebook_locking_enabled')) {
5843
        if (empty($course_code)) {
5844
            $course_code = api_get_course_id();
5845
        }
5846
        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_LINK);
5847
        $item_id = (int) $item_id;
5848
        $link_type = (int) $link_type;
5849
        $course_code = Database::escape_string($course_code);
5850
        $sql = "SELECT locked FROM $table
5851
                WHERE locked = 1 AND ref_id = $item_id AND type = $link_type AND course_code = '$course_code' ";
5852
        $result = Database::query($sql);
5853
        if (Database::num_rows($result)) {
5854
            return true;
5855
        }
5856
    }
5857
5858
    return false;
5859
}
5860
5861
/**
5862
 * Blocks a page if the item was added in a gradebook.
5863
 *
5864
 * @param int       exercise id, work id, thread id,
5865
 * @param int       LINK_EXERCISE, LINK_STUDENTPUBLICATION, LINK_LEARNPATH LINK_FORUM_THREAD, LINK_ATTENDANCE
5866
 * see gradebook/lib/be/linkfactory
5867
 * @param string    course code
5868
 *
5869
 * @return false|null
5870
 */
5871
function api_block_course_item_locked_by_gradebook($item_id, $link_type, $course_code = null)
5872
{
5873
    if (api_is_platform_admin()) {
5874
        return false;
5875
    }
5876
5877
    if (api_resource_is_locked_by_gradebook($item_id, $link_type, $course_code)) {
5878
        $message = Display::return_message(
5879
            get_lang(
5880
                'This option is not available because this activity is contained by an assessment, which is currently locked. To unlock the assessment, ask your platform administrator.'
5881
            ),
5882
            'warning'
5883
        );
5884
        api_not_allowed(true, $message);
5885
    }
5886
}
5887
5888
/**
5889
 * Checks the PHP version installed is enough to run Chamilo.
5890
 *
5891
 * @param string Include path (used to load the error page)
5892
 */
5893
function api_check_php_version()
5894
{
5895
    if (!function_exists('version_compare') ||
5896
        version_compare(PHP_VERSION, REQUIRED_PHP_VERSION, '<')
5897
    ) {
5898
        throw new Exception('Wrong PHP version');
5899
    }
5900
}
5901
5902
/**
5903
 * Checks whether the Archive directory is present and writeable. If not,
5904
 * prints a warning message.
5905
 */
5906
function api_check_archive_dir()
5907
{
5908
    if (is_dir(api_get_path(SYS_ARCHIVE_PATH)) && !is_writable(api_get_path(SYS_ARCHIVE_PATH))) {
5909
        $message = Display::return_message(
5910
            get_lang(
5911
                'The var/cache/ directory, used by this tool, is not writeable. Please contact your platform administrator.'
5912
            ),
5913
            'warning'
5914
        );
5915
        api_not_allowed(true, $message);
5916
    }
5917
}
5918
5919
/**
5920
 * Returns an array of global configuration settings which should be ignored
5921
 * when printing the configuration settings screens.
5922
 *
5923
 * @return array Array of strings, each identifying one of the excluded settings
5924
 */
5925
function api_get_locked_settings()
5926
{
5927
    return [
5928
        'permanently_remove_deleted_files',
5929
        'account_valid_duration',
5930
        'service_ppt2lp',
5931
        'wcag_anysurfer_public_pages',
5932
        'upload_extensions_list_type',
5933
        'upload_extensions_blacklist',
5934
        'upload_extensions_whitelist',
5935
        'upload_extensions_skip',
5936
        'upload_extensions_replace_by',
5937
        'split_users_upload_directory',
5938
        'permissions_for_new_directories',
5939
        'permissions_for_new_files',
5940
        'platform_charset',
5941
        'ldap_description',
5942
        'cas_activate',
5943
        'cas_server',
5944
        'cas_server_uri',
5945
        'cas_port',
5946
        'cas_protocol',
5947
        'cas_add_user_activate',
5948
        'update_user_info_cas_with_ldap',
5949
        'languagePriority1',
5950
        'languagePriority2',
5951
        'languagePriority3',
5952
        'languagePriority4',
5953
        'login_is_email',
5954
        'chamilo_database_version',
5955
    ];
5956
}
5957
5958
/**
5959
 * Guess the real ip for register in the database, even in reverse proxy cases.
5960
 * To be recognized, the IP has to be found in either $_SERVER['REMOTE_ADDR'] or
5961
 * in $_SERVER['HTTP_X_FORWARDED_FOR'], which is in common use with rproxies.
5962
 * Note: the result of this function is not SQL-safe. Please escape it before
5963
 * inserting in a database.
5964
 *
5965
 * @return string the user's real ip (unsafe - escape it before inserting to db)
5966
 *
5967
 * @author Jorge Frisancho Jibaja <[email protected]>, USIL - Some changes to allow the use of real IP using reverse proxy
5968
 *
5969
 * @version CEV CHANGE 24APR2012
5970
 * @throws RuntimeException
5971
 */
5972
function api_get_real_ip(): string
5973
{
5974
    if ('cli' === PHP_SAPI) {
5975
        $ip = '127.0.0.1';
5976
    } else {
5977
        $ip = trim($_SERVER['REMOTE_ADDR']);
5978
        if (empty($ip)) {
5979
            throw new RuntimeException("Unable to retrieve remote IP address.");
5980
        }
5981
    }
5982
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
5983
        if (preg_match('/,/', $_SERVER['HTTP_X_FORWARDED_FOR'])) {
5984
            @list($ip1, $ip2) = @explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
5985
        } else {
5986
            $ip1 = $_SERVER['HTTP_X_FORWARDED_FOR'];
5987
        }
5988
        $ip = trim($ip1);
5989
    }
5990
5991
    return $ip;
5992
}
5993
5994
/**
5995
 * Checks whether an IP is included inside an IP range.
5996
 *
5997
 * @param string IP address
5998
 * @param string IP range
5999
 * @param string $ip
6000
 *
6001
 * @return bool True if IP is in the range, false otherwise
6002
 *
6003
 * @author claudiu at cnixs dot com  on http://www.php.net/manual/fr/ref.network.php#55230
6004
 * @author Yannick Warnier for improvements and managment of multiple ranges
6005
 *
6006
 * @todo check for IPv6 support
6007
 */
6008
function api_check_ip_in_range($ip, $range)
6009
{
6010
    if (empty($ip) or empty($range)) {
6011
        return false;
6012
    }
6013
    $ip_ip = ip2long($ip);
6014
    // divide range param into array of elements
6015
    if (false !== strpos($range, ',')) {
6016
        $ranges = explode(',', $range);
6017
    } else {
6018
        $ranges = [$range];
6019
    }
6020
    foreach ($ranges as $range) {
0 ignored issues
show
introduced by
$range is overwriting one of the parameters of this function.
Loading history...
6021
        $range = trim($range);
6022
        if (empty($range)) {
6023
            continue;
6024
        }
6025
        if (false === strpos($range, '/')) {
6026
            if (0 === strcmp($ip, $range)) {
6027
                return true; // there is a direct IP match, return OK
6028
            }
6029
            continue; //otherwise, get to the next range
6030
        }
6031
        // the range contains a "/", so analyse completely
6032
        [$net, $mask] = explode("/", $range);
6033
6034
        $ip_net = ip2long($net);
6035
        // mask binary magic
6036
        $ip_mask = ~((1 << (32 - $mask)) - 1);
6037
6038
        $ip_ip_net = $ip_ip & $ip_mask;
6039
        if ($ip_ip_net == $ip_net) {
6040
            return true;
6041
        }
6042
    }
6043
6044
    return false;
6045
}
6046
6047
function api_check_user_access_to_legal($courseInfo)
6048
{
6049
    if (empty($courseInfo)) {
6050
        return false;
6051
    }
6052
6053
    $visibility = (int) $courseInfo['visibility'];
6054
    $visibilityList = [COURSE_VISIBILITY_OPEN_WORLD, COURSE_VISIBILITY_OPEN_PLATFORM];
6055
6056
    return
6057
        in_array($visibility, $visibilityList) ||
6058
        api_is_drh() ||
6059
        (COURSE_VISIBILITY_REGISTERED === $visibility && 1 === (int) $courseInfo['subscribe']);
6060
}
6061
6062
/**
6063
 * Checks if the global chat is enabled or not.
6064
 *
6065
 * @return bool
6066
 */
6067
function api_is_global_chat_enabled()
6068
{
6069
    return
6070
        !api_is_anonymous() &&
6071
        'true' === api_get_setting('chat.allow_global_chat') &&
6072
        'true' === api_get_setting('social.allow_social_tool');
6073
}
6074
6075
/**
6076
 * @param int   $item_id
6077
 * @param int   $tool_id
6078
 * @param int   $group_id   id
6079
 * @param array $courseInfo
6080
 * @param int   $sessionId
6081
 * @param int   $userId
6082
 *
6083
 * @deprecated
6084
 */
6085
function api_set_default_visibility(
6086
    $item_id,
6087
    $tool_id,
6088
    $group_id = 0,
6089
    $courseInfo = [],
6090
    $sessionId = 0,
6091
    $userId = 0
6092
) {
6093
    $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
6094
    $courseId = $courseInfo['real_id'];
6095
    $courseCode = $courseInfo['code'];
6096
    $sessionId = empty($sessionId) ? api_get_session_id() : $sessionId;
6097
    $userId = empty($userId) ? api_get_user_id() : $userId;
6098
6099
    // if group is null force group_id = 0, this force is needed to create a LP folder with group = 0
6100
    if (is_null($group_id)) {
6101
        $group_id = 0;
6102
    } else {
6103
        $group_id = empty($group_id) ? api_get_group_id() : $group_id;
6104
    }
6105
6106
    $groupInfo = [];
6107
    if (!empty($group_id)) {
6108
        $groupInfo = GroupManager::get_group_properties($group_id);
6109
    }
6110
    $original_tool_id = $tool_id;
6111
6112
    switch ($tool_id) {
6113
        case TOOL_LINK:
6114
        case TOOL_LINK_CATEGORY:
6115
            $tool_id = 'links';
6116
            break;
6117
        case TOOL_DOCUMENT:
6118
            $tool_id = 'documents';
6119
            break;
6120
        case TOOL_LEARNPATH:
6121
            $tool_id = 'learning';
6122
            break;
6123
        case TOOL_ANNOUNCEMENT:
6124
            $tool_id = 'announcements';
6125
            break;
6126
        case TOOL_FORUM:
6127
        case TOOL_FORUM_CATEGORY:
6128
        case TOOL_FORUM_THREAD:
6129
            $tool_id = 'forums';
6130
            break;
6131
        case TOOL_QUIZ:
6132
            $tool_id = 'quiz';
6133
            break;
6134
    }
6135
    $setting = api_get_setting('tool_visible_by_default_at_creation');
6136
6137
    if (isset($setting[$tool_id])) {
6138
        $visibility = 'invisible';
6139
        if ('true' === $setting[$tool_id]) {
6140
            $visibility = 'visible';
6141
        }
6142
6143
        // Read the portal and course default visibility
6144
        if ('documents' === $tool_id) {
6145
            $visibility = DocumentManager::getDocumentDefaultVisibility($courseInfo);
6146
        }
6147
6148
        // Fixes default visibility for tests
6149
        switch ($original_tool_id) {
6150
            case TOOL_QUIZ:
6151
                if (empty($sessionId)) {
6152
                    $objExerciseTmp = new Exercise($courseId);
6153
                    $objExerciseTmp->read($item_id);
6154
                    if ('visible' === $visibility) {
6155
                        $objExerciseTmp->enable();
6156
                        $objExerciseTmp->save();
6157
                    } else {
6158
                        $objExerciseTmp->disable();
6159
                        $objExerciseTmp->save();
6160
                    }
6161
                }
6162
                break;
6163
        }
6164
    }
6165
}
6166
6167
/**
6168
 * Returns available role codes => translated labels.
6169
 * Uses DI PermissionHelper and caches results.
6170
 */
6171
function api_get_roles(): array
6172
{
6173
    static $cache = null;
6174
    if ($cache !== null) {
6175
        return $cache;
6176
    }
6177
6178
    $codes = Container::$container
6179
        ->get(\Chamilo\CoreBundle\Helpers\PermissionHelper::class)
6180
        ->getUserRoles();
6181
6182
    $labels = [
6183
        'ROLE_STUDENT'          => get_lang('Learner'),
6184
        'STUDENT'               => get_lang('Learner'),
6185
        'ROLE_TEACHER'          => get_lang('Teacher'),
6186
        'TEACHER'               => get_lang('Teacher'),
6187
        'ROLE_HR'               => get_lang('Human Resources Manager'),
6188
        'HR'                    => get_lang('Human Resources Manager'),
6189
        'ROLE_SESSION_MANAGER'  => get_lang('Session administrator'),
6190
        'SESSION_MANAGER'       => get_lang('Session administrator'),
6191
        'ROLE_STUDENT_BOSS'     => get_lang('Superior (n+1)'),
6192
        'STUDENT_BOSS'          => get_lang('Superior (n+1)'),
6193
        'ROLE_INVITEE'          => get_lang('Invitee'),
6194
        'INVITEE'               => get_lang('Invitee'),
6195
        'ROLE_QUESTION_MANAGER' => get_lang('Question manager'),
6196
        'QUESTION_MANAGER'      => get_lang('Question manager'),
6197
        'ROLE_ADMIN'            => get_lang('Admin'),
6198
        'ADMIN'                 => get_lang('Admin'),
6199
        'ROLE_PLATFORM_ADMIN'   => get_lang('Administrator'),
6200
        'PLATFORM_ADMIN'        => get_lang('Administrator'),
6201
        'ROLE_GLOBAL_ADMIN'     => get_lang('Global admin'),
6202
        'GLOBAL_ADMIN'          => get_lang('Global admin'),
6203
        'ROLE_ANONYMOUS'        => 'Anonymous',
6204
        'ANONYMOUS'             => 'Anonymous',
6205
        'ROLE_USER'             => 'User',
6206
        'USER'                  => 'User',
6207
    ];
6208
6209
    $out = [];
6210
    foreach ((array) $codes as $code) {
6211
        $canon = strtoupper(trim((string) $code));
6212
        $label =
6213
            ($labels[$canon] ?? null) ??
6214
            ($labels['ROLE_'.$canon] ?? null) ??
6215
            ucwords(strtolower(str_replace('_', ' ', preg_replace('/^ROLE_/', '', $canon))));
6216
        $out[$code] = $label;
6217
    }
6218
6219
    ksort($out, SORT_STRING);
6220
    return $cache = $out;
6221
}
6222
6223
/**
6224
 * Normalizes a role code to canonical "ROLE_*" uppercase form.
6225
 */
6226
function api_normalize_role_code(string $code): string {
6227
    $c = strtoupper(trim($code));
6228
    $map = [
6229
        'STUDENT' => 'ROLE_STUDENT',
6230
        'TEACHER' => 'ROLE_TEACHER',
6231
        'HR' => 'ROLE_HR',
6232
        'SESSION_MANAGER' => 'ROLE_SESSION_MANAGER',
6233
        'STUDENT_BOSS' => 'ROLE_STUDENT_BOSS',
6234
        'INVITEE' => 'ROLE_INVITEE',
6235
        'QUESTION_MANAGER' => 'ROLE_QUESTION_MANAGER',
6236
        'ADMIN' => 'ROLE_ADMIN',
6237
        'GLOBAL_ADMIN' => 'ROLE_GLOBAL_ADMIN',
6238
    ];
6239
    if (!str_starts_with($c, 'ROLE_')) {
6240
        return $map[$c] ?? ('ROLE_'.$c);
6241
    }
6242
    return $map[$c] ?? $c;
6243
}
6244
6245
/**
6246
 * Priority when deriving legacy status from roles (first match wins).
6247
 */
6248
function api_roles_priority(): array {
6249
    return [
6250
        'ROLE_GLOBAL_ADMIN',
6251
        'ROLE_ADMIN',
6252
        'ROLE_SESSION_MANAGER',
6253
        'ROLE_TEACHER',
6254
        'ROLE_HR',
6255
        'ROLE_STUDENT_BOSS',
6256
        'ROLE_INVITEE',
6257
        'ROLE_STUDENT',
6258
    ];
6259
}
6260
6261
function api_has_admin_role(array $roles): bool {
6262
    $n = array_map('api_normalize_role_code', $roles);
6263
    return in_array('ROLE_ADMIN', $n, true) || in_array('ROLE_GLOBAL_ADMIN', $n, true);
6264
}
6265
6266
function api_role_status_map(): array {
6267
    return [
6268
        'ROLE_GLOBAL_ADMIN'   => COURSEMANAGER, // 1
6269
        'ROLE_ADMIN'          => COURSEMANAGER, // 1
6270
        'ROLE_SESSION_MANAGER'=> COURSEMANAGER, // 1
6271
        'ROLE_TEACHER'        => COURSEMANAGER, // 1
6272
        'ROLE_HR'             => DRH,           // 4
6273
        'ROLE_STUDENT_BOSS'   => STUDENT_BOSS,
6274
        'ROLE_INVITEE'        => INVITEE,
6275
        'ROLE_STUDENT'        => STUDENT,       // 5 (fallback)
6276
    ];
6277
}
6278
6279
/**
6280
 * Returns legacy status from a set of roles using priority.
6281
 * Defaults to STUDENT if none matched.
6282
 */
6283
function api_status_from_roles(array $roles): int
6284
{
6285
    $norm = array_map('api_normalize_role_code', $roles);
6286
    $priority = api_roles_priority();
6287
    $map = api_role_status_map();
6288
6289
    foreach ($priority as $p) {
6290
        if (in_array($p, $norm, true)) {
6291
            return $map[$p];
6292
        }
6293
    }
6294
    return STUDENT;
6295
}
6296
6297
function api_get_user_roles(): array
6298
{
6299
    $permissionService = Container::$container->get(PermissionHelper::class);
6300
6301
    $roles = $permissionService->getUserRoles();
6302
6303
    return array_combine($roles, $roles);
6304
}
6305
6306
/**
6307
 * @param string $file
6308
 *
6309
 * @return string
6310
 */
6311
function api_get_js_simple($file)
6312
{
6313
    return '<script type="text/javascript" src="'.$file.'"></script>'."\n";
6314
}
6315
6316
/**
6317
 * Modify default memory_limit and max_execution_time limits
6318
 * Needed when processing long tasks.
6319
 */
6320
function api_set_more_memory_and_time_limits()
6321
{
6322
    if (function_exists('ini_set')) {
6323
        api_set_memory_limit('256M');
6324
        ini_set('max_execution_time', 1800);
6325
    }
6326
}
6327
6328
/**
6329
 * Tries to set memory limit, if authorized and new limit is higher than current.
6330
 *
6331
 * @param string $mem New memory limit
6332
 *
6333
 * @return bool True on success, false on failure or current is higher than suggested
6334
 * @assert (null) === false
6335
 * @assert (-1) === false
6336
 * @assert (0) === true
6337
 * @assert ('1G') === true
6338
 */
6339
function api_set_memory_limit($mem)
6340
{
6341
    //if ini_set() not available, this function is useless
6342
    if (!function_exists('ini_set') || is_null($mem) || -1 == $mem) {
6343
        return false;
6344
    }
6345
6346
    $memory_limit = ini_get('memory_limit');
6347
    if (api_get_bytes_memory_limit($mem) > api_get_bytes_memory_limit($memory_limit)) {
6348
        ini_set('memory_limit', $mem);
6349
6350
        return true;
6351
    }
6352
6353
    return false;
6354
}
6355
6356
/**
6357
 * Gets memory limit in bytes.
6358
 *
6359
 * @param string The memory size (128M, 1G, 1000K, etc)
6360
 *
6361
 * @return int
6362
 * @assert (null) === false
6363
 * @assert ('1t')  === 1099511627776
6364
 * @assert ('1g')  === 1073741824
6365
 * @assert ('1m')  === 1048576
6366
 * @assert ('100k') === 102400
6367
 */
6368
function api_get_bytes_memory_limit($mem)
6369
{
6370
    $size = strtolower(substr($mem, -1));
6371
6372
    switch ($size) {
6373
        case 't':
6374
            $mem = (int) substr($mem, -1) * 1024 * 1024 * 1024 * 1024;
6375
            break;
6376
        case 'g':
6377
            $mem = (int) substr($mem, 0, -1) * 1024 * 1024 * 1024;
6378
            break;
6379
        case 'm':
6380
            $mem = (int) substr($mem, 0, -1) * 1024 * 1024;
6381
            break;
6382
        case 'k':
6383
            $mem = (int) substr($mem, 0, -1) * 1024;
6384
            break;
6385
        default:
6386
            // we assume it's integer only
6387
            $mem = (int) $mem;
6388
            break;
6389
    }
6390
6391
    return $mem;
6392
}
6393
6394
/**
6395
 * Finds all the information about a user from username instead of user id.
6396
 *
6397
 * @param string $officialCode
6398
 *
6399
 * @return array $user_info user_id, lastname, firstname, username, email, ...
6400
 *
6401
 * @author Yannick Warnier <[email protected]>
6402
 */
6403
function api_get_user_info_from_official_code($officialCode)
6404
{
6405
    if (empty($officialCode)) {
6406
        return false;
6407
    }
6408
    $sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_USER)."
6409
            WHERE official_code ='".Database::escape_string($officialCode)."'";
6410
    $result = Database::query($sql);
6411
    if (Database::num_rows($result) > 0) {
6412
        $result_array = Database::fetch_array($result);
6413
6414
        return _api_format_user($result_array);
6415
    }
6416
6417
    return false;
6418
}
6419
6420
/**
6421
 * @param string $usernameInputId
6422
 * @param string $passwordInputId
6423
 *
6424
 * @return string|null
6425
 */
6426
function api_get_password_checker_js($usernameInputId, $passwordInputId)
6427
{
6428
    $checkPass = api_get_setting('allow_strength_pass_checker');
6429
    $useStrengthPassChecker = 'true' === $checkPass;
6430
6431
    if (false === $useStrengthPassChecker) {
6432
        return null;
6433
    }
6434
6435
    $minRequirements = Security::getPasswordRequirements()['min'];
6436
6437
    $options = [
6438
        'rules' => [],
6439
    ];
6440
6441
    if ($minRequirements['length'] > 0) {
6442
        $options['rules'][] = [
6443
            'minChar' => $minRequirements['length'],
6444
            'pattern' => '.',
6445
            'helpText' => sprintf(
6446
                get_lang('Minimum %s characters in total'),
6447
                $minRequirements['length']
6448
            ),
6449
        ];
6450
    }
6451
6452
    if ($minRequirements['lowercase'] > 0) {
6453
        $options['rules'][] = [
6454
            'minChar' => $minRequirements['lowercase'],
6455
            'pattern' => '[a-z]',
6456
            'helpText' => sprintf(
6457
                get_lang('Minimum %s lowercase characters'),
6458
                $minRequirements['lowercase']
6459
            ),
6460
        ];
6461
    }
6462
6463
    if ($minRequirements['uppercase'] > 0) {
6464
        $options['rules'][] = [
6465
            'minChar' => $minRequirements['uppercase'],
6466
            'pattern' => '[A-Z]',
6467
            'helpText' => sprintf(
6468
                get_lang('Minimum %s uppercase characters'),
6469
                $minRequirements['uppercase']
6470
            ),
6471
        ];
6472
    }
6473
6474
    if ($minRequirements['numeric'] > 0) {
6475
        $options['rules'][] = [
6476
            'minChar' => $minRequirements['numeric'],
6477
            'pattern' => '[0-9]',
6478
            'helpText' => sprintf(
6479
                get_lang('Minimum %s numerical (0-9) characters'),
6480
                $minRequirements['numeric']
6481
            ),
6482
        ];
6483
    }
6484
6485
    if ($minRequirements['specials'] > 0) {
6486
        $options['rules'][] = [
6487
            'minChar' => $minRequirements['specials'],
6488
            'pattern' => '[!"#$%&\'()*+,\-./\\\:;<=>?@[\\]^_`{|}~]',
6489
            'helpText' => sprintf(
6490
                get_lang('Minimum %s special characters'),
6491
                $minRequirements['specials']
6492
            ),
6493
        ];
6494
    }
6495
6496
    $js = api_get_js('password-checker/password-checker.js');
6497
    $js .= "<script>
6498
    $(function() {
6499
        $('".$passwordInputId."').passwordChecker(".json_encode($options).");
6500
    });
6501
    </script>";
6502
6503
    return $js;
6504
}
6505
6506
/**
6507
 * create an user extra field called 'captcha_blocked_until_date'.
6508
 *
6509
 * @param string $username
6510
 *
6511
 * @return bool
6512
 */
6513
function api_block_account_captcha($username)
6514
{
6515
    $userInfo = api_get_user_info_from_username($username);
6516
    if (empty($userInfo)) {
6517
        return false;
6518
    }
6519
    $minutesToBlock = api_get_setting('captcha_time_to_block');
6520
    $time = time() + $minutesToBlock * 60;
6521
    UserManager::update_extra_field_value(
6522
        $userInfo['user_id'],
6523
        'captcha_blocked_until_date',
6524
        api_get_utc_datetime($time)
6525
    );
6526
6527
    return true;
6528
}
6529
6530
/**
6531
 * @param string $username
6532
 *
6533
 * @return bool
6534
 */
6535
function api_clean_account_captcha($username)
6536
{
6537
    $userInfo = api_get_user_info_from_username($username);
6538
    if (empty($userInfo)) {
6539
        return false;
6540
    }
6541
    Session::erase('loginFailedCount');
6542
    UserManager::update_extra_field_value(
6543
        $userInfo['user_id'],
6544
        'captcha_blocked_until_date',
6545
        null
6546
    );
6547
6548
    return true;
6549
}
6550
6551
/**
6552
 * @param string $username
6553
 *
6554
 * @return bool
6555
 */
6556
function api_get_user_blocked_by_captcha($username)
6557
{
6558
    $userInfo = api_get_user_info_from_username($username);
6559
    if (empty($userInfo)) {
6560
        return false;
6561
    }
6562
    $data = UserManager::get_extra_user_data_by_field(
6563
        $userInfo['user_id'],
6564
        'captcha_blocked_until_date'
6565
    );
6566
    if (isset($data) && isset($data['captcha_blocked_until_date'])) {
6567
        return $data['captcha_blocked_until_date'];
6568
    }
6569
6570
    return false;
6571
}
6572
6573
/**
6574
 * If true, the drh can access all content (courses, users) inside a session.
6575
 *
6576
 * @return bool
6577
 */
6578
function api_drh_can_access_all_session_content()
6579
{
6580
    return 'true' === api_get_setting('drh_can_access_all_session_content');
6581
}
6582
6583
/**
6584
 * Checks if user can login as another user.
6585
 *
6586
 * @param int $loginAsUserId the user id to log in
6587
 * @param int $userId        my user id
6588
 *
6589
 * @return bool
6590
 */
6591
function api_can_login_as($loginAsUserId, $userId = null)
6592
{
6593
    $loginAsUserId = (int) $loginAsUserId;
6594
    if (empty($loginAsUserId)) {
6595
        return false;
6596
    }
6597
6598
    if (empty($userId)) {
6599
        $userId = api_get_user_id();
6600
    }
6601
6602
    if ($loginAsUserId == $userId) {
6603
        return false;
6604
    }
6605
6606
    // If target is an admin, only global admins can login to admin accounts
6607
    if (api_is_platform_admin_by_id($loginAsUserId)) {
6608
        if (!api_global_admin_can_edit_admin($loginAsUserId)) {
6609
            return false;
6610
        }
6611
    }
6612
6613
    $userInfo = api_get_user_info($loginAsUserId);
6614
6615
    $isDrh = function () use ($loginAsUserId) {
6616
        if (api_is_drh()) {
6617
            if (api_drh_can_access_all_session_content()) {
6618
                $users   = SessionManager::getAllUsersFromCoursesFromAllSessionFromStatus('drh_all', api_get_user_id());
6619
                $userIds = [];
6620
                if (is_array($users)) {
6621
                    foreach ($users as $user) {
6622
                        $userIds[] = $user['id'];
6623
                    }
6624
                }
6625
                return in_array($loginAsUserId, $userIds);
6626
            }
6627
6628
            if (UserManager::is_user_followed_by_drh($loginAsUserId, api_get_user_id())) {
6629
                return true;
6630
            }
6631
        }
6632
6633
        return false;
6634
    };
6635
6636
    $loginAsStatusForSessionAdmins = [STUDENT];
6637
6638
    if ('true' === api_get_setting('session.allow_session_admin_login_as_teacher')) {
6639
        $loginAsStatusForSessionAdmins[] = COURSEMANAGER;
6640
    }
6641
6642
    return api_is_platform_admin() // local admins can login as (except into other admins unless allowed above)
6643
        || (api_is_session_admin() && in_array($userInfo['status'], $loginAsStatusForSessionAdmins))
6644
        || $isDrh();
6645
}
6646
6647
/**
6648
 * Return true on https install.
6649
 *
6650
 * @return bool
6651
 */
6652
function api_is_https()
6653
{
6654
    if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (! empty($_SERVER['HTTP_...ttps_forwarded_proto')), Probably Intended Meaning: ! empty($_SERVER['HTTP_X...tps_forwarded_proto')))
Loading history...
6655
        'https' == $_SERVER['HTTP_X_FORWARDED_PROTO'] || !empty(api_get_configuration_value('force_https_forwarded_proto'))
6656
    ) {
6657
        $isSecured = true;
6658
    } else {
6659
        if (!empty($_SERVER['HTTPS']) && 'off' != $_SERVER['HTTPS']) {
6660
            $isSecured = true;
6661
        } else {
6662
            $isSecured = false;
6663
            // last chance
6664
            if (!empty($_SERVER['SERVER_PORT']) && 443 == $_SERVER['SERVER_PORT']) {
6665
                $isSecured = true;
6666
            }
6667
        }
6668
    }
6669
6670
    return $isSecured;
6671
}
6672
6673
/**
6674
 * Return protocol (http or https).
6675
 *
6676
 * @return string
6677
 */
6678
function api_get_protocol()
6679
{
6680
    return api_is_https() ? 'https' : 'http';
6681
}
6682
6683
/**
6684
 * Get origin.
6685
 *
6686
 * @param string
6687
 *
6688
 * @return string
6689
 */
6690
function api_get_origin()
6691
{
6692
    return isset($_REQUEST['origin']) ? urlencode(Security::remove_XSS(urlencode($_REQUEST['origin']))) : '';
6693
}
6694
6695
/**
6696
 * Warns an user that the portal reach certain limit.
6697
 *
6698
 * @param string $limitName
6699
 */
6700
function api_warn_hosting_contact($limitName)
6701
{
6702
    $hostingParams = api_get_configuration_value(1);
6703
    $email = null;
6704
6705
    if (!empty($hostingParams)) {
6706
        if (isset($hostingParams['hosting_contact_mail'])) {
6707
            $email = $hostingParams['hosting_contact_mail'];
6708
        }
6709
    }
6710
6711
    if (!empty($email)) {
6712
        $subject = get_lang('Hosting warning reached');
6713
        $body = get_lang('Portal name').': '.api_get_path(WEB_PATH)." \n ";
6714
        $body .= get_lang('Portal\'s limit type').': '.$limitName." \n ";
6715
        if (isset($hostingParams[$limitName])) {
6716
            $body .= get_lang('Value').': '.$hostingParams[$limitName];
6717
        }
6718
        api_mail_html(null, $email, $subject, $body);
6719
    }
6720
}
6721
6722
/**
6723
 * Gets value of a variable from config/configuration.php
6724
 * Variables that are not set in the configuration.php file but set elsewhere:
6725
 * - virtual_css_theme_folder (vchamilo plugin)
6726
 * - access_url (global.inc.php)
6727
 * - apc/apc_prefix (global.inc.php).
6728
 *
6729
 * @param string $variable
6730
 *
6731
 * @return bool|mixed
6732
 */
6733
function api_get_configuration_value($variable)
6734
{
6735
    global $_configuration;
6736
    // Check the current url id, id = 1 by default
6737
    $urlId = isset($_configuration['access_url']) ? (int) $_configuration['access_url'] : 1;
6738
6739
    $variable = trim($variable);
6740
6741
    // Check if variable exists
6742
    if (isset($_configuration[$variable])) {
6743
        if (is_array($_configuration[$variable])) {
6744
            // Check if it exists for the sub portal
6745
            if (array_key_exists($urlId, $_configuration[$variable])) {
6746
                return $_configuration[$variable][$urlId];
6747
            } else {
6748
                // Try to found element with id = 1 (master portal)
6749
                if (array_key_exists(1, $_configuration[$variable])) {
6750
                    return $_configuration[$variable][1];
6751
                }
6752
            }
6753
        }
6754
6755
        return $_configuration[$variable];
6756
    }
6757
6758
    return false;
6759
}
6760
6761
/**
6762
 * Gets a specific hosting limit.
6763
 *
6764
 * @param int $urlId The URL ID.
6765
 * @param string $limitName The name of the limit.
6766
 * @return mixed The value of the limit, or null if not found.
6767
 */
6768
function get_hosting_limit(int $urlId, string $limitName): mixed
6769
{
6770
    if (!Container::$container->hasParameter('settings_overrides')) {
6771
        return [];
6772
    }
6773
6774
    $settingsOverrides = Container::$container->getParameter('settings_overrides');
6775
6776
    $limits = $settingsOverrides[$urlId]['hosting_limit'] ?? $settingsOverrides['default']['hosting_limit'];
6777
6778
    foreach ($limits as $limitArray) {
6779
        if (isset($limitArray[$limitName])) {
6780
            return $limitArray[$limitName];
6781
        }
6782
    }
6783
6784
    return null;
6785
}
6786
6787
6788
/**
6789
 * Retrieves an environment variable value with validation and handles boolean conversion.
6790
 *
6791
 * @param string $variable The name of the environment variable.
6792
 * @param mixed $default The default value to return if the variable is not set.
6793
 * @return mixed The value of the environment variable, converted to boolean if necessary, or the default value.
6794
 */
6795
function api_get_env_variable(string $variable, mixed $default = null): mixed
6796
{
6797
    $variable = strtolower($variable);
6798
    if (Container::$container->hasParameter($variable)) {
6799
        return Container::$container->getParameter($variable);
6800
    }
6801
6802
    return $default;
6803
}
6804
6805
/**
6806
 * Retreives and returns a value in a hierarchical configuration array
6807
 * api_get_configuration_sub_value('a/b/c') returns api_get_configuration_value('a')['b']['c'].
6808
 *
6809
 * @param string $path      the successive array keys, separated by the separator
6810
 * @param mixed  $default   value to be returned if not found, null by default
6811
 * @param string $separator '/' by default
6812
 * @param array  $array     the active configuration array by default
6813
 *
6814
 * @return mixed the found value or $default
6815
 */
6816
function api_get_configuration_sub_value($path, $default = null, $separator = '/', $array = null)
6817
{
6818
    $pos = strpos($path, $separator);
6819
    if (false === $pos) {
6820
        if (is_null($array)) {
6821
            return api_get_configuration_value($path);
6822
        }
6823
        if (is_array($array) && array_key_exists($path, $array)) {
6824
            return $array[$path];
6825
        }
6826
6827
        return $default;
6828
    }
6829
    $key = substr($path, 0, $pos);
6830
    if (is_null($array)) {
6831
        $newArray = api_get_configuration_value($key);
6832
    } elseif (is_array($array) && array_key_exists($key, $array)) {
6833
        $newArray = $array[$key];
6834
    } else {
6835
        return $default;
6836
    }
6837
    if (is_array($newArray)) {
6838
        $newPath = substr($path, $pos + 1);
6839
6840
        return api_get_configuration_sub_value($newPath, $default, $separator, $newArray);
6841
    }
6842
6843
    return $default;
6844
}
6845
6846
/**
6847
 * Retrieves and returns a value in a hierarchical configuration array
6848
 * api_array_sub_value($array, 'a/b/c') returns $array['a']['b']['c'].
6849
 *
6850
 * @param array  $array     the recursive array that contains the value to be returned (or not)
6851
 * @param string $path      the successive array keys, separated by the separator
6852
 * @param mixed  $default   the value to be returned if not found
6853
 * @param string $separator the separator substring
6854
 *
6855
 * @return mixed the found value or $default
6856
 */
6857
function api_array_sub_value($array, $path, $default = null, $separator = '/')
6858
{
6859
    $pos = strpos($path, $separator);
6860
    if (false === $pos) {
6861
        if (is_array($array) && array_key_exists($path, $array)) {
6862
            return $array[$path];
6863
        }
6864
6865
        return $default;
6866
    }
6867
    $key = substr($path, 0, $pos);
6868
    if (is_array($array) && array_key_exists($key, $array)) {
6869
        $newArray = $array[$key];
6870
    } else {
6871
        return $default;
6872
    }
6873
    if (is_array($newArray)) {
6874
        $newPath = substr($path, $pos + 1);
6875
6876
        return api_array_sub_value($newArray, $newPath, $default);
6877
    }
6878
6879
    return $default;
6880
}
6881
6882
/**
6883
 * Returns supported image extensions in the portal.
6884
 *
6885
 * @param bool $supportVectors Whether vector images should also be accepted or not
6886
 *
6887
 * @return array Supported image extensions in the portal
6888
 */
6889
function api_get_supported_image_extensions($supportVectors = true)
6890
{
6891
    // jpg can also be called jpeg, jpe, jfif and jif. See https://en.wikipedia.org/wiki/JPEG#JPEG_filename_extensions
6892
    $supportedImageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'jpe', 'jfif', 'jif'];
6893
    if ($supportVectors) {
6894
        array_push($supportedImageExtensions, 'svg');
6895
    }
6896
    if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
6897
        array_push($supportedImageExtensions, 'webp');
6898
    }
6899
6900
    return $supportedImageExtensions;
6901
}
6902
6903
/**
6904
 * This setting changes the registration status for the campus.
6905
 *
6906
 * @author Patrick Cool <[email protected]>, Ghent University
6907
 *
6908
 * @version August 2006
6909
 *
6910
 * @param bool $listCampus Whether we authorize
6911
 *
6912
 * @todo the $_settings should be reloaded here. => write api function for this and use this in global.inc.php also.
6913
 */
6914
function api_register_campus($listCampus = true)
6915
{
6916
    $tbl_settings = Database::get_main_table(TABLE_MAIN_SETTINGS);
6917
6918
    $sql = "UPDATE $tbl_settings SET selected_value='true' WHERE variable='registered'";
6919
    Database::query($sql);
6920
6921
    if (!$listCampus) {
6922
        $sql = "UPDATE $tbl_settings SET selected_value='true' WHERE variable='donotlistcampus'";
6923
        Database::query($sql);
6924
    }
6925
}
6926
6927
/**
6928
 * Check whether the user type should be exclude.
6929
 * Such as invited or anonymous users.
6930
 *
6931
 * @param bool $checkDB Optional. Whether check the user status
6932
 * @param int  $userId  Options. The user id
6933
 *
6934
 * @return bool
6935
 */
6936
function api_is_excluded_user_type($checkDB = false, $userId = 0)
6937
{
6938
    if ($checkDB) {
6939
        $userId = empty($userId) ? api_get_user_id() : (int) $userId;
6940
6941
        if (0 == $userId) {
6942
            return true;
6943
        }
6944
6945
        $userInfo = api_get_user_info($userId);
6946
6947
        switch ($userInfo['status']) {
6948
            case INVITEE:
6949
            case ANONYMOUS:
6950
                return true;
6951
            default:
6952
                return false;
6953
        }
6954
    }
6955
6956
    $isInvited = api_is_invitee();
6957
    $isAnonymous = api_is_anonymous();
6958
6959
    if ($isInvited || $isAnonymous) {
6960
        return true;
6961
    }
6962
6963
    return false;
6964
}
6965
6966
/**
6967
 * Get the user status to ignore in reports.
6968
 *
6969
 * @param string $format Optional. The result type (array or string)
6970
 *
6971
 * @return array|string
6972
 */
6973
function api_get_users_status_ignored_in_reports($format = 'array')
6974
{
6975
    $excludedTypes = [
6976
        INVITEE,
6977
        ANONYMOUS,
6978
    ];
6979
6980
    if ('string' == $format) {
6981
        return implode(', ', $excludedTypes);
6982
    }
6983
6984
    return $excludedTypes;
6985
}
6986
6987
/**
6988
 * Set the Site Use Cookie Warning for 1 year.
6989
 */
6990
function api_set_site_use_cookie_warning_cookie()
6991
{
6992
    setcookie('ChamiloUsesCookies', 'ok', time() + 31556926);
6993
}
6994
6995
/**
6996
 * Return true if the Site Use Cookie Warning Cookie warning exists.
6997
 *
6998
 * @return bool
6999
 */
7000
function api_site_use_cookie_warning_cookie_exist()
7001
{
7002
    return isset($_COOKIE['ChamiloUsesCookies']);
7003
}
7004
7005
/**
7006
 * Given a number of seconds, format the time to show hours, minutes and seconds.
7007
 *
7008
 * @param int    $time         The time in seconds
7009
 * @param string $originFormat Optional. PHP o JS
7010
 *
7011
 * @return string (00h00'00")
7012
 */
7013
function api_format_time($time, $originFormat = 'php')
7014
{
7015
    $h = get_lang('h');
7016
    $hours = $time / 3600;
7017
    $mins = ($time % 3600) / 60;
7018
    $secs = ($time % 60);
7019
7020
    if ($time < 0) {
7021
        $hours = 0;
7022
        $mins = 0;
7023
        $secs = 0;
7024
    }
7025
7026
    if ('js' === $originFormat) {
7027
        $formattedTime = trim(sprintf("%02d : %02d : %02d", $hours, $mins, $secs));
7028
    } else {
7029
        $formattedTime = trim(sprintf("%02d$h%02d'%02d\"", $hours, $mins, $secs));
7030
    }
7031
7032
    return $formattedTime;
7033
}
7034
7035
/**
7036
 * Sends an email
7037
 * Sender name and email can be specified, if not specified
7038
 * name and email of the platform admin are used.
7039
 *
7040
 * @param string    name of recipient
7041
 * @param string    email of recipient
7042
 * @param string    email subject
7043
 * @param string    email body
7044
 * @param string    sender name
7045
 * @param string    sender e-mail
7046
 * @param array     extra headers in form $headers = array($name => $value) to allow parsing
7047
 * @param array     data file (path and filename)
7048
 * @param bool      True for attaching a embedded file inside content html (optional)
7049
 * @param array     Additional parameters
7050
 *
7051
 * @return bool true if mail was sent
7052
 */
7053
function api_mail_html(
7054
    $recipientName,
7055
    $recipientEmail,
7056
    $subject,
7057
    $body,
7058
    $senderName = '',
7059
    $senderEmail = '',
7060
    $extra_headers = [],
7061
    $data_file = [],
7062
    $embeddedImage = false,
7063
    $additionalParameters = [],
7064
    string $sendErrorTo = null
7065
) {
7066
    $mailHelper = Container::$container->get(MailHelper::class);
7067
7068
    return $mailHelper->send(
7069
        $recipientName,
7070
        $recipientEmail,
7071
        $subject,
7072
        $body,
7073
        $senderName,
7074
        $senderEmail,
7075
        $extra_headers,
7076
        $data_file,
7077
        $embeddedImage,
7078
        $additionalParameters,
7079
        $sendErrorTo
7080
    );
7081
}
7082
7083
/**
7084
 * @param int  $tool       Possible values: GroupManager::GROUP_TOOL_*
7085
 * @param bool $showHeader
7086
 */
7087
function api_protect_course_group($tool, $showHeader = true)
7088
{
7089
    $groupId = api_get_group_id();
7090
    if (!empty($groupId)) {
7091
        if (api_is_platform_admin()) {
7092
            return true;
7093
        }
7094
7095
        if (api_is_allowed_to_edit(false, true, true)) {
7096
            return true;
7097
        }
7098
7099
        $userId = api_get_user_id();
7100
        $sessionId = api_get_session_id();
7101
        if (!empty($sessionId)) {
7102
            if (api_is_coach($sessionId, api_get_course_int_id())) {
7103
                return true;
7104
            }
7105
7106
            if (api_is_drh()) {
7107
                if (SessionManager::isUserSubscribedAsHRM($sessionId, $userId)) {
7108
                    return true;
7109
                }
7110
            }
7111
        }
7112
7113
        $group = api_get_group_entity($groupId);
7114
7115
        // Group doesn't exists
7116
        if (null === $group) {
7117
            api_not_allowed($showHeader);
7118
        }
7119
7120
        // Check group access
7121
        $allow = GroupManager::userHasAccess(
7122
            $userId,
7123
            $group,
7124
            $tool
7125
        );
7126
7127
        if (!$allow) {
7128
            api_not_allowed($showHeader);
7129
        }
7130
    }
7131
7132
    return false;
7133
}
7134
7135
/**
7136
 * Check if a date is in a date range.
7137
 *
7138
 * @param datetime $startDate
7139
 * @param datetime $endDate
7140
 * @param datetime $currentDate
7141
 *
7142
 * @return bool true if date is in rage, false otherwise
7143
 */
7144
function api_is_date_in_date_range($startDate, $endDate, $currentDate = null)
7145
{
7146
    $startDate = strtotime(api_get_local_time($startDate));
7147
    $endDate = strtotime(api_get_local_time($endDate));
7148
    $currentDate = strtotime(api_get_local_time($currentDate));
7149
7150
    if ($currentDate >= $startDate && $currentDate <= $endDate) {
7151
        return true;
7152
    }
7153
7154
    return false;
7155
}
7156
7157
/**
7158
 * Eliminate the duplicates of a multidimensional array by sending the key.
7159
 *
7160
 * @param array $array multidimensional array
7161
 * @param int   $key   key to find to compare
7162
 *
7163
 * @return array
7164
 */
7165
function api_unique_multidim_array($array, $key)
7166
{
7167
    $temp_array = [];
7168
    $i = 0;
7169
    $key_array = [];
7170
7171
    foreach ($array as $val) {
7172
        if (!in_array($val[$key], $key_array)) {
7173
            $key_array[$i] = $val[$key];
7174
            $temp_array[$i] = $val;
7175
        }
7176
        $i++;
7177
    }
7178
7179
    return $temp_array;
7180
}
7181
7182
/**
7183
 * Limit the access to Session Admins when the limit_session_admin_role
7184
 * configuration variable is set to true.
7185
 */
7186
function api_protect_limit_for_session_admin()
7187
{
7188
    $limitAdmin = api_get_setting('limit_session_admin_role');
7189
    if (api_is_session_admin() && 'true' === $limitAdmin) {
7190
        api_not_allowed(true);
7191
    }
7192
}
7193
7194
/**
7195
 * Limits that a session admin has access to list users.
7196
 * When limit_session_admin_list_users configuration variable is set to true.
7197
 */
7198
function api_protect_session_admin_list_users()
7199
{
7200
    $limitAdmin = ('true' === api_get_setting('session.limit_session_admin_list_users'));
7201
7202
    if (api_is_session_admin() && true === $limitAdmin) {
7203
        api_not_allowed(true);
7204
    }
7205
}
7206
7207
/**
7208
 * @return bool
7209
 */
7210
function api_is_student_view_active(): bool
7211
{
7212
    $studentView = Session::read('studentview');
7213
7214
    return 'studentview' === $studentView;
7215
}
7216
7217
/**
7218
 * Converts string value to float value.
7219
 *
7220
 * 3.141516 => 3.141516
7221
 * 3,141516 => 3.141516
7222
 *
7223
 * @todo WIP
7224
 *
7225
 * @param string $number
7226
 *
7227
 * @return float
7228
 */
7229
function api_float_val($number)
7230
{
7231
    return (float) str_replace(',', '.', trim($number));
7232
}
7233
7234
/**
7235
 * Converts float values
7236
 * Example if $decimals = 2.
7237
 *
7238
 * 3.141516 => 3.14
7239
 * 3,141516 => 3,14
7240
 *
7241
 * @param string $number            number in iso code
7242
 * @param int    $decimals
7243
 * @param string $decimalSeparator
7244
 * @param string $thousandSeparator
7245
 *
7246
 * @return bool|string
7247
 */
7248
function api_number_format($number, $decimals = 0, $decimalSeparator = '.', $thousandSeparator = ',')
7249
{
7250
    $number = api_float_val($number);
7251
7252
    return number_format($number, $decimals, $decimalSeparator, $thousandSeparator);
7253
}
7254
7255
/**
7256
 * Set location url with a exit break by default.
7257
 *
7258
 * @param string $url
7259
 * @param bool   $exit
7260
 */
7261
function api_location($url, $exit = true)
7262
{
7263
    header('Location: '.$url);
7264
7265
    if ($exit) {
7266
        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...
7267
    }
7268
}
7269
7270
/**
7271
 * @param string $from
7272
 * @param string $to
7273
 *
7274
 * @return string
7275
 */
7276
function api_get_relative_path($from, $to)
7277
{
7278
    // some compatibility fixes for Windows paths
7279
    $from = is_dir($from) ? rtrim($from, '\/').'/' : $from;
7280
    $to = is_dir($to) ? rtrim($to, '\/').'/' : $to;
7281
    $from = str_replace('\\', '/', $from);
7282
    $to = str_replace('\\', '/', $to);
7283
7284
    $from = explode('/', $from);
7285
    $to = explode('/', $to);
7286
    $relPath = $to;
7287
7288
    foreach ($from as $depth => $dir) {
7289
        // find first non-matching dir
7290
        if ($dir === $to[$depth]) {
7291
            // ignore this directory
7292
            array_shift($relPath);
7293
        } else {
7294
            // get number of remaining dirs to $from
7295
            $remaining = count($from) - $depth;
7296
            if ($remaining > 1) {
7297
                // add traversals up to first matching dir
7298
                $padLength = (count($relPath) + $remaining - 1) * -1;
7299
                $relPath = array_pad($relPath, $padLength, '..');
7300
                break;
7301
            } else {
7302
                $relPath[0] = './'.$relPath[0];
7303
            }
7304
        }
7305
    }
7306
7307
    return implode('/', $relPath);
7308
}
7309
7310
/**
7311
 * @param string $template
7312
 *
7313
 * @return string
7314
 */
7315
function api_find_template($template)
7316
{
7317
    return Template::findTemplateFilePath($template);
7318
}
7319
7320
/**
7321
 * @return array
7322
 */
7323
function api_get_language_list_for_flag()
7324
{
7325
    $table = Database::get_main_table(TABLE_MAIN_LANGUAGE);
7326
    $sql = "SELECT english_name, isocode FROM $table
7327
            ORDER BY original_name ASC";
7328
    static $languages = [];
7329
    if (empty($languages)) {
7330
        $result = Database::query($sql);
7331
        while ($row = Database::fetch_array($result)) {
7332
            $languages[$row['english_name']] = $row['isocode'];
7333
        }
7334
        $languages['english'] = 'gb';
7335
    }
7336
7337
    return $languages;
7338
}
7339
7340
function api_create_zip(string $name): ZipStream
7341
{
7342
    $zipStreamOptions = new Archive();
7343
    $zipStreamOptions->setSendHttpHeaders(true);
7344
    $zipStreamOptions->setContentDisposition('attachment');
7345
    $zipStreamOptions->setContentType('application/x-zip');
7346
7347
    return new ZipStream($name, $zipStreamOptions);
7348
}
7349
7350
function api_get_language_translate_html(): string
7351
{
7352
    $translate = 'true' === api_get_setting('editor.translate_html');
7353
7354
    if (!$translate) {
7355
        return '';
7356
    }
7357
7358
    /*$languageList = api_get_languages();
7359
    $hideAll = '';
7360
    foreach ($languageList as $isocode => $name) {
7361
        $hideAll .= '
7362
        $(".mce-translatehtml").hide();
7363
        $("span:lang('.$isocode.')").filter(
7364
            function(e, val) {
7365
                // Only find the spans if they have set the lang
7366
                if ($(this).attr("lang") == null) {
7367
                    return false;
7368
                }
7369
                // Ignore ckeditor classes
7370
                return !this.className.match(/cke(.*)/);
7371
        }).hide();'."\n";
7372
    }*/
7373
7374
    $userInfo = api_get_user_info();
7375
    if (!empty($userInfo['language'])) {
7376
        $isoCode = $userInfo['language'];
7377
7378
        return '
7379
            $(function() {
7380
                $(".mce-translatehtml").hide();
7381
                var defaultLanguageFromUser = "'.$isoCode.'";
7382
                $("span:lang('.$isoCode.')").show();
7383
            });
7384
        ';
7385
    }
7386
7387
    return '';
7388
}
7389
7390
function api_protect_webservices()
7391
{
7392
    if ('true' === api_get_setting('webservice.disable_webservices')) {
7393
        echo "Webservices are disabled. \n";
7394
        echo "To enable, add \$_configuration['disable_webservices'] = true; in configuration.php";
7395
        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...
7396
    }
7397
}
7398
7399
function api_filename_has_blacklisted_stream_wrapper(string $filename) {
7400
    if (strpos($filename, '://') > 0) {
7401
        $wrappers = stream_get_wrappers();
7402
        $allowedWrappers = ['http', 'https', 'file'];
7403
        foreach ($wrappers as $wrapper) {
7404
            if (in_array($wrapper, $allowedWrappers)) {
7405
                continue;
7406
            }
7407
            if (stripos($filename, $wrapper . '://') === 0) {
7408
                return true;
7409
            }
7410
        }
7411
    }
7412
    return false;
7413
}
7414
7415
/**
7416
 * Checks if a set of roles have a specific permission.
7417
 *
7418
 * @param string $permissionSlug The slug of the permission to check.
7419
 * @param array  $roles          An array of role codes to check against.
7420
 * @return bool True if any of the roles have the permission, false otherwise.
7421
 */
7422
function api_get_permission(string $permissionSlug, array $roles): bool
7423
{
7424
    $permissionService = Container::$container->get(PermissionHelper::class);
7425
7426
    return $permissionService->hasPermission($permissionSlug, $roles);
7427
}
7428
7429
/**
7430
 * Calculate the percentage of change between two numbers.
7431
 *
7432
 * @param int $newValue
7433
 * @param int $oldValue
7434
 * @return string
7435
 */
7436
function api_calculate_increment_percent(int $newValue, int $oldValue): string
7437
{
7438
    if ($oldValue <= 0) {
7439
        $result = " - ";
7440
    } else {
7441
        $result = ' '.round(100 * (($newValue / $oldValue) - 1), 2).' %';
7442
    }
7443
    return $result;
7444
}
7445
7446
/**
7447
 * @todo Move to UserRegistrationHelper when migrating inscription.php to Symfony
7448
 */
7449
function api_email_reached_registration_limit(string $email): bool
7450
{
7451
    $limit = (int) api_get_setting('platform.hosting_limit_identical_email');
7452
7453
    if ($limit <= 0 || empty($email)) {
7454
        return false;
7455
    }
7456
7457
    $repo = Container::getUserRepository();
7458
    $count = $repo->countUsersByEmail($email);
7459
7460
    return $count >= $limit;
7461
}
7462
7463