api_set_setting()   F
last analyzed

Complexity

Conditions 23
Paths 2257

Size

Total Lines 87
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 23
eloc 63
nc 2257
nop 5
dl 0
loc 87
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\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', 0); // 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 => Container::$container ? Container::getCacheDir() : $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'])
1545
            ->getAuthSourcesAuthentications(
1546
                Container::getAccessUrlUtil()->getCurrent()
1547
            )
1548
        ;
1549
        $result_array['user_is_online_in_chat'] = 0;
1550
        if ($checkIfUserOnline) {
1551
            $use_status_in_platform = user_is_online($user_id);
1552
            $result_array['user_is_online'] = $use_status_in_platform;
1553
            $user_online_in_chat = 0;
1554
            if ($use_status_in_platform) {
1555
                $user_status = UserManager::get_extra_user_data_by_field(
1556
                    $user_id,
1557
                    'user_chat_status',
1558
                    false,
1559
                    true
1560
                );
1561
                if (1 == (int) $user_status['user_chat_status']) {
1562
                    $user_online_in_chat = 1;
1563
                }
1564
            }
1565
            $result_array['user_is_online_in_chat'] = $user_online_in_chat;
1566
        }
1567
1568
        if ($loadExtraData) {
1569
            $fieldValue = new ExtraFieldValue('user');
1570
            $result_array['extra'] = $fieldValue->getAllValuesForAnItem(
1571
                $user_id,
1572
                $loadOnlyVisibleExtraData
1573
            );
1574
        }
1575
        $user = _api_format_user($result_array, $showPassword, $loadAvatars);
1576
    }
1577
1578
    return $user;
1579
}
1580
1581
function api_get_user_info_from_entity(
1582
    User $user,
1583
    $checkIfUserOnline = false,
1584
    $showPassword = false,
1585
    $loadExtraData = false,
1586
    $loadOnlyVisibleExtraData = false,
1587
    $loadAvatars = true,
1588
    $loadCertificate = false
1589
) {
1590
    if (!$user instanceof UserInterface) {
1591
        return false;
1592
    }
1593
1594
    // Make sure user_id is safe
1595
    $user_id = (int) $user->getId();
1596
1597
    if (empty($user_id)) {
1598
        $userFromSession = Session::read('_user');
1599
1600
        if (isset($userFromSession) && !empty($userFromSession)) {
1601
            return $userFromSession;
1602
        }
1603
1604
        return false;
1605
    }
1606
1607
    $result = [];
1608
    $result['user_is_online_in_chat'] = 0;
1609
    if ($checkIfUserOnline) {
1610
        $use_status_in_platform = user_is_online($user_id);
1611
        $result['user_is_online'] = $use_status_in_platform;
1612
        $user_online_in_chat = 0;
1613
        if ($use_status_in_platform) {
1614
            $user_status = UserManager::get_extra_user_data_by_field(
1615
                $user_id,
1616
                'user_chat_status',
1617
                false,
1618
                true
1619
            );
1620
            if (1 == (int) $user_status['user_chat_status']) {
1621
                $user_online_in_chat = 1;
1622
            }
1623
        }
1624
        $result['user_is_online_in_chat'] = $user_online_in_chat;
1625
    }
1626
1627
    if ($loadExtraData) {
1628
        $fieldValue = new ExtraFieldValue('user');
1629
        $result['extra'] = $fieldValue->getAllValuesForAnItem(
1630
            $user_id,
1631
            $loadOnlyVisibleExtraData
1632
        );
1633
    }
1634
1635
    $result['username'] = $user->getUsername();
1636
    $result['status'] = $user->getStatus();
1637
    $result['firstname'] = $user->getFirstname();
1638
    $result['lastname'] = $user->getLastname();
1639
    $result['email'] = $result['mail'] = $user->getEmail();
1640
    $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
1641
    $result['complete_name_with_username'] = $result['complete_name'];
1642
1643
    if (!empty($result['username']) && 'false' === api_get_setting('profile.hide_username_with_complete_name')) {
1644
        $result['complete_name_with_username'] = $result['complete_name'].' ('.$result['username'].')';
1645
    }
1646
1647
    $showEmail = 'true' === api_get_setting('show_email_addresses');
1648
    if (!empty($result['email'])) {
1649
        $result['complete_name_with_email_forced'] = $result['complete_name'].' ('.$result['email'].')';
1650
        if ($showEmail) {
1651
            $result['complete_name_with_email'] = $result['complete_name'].' ('.$result['email'].')';
1652
        }
1653
    } else {
1654
        $result['complete_name_with_email'] = $result['complete_name'];
1655
        $result['complete_name_with_email_forced'] = $result['complete_name'];
1656
    }
1657
1658
    // Kept for historical reasons
1659
    $result['firstName'] = $result['firstname'];
1660
    $result['lastName'] = $result['lastname'];
1661
1662
    $attributes = [
1663
        'picture_uri',
1664
        'last_login',
1665
        'user_is_online',
1666
    ];
1667
1668
    $result['phone'] = $user->getPhone();
1669
    $result['address'] = $user->getAddress();
1670
    $result['official_code'] = $user->getOfficialCode();
1671
    $result['active'] = $user->isActive();
1672
    $result['auth_sources'] = $user->getAuthSourcesAuthentications(
1673
        Container::getAccessUrlUtil()->getCurrent()
1674
    );
1675
    $result['language'] = $user->getLocale();
1676
    $result['creator_id'] = $user->getCreatorId();
1677
    $result['created_at'] = $user->getCreatedAt()->format('Y-m-d H:i:s');
1678
    $result['hr_dept_id'] = $user->getHrDeptId();
1679
    $result['expiration_date'] = '';
1680
    if ($user->getExpirationDate()) {
1681
        $result['expiration_date'] = $user->getExpirationDate()->format('Y-m-d H:i:s');
1682
    }
1683
1684
    $result['last_login'] = null;
1685
    if ($user->getLastLogin()) {
1686
        $result['last_login'] = $user->getLastLogin()->format('Y-m-d H:i:s');
1687
    }
1688
1689
    $result['competences'] = $user->getCompetences();
1690
    $result['diplomas'] = $user->getDiplomas();
1691
    $result['teach'] = $user->getTeach();
1692
    $result['openarea'] = $user->getOpenarea();
1693
    $user_id = (int) $user->getId();
1694
1695
    // Maintain the user_id index for backwards compatibility
1696
    $result['user_id'] = $result['id'] = $user_id;
1697
1698
    if ($loadCertificate) {
1699
        $hasCertificates = Certificate::getCertificateByUser($user_id);
1700
        $result['has_certificates'] = 0;
1701
        if (!empty($hasCertificates)) {
1702
            $result['has_certificates'] = 1;
1703
        }
1704
    }
1705
1706
    $result['icon_status'] = '';
1707
    $result['icon_status_medium'] = '';
1708
    $result['is_admin'] = UserManager::is_admin($user_id);
1709
1710
    // Getting user avatar.
1711
    if ($loadAvatars) {
1712
        $result['avatar'] = '';
1713
        $result['avatar_no_query'] = '';
1714
        $result['avatar_small'] = '';
1715
        $result['avatar_medium'] = '';
1716
        $urlImg = '/';
1717
        $iconStatus = '';
1718
        $iconStatusMedium = '';
1719
1720
        switch ($user->getStatus()) {
1721
            case STUDENT:
1722
                if (isset($result['has_certificates']) && $result['has_certificates']) {
1723
                    $iconStatus = $urlImg.'icons/svg/identifier_graduated.svg';
1724
                } else {
1725
                    $iconStatus = $urlImg.'icons/svg/identifier_student.svg';
1726
                }
1727
                break;
1728
            case COURSEMANAGER:
1729
                if ($result['is_admin']) {
1730
                    $iconStatus = $urlImg.'icons/svg/identifier_admin.svg';
1731
                } else {
1732
                    $iconStatus = $urlImg.'icons/svg/identifier_teacher.svg';
1733
                }
1734
                break;
1735
            case STUDENT_BOSS:
1736
                $iconStatus = $urlImg.'icons/svg/identifier_teacher.svg';
1737
                break;
1738
        }
1739
1740
        if (!empty($iconStatus)) {
1741
            $iconStatusMedium = '<img src="'.$iconStatus.'" width="32px" height="32px">';
1742
            $iconStatus = '<img src="'.$iconStatus.'" width="22px" height="22px">';
1743
        }
1744
1745
        $result['icon_status'] = $iconStatus;
1746
        $result['icon_status_medium'] = $iconStatusMedium;
1747
    }
1748
1749
    if (isset($result['user_is_online'])) {
1750
        $result['user_is_online'] = true == $result['user_is_online'] ? 1 : 0;
1751
    }
1752
    if (isset($result['user_is_online_in_chat'])) {
1753
        $result['user_is_online_in_chat'] = $result['user_is_online_in_chat'];
1754
    }
1755
1756
    $result['password'] = '';
1757
    if ($showPassword) {
1758
        $result['password'] = $user->getPassword();
1759
    }
1760
1761
    if (isset($result['profile_completed'])) {
1762
        $result['profile_completed'] = $result['profile_completed'];
1763
    }
1764
1765
    $result['profile_url'] = api_get_path(WEB_CODE_PATH).'social/profile.php?u='.$user_id;
1766
1767
    // Send message link
1768
    $sendMessage = api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_popup&user_id='.$user_id;
1769
    $result['complete_name_with_message_link'] = Display::url(
1770
        $result['complete_name_with_username'],
1771
        $sendMessage,
1772
        ['class' => 'ajax']
1773
    );
1774
1775
    if (isset($result['extra'])) {
1776
        $result['extra'] = $result['extra'];
1777
    }
1778
1779
    return $result;
1780
}
1781
1782
function api_get_lp_entity(int $id): ?CLp
1783
{
1784
    return Database::getManager()->getRepository(CLp::class)->find($id);
1785
}
1786
1787
function api_get_user_entity(int $userId = 0): ?User
1788
{
1789
    $userId = $userId ?: api_get_user_id();
1790
    $repo = Container::getUserRepository();
1791
1792
    return $repo->find($userId);
1793
}
1794
1795
function api_get_current_user(): ?User
1796
{
1797
    $isLoggedIn = Container::getAuthorizationChecker()->isGranted('IS_AUTHENTICATED_REMEMBERED');
1798
    if (false === $isLoggedIn) {
1799
        return null;
1800
    }
1801
1802
    $token = Container::getTokenStorage()->getToken();
1803
1804
    if (null !== $token) {
1805
        return $token->getUser();
1806
    }
1807
1808
    return null;
1809
}
1810
1811
/**
1812
 * Finds all the information about a user from username instead of user id.
1813
 *
1814
 * @param string $username
1815
 *
1816
 * @return mixed $user_info array user_id, lastname, firstname, username, email or false on error
1817
 *
1818
 * @author Yannick Warnier <[email protected]>
1819
 */
1820
function api_get_user_info_from_username($username)
1821
{
1822
    if (empty($username)) {
1823
        return false;
1824
    }
1825
    $username = trim($username);
1826
1827
    $sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_USER)."
1828
            WHERE username='".Database::escape_string($username)."'";
1829
    $result = Database::query($sql);
1830
    if (Database::num_rows($result) > 0) {
1831
        $resultArray = Database::fetch_array($result);
1832
1833
        return _api_format_user($resultArray);
1834
    }
1835
1836
    return false;
1837
}
1838
1839
/**
1840
 * Get first user with an email.
1841
 *
1842
 * @param string $email
1843
 *
1844
 * @return array|bool
1845
 */
1846
function api_get_user_info_from_email($email = '')
1847
{
1848
    if (empty($email)) {
1849
        return false;
1850
    }
1851
    $sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_USER)."
1852
            WHERE email ='".Database::escape_string($email)."' LIMIT 1";
1853
    $result = Database::query($sql);
1854
    if (Database::num_rows($result) > 0) {
1855
        $resultArray = Database::fetch_array($result);
1856
1857
        return _api_format_user($resultArray);
1858
    }
1859
1860
    return false;
1861
}
1862
1863
/**
1864
 * @return string
1865
 */
1866
function api_get_course_id()
1867
{
1868
    return Session::read('_cid', null);
1869
}
1870
1871
/**
1872
 * Returns the current course id (integer).
1873
 *
1874
 * @param ?string $code Optional course code
1875
 *
1876
 * @return int
1877
 */
1878
function api_get_course_int_id(?string $code = null): int
1879
{
1880
    if (!empty($code)) {
1881
        $code = Database::escape_string($code);
1882
        $row = Database::select(
1883
            'id',
1884
            Database::get_main_table(TABLE_MAIN_COURSE),
1885
            ['where' => ['code = ?' => [$code]]],
1886
            'first'
1887
        );
1888
1889
        if (is_array($row) && isset($row['id'])) {
1890
            return $row['id'];
1891
        } else {
1892
            return 0;
1893
        }
1894
    }
1895
1896
    $cid = Session::read('_real_cid', 0);
1897
    if (empty($cid) && isset($_REQUEST['cid'])) {
1898
        $cid = (int) $_REQUEST['cid'];
1899
    }
1900
1901
    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...
1902
}
1903
1904
/**
1905
 * Gets a course setting from the current course_setting table. Try always using integer values.
1906
 *
1907
 * @param string       $settingName The name of the setting we want from the table
1908
 * @param Course|array $courseInfo
1909
 * @param bool         $force       force checking the value in the database
1910
 *
1911
 * @return mixed The value of that setting in that table. Return -1 if not found.
1912
 */
1913
function api_get_course_setting($settingName, $courseInfo = null, $force = false)
1914
{
1915
    if (empty($courseInfo)) {
1916
        $courseInfo = api_get_course_info();
1917
    }
1918
1919
    if (empty($courseInfo) || empty($settingName)) {
1920
        return -1;
1921
    }
1922
1923
    if ($courseInfo instanceof Course) {
1924
        $courseId = $courseInfo->getId();
1925
    } else {
1926
        $courseId = isset($courseInfo['real_id']) && !empty($courseInfo['real_id']) ? $courseInfo['real_id'] : 0;
1927
    }
1928
1929
    if (empty($courseId)) {
1930
        return -1;
1931
    }
1932
1933
    static $courseSettingInfo = [];
1934
1935
    if ($force) {
1936
        $courseSettingInfo = [];
1937
    }
1938
1939
    if (!isset($courseSettingInfo[$courseId])) {
1940
        $table = Database::get_course_table(TABLE_COURSE_SETTING);
1941
        $settingName = Database::escape_string($settingName);
1942
1943
        $sql = "SELECT variable, value FROM $table
1944
                WHERE c_id = $courseId ";
1945
        $res = Database::query($sql);
1946
        if (Database::num_rows($res) > 0) {
1947
            $result = Database::store_result($res, 'ASSOC');
1948
            $courseSettingInfo[$courseId] = array_column($result, 'value', 'variable');
1949
1950
            if (isset($courseSettingInfo[$courseId]['email_alert_manager_on_new_quiz'])) {
1951
                $value = $courseSettingInfo[$courseId]['email_alert_manager_on_new_quiz'];
1952
                if (!is_null($value)) {
1953
                    $result = explode(',', $value);
1954
                    $courseSettingInfo[$courseId]['email_alert_manager_on_new_quiz'] = $result;
1955
                }
1956
            }
1957
        }
1958
    }
1959
1960
    if (isset($courseSettingInfo[$courseId]) && isset($courseSettingInfo[$courseId][$settingName])) {
1961
        return $courseSettingInfo[$courseId][$settingName];
1962
    }
1963
1964
    return -1;
1965
}
1966
1967
function api_get_course_plugin_setting($plugin, $settingName, $courseInfo = [])
1968
{
1969
    $value = api_get_course_setting($settingName, $courseInfo, true);
1970
1971
    if (-1 === $value) {
1972
        // Check global settings
1973
        $value = api_get_plugin_setting($plugin, $settingName);
1974
        if ('true' === $value) {
1975
            return 1;
1976
        }
1977
        if ('false' === $value) {
1978
            return 0;
1979
        }
1980
        if (null === $value) {
1981
            return -1;
1982
        }
1983
    }
1984
1985
    return $value;
1986
}
1987
1988
/**
1989
 * Gets an anonymous user ID.
1990
 *
1991
 * For some tools that need tracking, like the learnpath tool, it is necessary
1992
 * to have a usable user-id to enable some kind of tracking, even if not
1993
 * perfect. An anonymous ID is taken from the users table by looking for a
1994
 * status of "6" (anonymous).
1995
 *
1996
 * @return int User ID of the anonymous user, or O if no anonymous user found
1997
 */
1998
function api_get_anonymous_id()
1999
{
2000
    // Find if another anon is connected now
2001
    $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
2002
    $tableU = Database::get_main_table(TABLE_MAIN_USER);
2003
    $ip = Database::escape_string(api_get_real_ip());
2004
    $max = (int) api_get_setting('admin.max_anonymous_users');
2005
    if ($max >= 2) {
2006
        $sql = "SELECT * FROM $table as TEL
2007
                JOIN $tableU as U
2008
                ON U.id = TEL.login_user_id
2009
                WHERE TEL.user_ip = '$ip'
2010
                    AND U.status = ".ANONYMOUS."
2011
                    AND U.id != 2 ";
2012
2013
        $result = Database::query($sql);
2014
        if (empty(Database::num_rows($result))) {
2015
            $login = uniqid('anon_');
2016
            $anonList = UserManager::get_user_list(['status' => ANONYMOUS], ['created_at ASC']);
2017
            if (count($anonList) >= $max) {
2018
                foreach ($anonList as $userToDelete) {
2019
                    UserManager::delete_user($userToDelete['user_id']);
2020
                    break;
2021
                }
2022
            }
2023
2024
            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...
2025
                $login,
2026
                'anon',
2027
                ANONYMOUS,
2028
                ' anonymous@localhost',
2029
                $login,
2030
                $login
2031
            );
2032
        } else {
2033
            $row = Database::fetch_assoc($result);
2034
2035
            return $row['id'];
2036
        }
2037
    }
2038
2039
    $table = Database::get_main_table(TABLE_MAIN_USER);
2040
    $sql = "SELECT id
2041
            FROM $table
2042
            WHERE status = ".ANONYMOUS." ";
2043
    $res = Database::query($sql);
2044
    if (Database::num_rows($res) > 0) {
2045
        $row = Database::fetch_assoc($res);
2046
2047
        return $row['id'];
2048
    }
2049
2050
    // No anonymous user was found.
2051
    return 0;
2052
}
2053
2054
/**
2055
 * @param int $courseId
2056
 * @param int $sessionId
2057
 * @param int $groupId
2058
 *
2059
 * @return string
2060
 */
2061
function api_get_cidreq_params($courseId, $sessionId = 0, $groupId = 0)
2062
{
2063
    $courseId = !empty($courseId) ? (int) $courseId : 0;
2064
    $sessionId = !empty($sessionId) ? (int) $sessionId : 0;
2065
    $groupId = !empty($groupId) ? (int) $groupId : 0;
2066
2067
    $url = 'cid='.$courseId;
2068
    $url .= '&sid='.$sessionId;
2069
    $url .= '&gid='.$groupId;
2070
2071
    return $url;
2072
}
2073
2074
/**
2075
 * Returns the current course url part including session, group, and gradebook params.
2076
 *
2077
 * @param bool   $addSessionId
2078
 * @param bool   $addGroupId
2079
 * @param string $origin
2080
 *
2081
 * @return string Course & session references to add to a URL
2082
 */
2083
function api_get_cidreq($addSessionId = true, $addGroupId = true, $origin = '')
2084
{
2085
    $courseId = api_get_course_int_id();
2086
    if (0 === $courseId && isset($_REQUEST['cid'])) {
2087
        $courseId = (int) $_REQUEST['cid'];
2088
    }
2089
    $url = empty($courseId) ? '' : 'cid='.$courseId;
2090
    $origin = empty($origin) ? api_get_origin() : Security::remove_XSS($origin);
2091
2092
    if ($addSessionId) {
2093
        if (!empty($url)) {
2094
            $sessionId = api_get_session_id();
2095
            if (0 === $sessionId && isset($_REQUEST['sid'])) {
2096
                $sessionId = (int) $_REQUEST['sid'];
2097
            }
2098
            $url .= 0 === $sessionId ? '&sid=0' : '&sid='.$sessionId;
2099
        }
2100
    }
2101
2102
    if ($addGroupId) {
2103
        if (!empty($url)) {
2104
            $url .= 0 == api_get_group_id() ? '&gid=0' : '&gid='.api_get_group_id();
2105
        }
2106
    }
2107
2108
    if (!empty($url)) {
2109
        $url .= '&gradebook='.(int) api_is_in_gradebook();
2110
        if (false !== $origin) {
2111
            $url .= '&origin=' . $origin;
2112
        }
2113
    }
2114
2115
    return $url;
2116
}
2117
2118
/**
2119
 * Get if we visited a gradebook page.
2120
 *
2121
 * @return bool
2122
 */
2123
function api_is_in_gradebook()
2124
{
2125
    return Session::read('in_gradebook', false);
2126
}
2127
2128
/**
2129
 * Set that we are in a page inside a gradebook.
2130
 */
2131
function api_set_in_gradebook()
2132
{
2133
    Session::write('in_gradebook', true);
2134
}
2135
2136
/**
2137
 * Remove gradebook session.
2138
 */
2139
function api_remove_in_gradebook()
2140
{
2141
    Session::erase('in_gradebook');
2142
}
2143
2144
/**
2145
 * Returns the current course info array see api_format_course_array()
2146
 * If the course_code is given, the returned array gives info about that
2147
 * particular course, if none given it gets the course info from the session.
2148
 *
2149
 * @param string $courseCode
2150
 *
2151
 * @return array
2152
 */
2153
function api_get_course_info($courseCode = null)
2154
{
2155
    if (!empty($courseCode)) {
2156
        $course = Container::getCourseRepository()->findOneByCode($courseCode);
2157
2158
        return api_format_course_array($course);
2159
    }
2160
2161
    $course = Session::read('_course');
2162
    if ('-1' == $course) {
2163
        $course = [];
2164
    }
2165
2166
    if (empty($course) && isset($_REQUEST['cid'])) {
2167
        $course = api_get_course_info_by_id((int) $_REQUEST['cid']);
2168
    }
2169
2170
    return $course;
2171
}
2172
2173
/**
2174
 * @param int $courseId
2175
 */
2176
function api_get_course_entity($courseId = 0): ?Course
2177
{
2178
    if (empty($courseId)) {
2179
        $courseId = api_get_course_int_id();
2180
    }
2181
2182
    if (empty($courseId)) {
2183
        return null;
2184
    }
2185
2186
    return Container::getCourseRepository()->find($courseId);
2187
}
2188
2189
/**
2190
 * @param int $id
2191
 */
2192
function api_get_session_entity($id = 0): ?SessionEntity
2193
{
2194
    if (empty($id)) {
2195
        $id = api_get_session_id();
2196
    }
2197
2198
    if (empty($id)) {
2199
        return null;
2200
    }
2201
2202
    return Container::getSessionRepository()->find($id);
2203
}
2204
2205
/**
2206
 * @param int $id
2207
 */
2208
function api_get_group_entity($id = 0): ?CGroup
2209
{
2210
    if (empty($id)) {
2211
        $id = api_get_group_id();
2212
    }
2213
2214
    return Container::getGroupRepository()->find($id);
2215
}
2216
2217
/**
2218
 * @param int $id
2219
 */
2220
function api_get_url_entity($id = 0): ?AccessUrl
2221
{
2222
    if (empty($id)) {
2223
        $id = api_get_current_access_url_id();
2224
    }
2225
2226
    return Container::getAccessUrlRepository()->find($id);
2227
}
2228
2229
/**
2230
 * Returns the current course info array.
2231
2232
 * Now if the course_code is given, the returned array gives info about that
2233
 * particular course, not specially the current one.
2234
 *
2235
 * @param int $id Numeric ID of the course
2236
 *
2237
 * @return array The course info as an array formatted by api_format_course_array, including category.title
2238
 */
2239
function api_get_course_info_by_id(?int $id = 0)
2240
{
2241
    if (empty($id)) {
2242
        $course = Session::read('_course', []);
2243
2244
        return $course;
2245
    }
2246
2247
    $course = Container::getCourseRepository()->find($id);
2248
    if (empty($course)) {
2249
        return [];
2250
    }
2251
2252
    return api_format_course_array($course);
2253
}
2254
2255
/**
2256
 * Reformat the course array (output by api_get_course_info()) in order, mostly,
2257
 * to switch from 'code' to 'id' in the array.
2258
 *
2259
 * @return array
2260
 *
2261
 * @todo eradicate the false "id"=code field of the $_course array and use the int id
2262
 */
2263
function api_format_course_array(Course $course = null)
2264
{
2265
    if (empty($course)) {
2266
        return [];
2267
    }
2268
2269
    $courseData = [];
2270
    $courseData['id'] = $courseData['real_id'] = $course->getId();
2271
2272
    // Added
2273
    $courseData['code'] = $courseData['sysCode'] = $course->getCode();
2274
    $courseData['name'] = $courseData['title'] = $course->getTitle(); // 'name' only used for backwards compatibility - should be removed in the long run
2275
    $courseData['official_code'] = $courseData['visual_code'] = $course->getVisualCode();
2276
    $courseData['creation_date'] = $course->getCreationDate()->format('Y-m-d H:i:s');
2277
    $courseData['titular'] = $course->getTutorName();
2278
    $courseData['language'] = $courseData['course_language'] = $course->getCourseLanguage();
2279
    $courseData['extLink']['url'] = $courseData['department_url'] = $course->getDepartmentUrl();
2280
    $courseData['extLink']['name'] = $courseData['department_name'] = $course->getDepartmentName();
2281
2282
    $courseData['visibility'] = $course->getVisibility();
2283
    $courseData['subscribe_allowed'] = $courseData['subscribe'] = $course->getSubscribe();
2284
    $courseData['unsubscribe'] = $course->getUnsubscribe();
2285
    $courseData['activate_legal'] = $course->getActivateLegal();
2286
    $courseData['legal'] = $course->getLegal();
2287
    $courseData['show_score'] = $course->getShowScore(); //used in the work tool
2288
    $courseData['video_url'] = $course->getVideoUrl();
2289
    $courseData['sticky'] = (int) $course->isSticky();
2290
2291
    $coursePath = '/course/';
2292
    $webCourseHome = $coursePath.$courseData['real_id'].'/home';
2293
2294
    // Course password
2295
    $courseData['registration_code'] = $course->getRegistrationCode();
2296
    $courseData['disk_quota'] = $course->getDiskQuota();
2297
    $courseData['course_public_url'] = $webCourseHome;
2298
    $courseData['about_url'] = $coursePath.$courseData['real_id'].'/about';
2299
    $courseData['add_teachers_to_sessions_courses'] = $course->isAddTeachersToSessionsCourses();
2300
2301
    $image = Display::getMdiIcon(
2302
        ObjectIcon::COURSE,
2303
        'ch-tool-icon',
2304
        null,
2305
        ICON_SIZE_BIG
2306
    );
2307
2308
    $illustration = Container::getIllustrationRepository()->getIllustrationUrl($course);
2309
    if (!empty($illustration)) {
2310
        $image = $illustration;
2311
    }
2312
2313
    $courseData['course_image'] = $image.'?filter=course_picture_small';
2314
    $courseData['course_image_large'] = $image.'?filter=course_picture_medium';
2315
2316
    if ('true' === api_get_setting('course.show_course_duration') && null !== $course->getDuration()) {
2317
        $courseData['duration'] = $course->getDuration();
2318
    }
2319
2320
    return $courseData;
2321
}
2322
2323
/**
2324
 * Returns a difficult to guess password.
2325
 */
2326
function api_generate_password(int $length = 8, $useRequirements = true): string
2327
{
2328
    if ($length < 2) {
2329
        $length = 2;
2330
    }
2331
2332
    $charactersLowerCase = 'abcdefghijkmnopqrstuvwxyz';
2333
    $charactersUpperCase = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
2334
    $charactersSpecials = '!@#$%^&*()_+-=[]{}|;:,.<>?';
2335
    $minNumbers = 2;
2336
    $length = $length - $minNumbers;
2337
    $minLowerCase = round($length / 2);
2338
    $minUpperCase = $length - $minLowerCase;
2339
    $minSpecials = 1; // Default minimum special characters
2340
2341
    $password = '';
2342
    $passwordRequirements = $useRequirements ? Security::getPasswordRequirements() : [];
2343
2344
    $factory = new RandomLib\Factory();
2345
    $generator = $factory->getGenerator(new SecurityLib\Strength(SecurityLib\Strength::MEDIUM));
2346
2347
    if (!empty($passwordRequirements)) {
2348
        $length = $passwordRequirements['min']['length'];
2349
        $minNumbers = $passwordRequirements['min']['numeric'];
2350
        $minLowerCase = $passwordRequirements['min']['lowercase'];
2351
        $minUpperCase = $passwordRequirements['min']['uppercase'];
2352
        $minSpecials = $passwordRequirements['min']['specials'];
2353
2354
        $rest = $length - $minNumbers - $minLowerCase - $minUpperCase - $minSpecials;
2355
        // Add the rest to fill the length requirement
2356
        if ($rest > 0) {
2357
            $password .= $generator->generateString($rest, $charactersLowerCase.$charactersUpperCase);
2358
        }
2359
    }
2360
2361
    // Min digits default 2
2362
    for ($i = 0; $i < $minNumbers; $i++) {
2363
        $password .= $generator->generateInt(2, 9);
2364
    }
2365
2366
    // Min lowercase
2367
    $password .= $generator->generateString($minLowerCase, $charactersLowerCase);
2368
2369
    // Min uppercase
2370
    $password .= $generator->generateString($minUpperCase, $charactersUpperCase);
2371
2372
    // Min special characters
2373
    $password .= $generator->generateString($minSpecials, $charactersSpecials);
2374
2375
    // Shuffle the password to ensure randomness
2376
    $password = str_shuffle($password);
2377
2378
    return $password;
2379
}
2380
2381
/**
2382
 * Checks a password to see wether it is OK to use.
2383
 *
2384
 * @param string $password
2385
 *
2386
 * @return bool if the password is acceptable, false otherwise
2387
 *              Notes about what a password "OK to use" is:
2388
 *              1. The password should be at least 5 characters long.
2389
 *              2. Only English letters (uppercase or lowercase, it doesn't matter) and digits are allowed.
2390
 *              3. The password should contain at least 3 letters.
2391
 *              4. It should contain at least 2 digits.
2392
 *              Settings will change if the configuration value is set: password_requirements
2393
 */
2394
function api_check_password($password)
2395
{
2396
    $passwordRequirements = Security::getPasswordRequirements();
2397
2398
    $minLength = $passwordRequirements['min']['length'];
2399
    $minNumbers = $passwordRequirements['min']['numeric'];
2400
    // Optional
2401
    $minLowerCase = $passwordRequirements['min']['lowercase'];
2402
    $minUpperCase = $passwordRequirements['min']['uppercase'];
2403
2404
    $minLetters = $minLowerCase + $minUpperCase;
2405
    $passwordLength = api_strlen($password);
2406
2407
    $conditions = [
2408
        'min_length' => $passwordLength >= $minLength,
2409
    ];
2410
2411
    $digits = 0;
2412
    $lowerCase = 0;
2413
    $upperCase = 0;
2414
2415
    for ($i = 0; $i < $passwordLength; $i++) {
2416
        $currentCharacterCode = api_ord(api_substr($password, $i, 1));
2417
        if ($currentCharacterCode >= 65 && $currentCharacterCode <= 90) {
2418
            $upperCase++;
2419
        }
2420
2421
        if ($currentCharacterCode >= 97 && $currentCharacterCode <= 122) {
2422
            $lowerCase++;
2423
        }
2424
        if ($currentCharacterCode >= 48 && $currentCharacterCode <= 57) {
2425
            $digits++;
2426
        }
2427
    }
2428
2429
    // Min number of digits
2430
    $conditions['min_numeric'] = $digits >= $minNumbers;
2431
2432
    if (!empty($minUpperCase)) {
2433
        // Uppercase
2434
        $conditions['min_uppercase'] = $upperCase >= $minUpperCase;
2435
    }
2436
2437
    if (!empty($minLowerCase)) {
2438
        // Lowercase
2439
        $conditions['min_lowercase'] = $upperCase >= $minLowerCase;
2440
    }
2441
2442
    // Min letters
2443
    $letters = $upperCase + $lowerCase;
2444
    $conditions['min_letters'] = $letters >= $minLetters;
2445
2446
    $isPasswordOk = true;
2447
    foreach ($conditions as $condition) {
2448
        if (false === $condition) {
2449
            $isPasswordOk = false;
2450
            break;
2451
        }
2452
    }
2453
2454
    if (false === $isPasswordOk) {
2455
        $output = get_lang('The new password does not match the minimum security requirements').'<br />';
2456
        $output .= Security::getPasswordRequirementsToString($conditions);
2457
2458
        Display::addFlash(Display::return_message($output, 'warning', false));
2459
    }
2460
2461
    return $isPasswordOk;
2462
}
2463
2464
/**
2465
 * Gets the current Chamilo (not PHP/cookie) session ID.
2466
 *
2467
 * @return int O if no active session, the session ID otherwise
2468
 */
2469
function api_get_session_id()
2470
{
2471
    return (int) Session::read('sid', 0);
2472
}
2473
2474
/**
2475
 * Gets the current Chamilo (not social network) group ID.
2476
 *
2477
 * @return int O if no active session, the session ID otherwise
2478
 */
2479
function api_get_group_id()
2480
{
2481
    return Session::read('gid', 0);
2482
}
2483
2484
/**
2485
 * Gets the current or given session name.
2486
 *
2487
 * @param   int     Session ID (optional)
2488
 *
2489
 * @return string The session name, or null if not found
2490
 */
2491
function api_get_session_name($session_id = 0)
2492
{
2493
    if (empty($session_id)) {
2494
        $session_id = api_get_session_id();
2495
        if (empty($session_id)) {
2496
            return null;
2497
        }
2498
    }
2499
    $t = Database::get_main_table(TABLE_MAIN_SESSION);
2500
    $s = "SELECT title FROM $t WHERE id = ".(int) $session_id;
2501
    $r = Database::query($s);
2502
    $c = Database::num_rows($r);
2503
    if ($c > 0) {
2504
        //technically, there can be only one, but anyway we take the first
2505
        $rec = Database::fetch_array($r);
2506
2507
        return $rec['title'];
2508
    }
2509
2510
    return null;
2511
}
2512
2513
/**
2514
 * Gets the session info by id.
2515
 *
2516
 * @param int $id Session ID
2517
 *
2518
 * @return array information of the session
2519
 */
2520
function api_get_session_info($id)
2521
{
2522
    return SessionManager::fetch($id);
2523
}
2524
2525
/**
2526
 * Gets the session visibility by session id.
2527
 *
2528
 * @deprecated Use Session::setAccessVisibilityByUser() instead.
2529
 *
2530
 * @param int  $session_id
2531
 * @param int  $courseId
2532
 * @param bool $ignore_visibility_for_admins
2533
 *
2534
 * @return int
2535
 *             0 = session still available,
2536
 *             SESSION_VISIBLE_READ_ONLY = 1,
2537
 *             SESSION_VISIBLE = 2,
2538
 *             SESSION_INVISIBLE = 3
2539
 */
2540
function api_get_session_visibility(
2541
    $session_id,
2542
    $courseId = null,
2543
    $ignore_visibility_for_admins = true,
2544
    $userId = 0
2545
) {
2546
    if (api_is_platform_admin()) {
2547
        if ($ignore_visibility_for_admins) {
2548
            return SESSION_AVAILABLE;
2549
        }
2550
    }
2551
    $userId = empty($userId) ? api_get_user_id() : (int) $userId;
2552
2553
    $now = time();
2554
    if (empty($session_id)) {
2555
        return 0; // Means that the session is still available.
2556
    }
2557
2558
    $session_id = (int) $session_id;
2559
    $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
2560
2561
    $result = Database::query("SELECT * FROM $tbl_session WHERE id = $session_id");
2562
2563
    if (Database::num_rows($result) <= 0) {
2564
        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

2564
        return /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE;
Loading history...
2565
    }
2566
2567
    $row = Database::fetch_assoc($result);
2568
    $visibility = $row['visibility'];
2569
2570
    // I don't care the session visibility.
2571
    if (empty($row['access_start_date']) && empty($row['access_end_date'])) {
2572
        // Session duration per student.
2573
        if (isset($row['duration']) && !empty($row['duration'])) {
2574
            $duration = $row['duration'] * 24 * 60 * 60;
2575
            $courseAccess = CourseManager::getFirstCourseAccessPerSessionAndUser($session_id, $userId);
2576
2577
            // If there is a session duration but there is no previous
2578
            // access by the user, then the session is still available
2579
            if (0 == count($courseAccess)) {
2580
                return SESSION_AVAILABLE;
2581
            }
2582
2583
            $currentTime = time();
2584
            $firstAccess = isset($courseAccess['login_course_date'])
2585
                ? api_strtotime($courseAccess['login_course_date'], 'UTC')
2586
                : 0;
2587
            $userDurationData = SessionManager::getUserSession($userId, $session_id);
2588
            $userDuration = isset($userDurationData['duration'])
2589
                ? (intval($userDurationData['duration']) * 24 * 60 * 60)
2590
                : 0;
2591
2592
            $totalDuration = $firstAccess + $duration + $userDuration;
2593
2594
            return $totalDuration > $currentTime ? SESSION_AVAILABLE : SESSION_VISIBLE_READ_ONLY;
2595
        }
2596
2597
        return SESSION_AVAILABLE;
2598
    }
2599
2600
    // If start date was set.
2601
    if (!empty($row['access_start_date'])) {
2602
        $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

2602
        $visibility = $now > api_strtotime($row['access_start_date'], 'UTC') ? SESSION_AVAILABLE : /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE;
Loading history...
2603
    } else {
2604
        // If there's no start date, assume it's available until the end date
2605
        $visibility = SESSION_AVAILABLE;
2606
    }
2607
2608
    // If the end date was set.
2609
    if (!empty($row['access_end_date'])) {
2610
        // Only if date_start said that it was ok
2611
        if (SESSION_AVAILABLE === $visibility) {
2612
            $visibility = $now < api_strtotime($row['access_end_date'], 'UTC')
2613
                ? SESSION_AVAILABLE // Date still available
2614
                : $row['visibility']; // Session ends
2615
        }
2616
    }
2617
2618
    // If I'm a coach the visibility can change in my favor depending in the coach dates.
2619
    $isCoach = api_is_coach($session_id, $courseId);
2620
2621
    if ($isCoach) {
2622
        // Test start date.
2623
        if (!empty($row['coach_access_start_date'])) {
2624
            $start = api_strtotime($row['coach_access_start_date'], 'UTC');
2625
            $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

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

3358
    $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...
3359
    $studentView = api_is_student_view_active();
3360
    $isAllowed = false;
3361
3362
    // If platform admin, allow unless student view is active
3363
    if (api_is_platform_admin($allowSessionAdminEdit)) {
3364
        if ($check_student_view && $studentView) {
3365
            $isAllowed = false;
3366
        } else {
3367
            return true;
3368
        }
3369
    }
3370
3371
    // Respect session course read-only mode from extra field
3372
    if ($sessionId && 'true' === api_get_setting('session.session_courses_read_only_mode')) {
3373
        $efv = new ExtraFieldValue('course');
3374
        $lock = $efv->get_values_by_handler_and_field_variable(
3375
            api_get_course_int_id(),
3376
            'session_courses_read_only_mode'
3377
        );
3378
        if (!empty($lock['value'])) {
3379
            return false;
3380
        }
3381
    }
3382
3383
    // ---------------------------------------------------------------------
3384
    // Explicit student subscription guard (no session context).
3385
    // If the user is subscribed as a learner in the course, do NOT grant edit
3386
    // rights even if "current course teacher" roles are polluted/persisted.
3387
    // ---------------------------------------------------------------------
3388
    $courseCode = api_get_course_id();
3389
    $inCourse = !empty($courseCode) && $courseCode != -1;
3390
3391
    if ($inCourse && empty($sessionId)) {
3392
        if (api_is_explicit_course_student(api_get_user_id(), api_get_course_int_id())) {
3393
            return false;
3394
        }
3395
    }
3396
3397
    $isCourseAdmin = api_is_course_admin();
3398
    $isCoach = api_is_coach(0, null, $check_student_view);
3399
3400
    if (!$isCourseAdmin && $tutor) {
3401
        $isCourseAdmin = api_is_course_tutor();
3402
    }
3403
3404
    if (!$isCourseAdmin && $coach) {
3405
        if (SESSION_VISIBLE_READ_ONLY == $sessionVisibility) {
3406
            $isCoach = false;
3407
        }
3408
        if ('true' === api_get_setting('allow_coach_to_edit_course_session')) {
3409
            $isCourseAdmin = $isCoach;
3410
        }
3411
    }
3412
3413
    if (!$isCourseAdmin && $session_coach) {
3414
        $isCourseAdmin = $isCoach;
3415
    }
3416
3417
    // Handle student view mode
3418
    if ('true' === api_get_setting('student_view_enabled')) {
3419
        if (!empty($sessionId)) {
3420
            if (SESSION_VISIBLE_READ_ONLY == $sessionVisibility) {
3421
                $isCoach = false;
3422
            }
3423
            if ('true' === api_get_setting('allow_coach_to_edit_course_session')) {
3424
                $isAllowed = $isCoach;
3425
            }
3426
3427
            if ($check_student_view) {
3428
                $isAllowed = $isAllowed && !$studentView;
3429
            }
3430
        } else {
3431
            $isAllowed = $isCourseAdmin;
3432
            if ($check_student_view) {
3433
                $isAllowed = $isCourseAdmin && !$studentView;
3434
            }
3435
        }
3436
3437
        if ($isAllowed) {
3438
            return true;
3439
        }
3440
    } else {
3441
        if ($isCourseAdmin) {
3442
            return true;
3443
        }
3444
    }
3445
3446
    // Final fallback: permission-based system (only if nothing before returned true)
3447
    $courseId = api_get_course_id();
3448
    $inCourse = !empty($courseId) && $courseId != -1;
3449
3450
    if (!$inCourse) {
3451
        $userRoles = api_get_user_roles();
3452
        $feature = api_detect_feature_context();
3453
        $permission = $feature.':edit';
3454
3455
        return api_get_permission($permission, $userRoles);
3456
    }
3457
3458
    return $isAllowed;
3459
}
3460
3461
/**
3462
 * UI/legacy safeguard: returns true if the user is explicitly subscribed as STUDENT
3463
 * in the current course (course_rel_user), regardless of Symfony/serialized roles.
3464
 *
3465
 * This is intentionally a low-level check to avoid polluted context roles.
3466
 */
3467
function api_is_explicit_course_student(?int $userId = null, ?int $courseIntId = null): bool
3468
{
3469
    $userId = $userId ?? api_get_user_id();
3470
    $courseIntId = $courseIntId ?? api_get_course_int_id();
3471
3472
    if (empty($userId) || empty($courseIntId)) {
3473
        return false;
3474
    }
3475
3476
    $studentStatus = defined('STUDENT') ? (int) STUDENT : 5;
3477
    $table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
3478
    $sql = "SELECT status
3479
            FROM $table
3480
            WHERE c_id = ".((int) $courseIntId)."
3481
              AND user_id = ".((int) $userId)."
3482
            LIMIT 1";
3483
3484
    $res = Database::query($sql);
3485
    $row = Database::fetch_array($res, 'ASSOC');
3486
3487
    return !empty($row) && (int) $row['status'] === $studentStatus;
3488
}
3489
3490
/**
3491
 * Returns the current main feature (module) based on the current script path.
3492
 * Used to determine permissions for non-course tools.
3493
 */
3494
function api_detect_feature_context(): string
3495
{
3496
    $script = $_SERVER['SCRIPT_NAME'] ?? '';
3497
    $script = basename($script);
3498
3499
    $map = [
3500
        'user_list.php' => 'user',
3501
        'user_add.php' => 'user',
3502
        'user_edit.php' => 'user',
3503
        'session_list.php' => 'session',
3504
        'session_add.php' => 'session',
3505
        'session_edit.php' => 'session',
3506
        'skill_list.php' => 'skill',
3507
        'skill_edit.php' => 'skill',
3508
        'badge_list.php' => 'badge',
3509
        'settings.php' => 'settings',
3510
        'course_list.php' => 'course',
3511
    ];
3512
3513
    if (isset($map[$script])) {
3514
        return $map[$script];
3515
    }
3516
3517
    if (preg_match('#/main/([a-z_]+)/#i', $_SERVER['SCRIPT_NAME'], $matches)) {
3518
        return $matches[1];
3519
    }
3520
3521
    return 'unknown';
3522
}
3523
3524
/**
3525
 * Returns true if user is a course coach of at least one course in session.
3526
 *
3527
 * @param int $sessionId
3528
 *
3529
 * @return bool
3530
 */
3531
function api_is_coach_of_course_in_session($sessionId)
3532
{
3533
    if (api_is_platform_admin()) {
3534
        return true;
3535
    }
3536
3537
    $userId = api_get_user_id();
3538
    $courseList = UserManager::get_courses_list_by_session(
3539
        $userId,
3540
        $sessionId
3541
    );
3542
3543
    // Session visibility.
3544
    $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

3544
    $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...
3545
        $sessionId,
3546
        null,
3547
        false
3548
    );
3549
3550
    if (SESSION_VISIBLE != $visibility && !empty($courseList)) {
3551
        // Course Coach session visibility.
3552
        $blockedCourseCount = 0;
3553
        $closedVisibilityList = [
3554
            COURSE_VISIBILITY_CLOSED,
3555
            COURSE_VISIBILITY_HIDDEN,
3556
        ];
3557
3558
        foreach ($courseList as $course) {
3559
            // Checking session visibility
3560
            $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

3560
            $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...
3561
                $sessionId,
3562
                $course['real_id']
3563
            );
3564
3565
            $courseIsVisible = !in_array(
3566
                $course['visibility'],
3567
                $closedVisibilityList
3568
            );
3569
            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

3569
            if (false === $courseIsVisible || /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE == $sessionCourseVisibility) {
Loading history...
3570
                $blockedCourseCount++;
3571
            }
3572
        }
3573
3574
        // If all courses are blocked then no show in the list.
3575
        if ($blockedCourseCount === count($courseList)) {
3576
            $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

3576
            $visibility = /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE;
Loading history...
3577
        } else {
3578
            $visibility = SESSION_VISIBLE;
3579
        }
3580
    }
3581
3582
    switch ($visibility) {
3583
        case SESSION_VISIBLE_READ_ONLY:
3584
        case SESSION_VISIBLE:
3585
        case SESSION_AVAILABLE:
3586
            return true;
3587
            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...
3588
        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

3588
        case /** @scrutinizer ignore-deprecated */ SESSION_INVISIBLE:
Loading history...
3589
            return false;
3590
    }
3591
3592
    return false;
3593
}
3594
3595
/**
3596
 * Checks if a student can edit contents in a session depending
3597
 * on the session visibility.
3598
 *
3599
 * @param bool $tutor Whether to check if the user has the tutor role
3600
 * @param bool $coach Whether to check if the user has the coach role
3601
 *
3602
 * @return bool true: the user has the rights to edit, false: he does not
3603
 */
3604
function api_is_allowed_to_session_edit($tutor = false, $coach = false)
3605
{
3606
    if (api_is_allowed_to_edit($tutor, $coach)) {
3607
        // If I'm a teacher, I will return true in order to not affect the normal behaviour of Chamilo tools.
3608
        return true;
3609
    } else {
3610
        $sessionId = api_get_session_id();
3611
3612
        if (0 == $sessionId) {
3613
            // I'm not in a session so i will return true to not affect the normal behaviour of Chamilo tools.
3614
            return true;
3615
        } else {
3616
            // I'm in a session and I'm a student
3617
            // Get the session visibility
3618
            $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

3618
            $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...
3619
            // if 5 the session is still available
3620
            switch ($session_visibility) {
3621
                case SESSION_VISIBLE_READ_ONLY: // 1
3622
                    return false;
3623
                case SESSION_VISIBLE:           // 2
3624
                    return true;
3625
                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

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

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
7612
        try {
7613
            $courseEntity = Container::getCourseRepository()->find($courseId);
7614
7615
            if ($courseEntity && $courseEntity->getResourceNode()) {
7616
                $resourceNodeParentId = (int) $courseEntity->getResourceNode()->getId();
7617
            }
7618
        } catch (\Throwable $exception) {
7619
            error_log('[Glossary] Failed to resolve resourceNodeParentId from course: '.$exception->getMessage());
7620
        }
7621
    }
7622
7623
    $course  = $courseId ?: 'null';
7624
    $session = $sessionId ?: 'null';
7625
    $parent  = $resourceNodeParentId ?: 'null';
7626
7627
    return '
7628
        <script>
7629
          window.chamiloGlossaryConfig = {
7630
            courseId: ' . $course . ',
7631
            sessionId: ' . $session . ',
7632
            resourceNodeParentId: ' . $parent . ',
7633
            termsEndpoint: "/api/glossaries"
7634
          };
7635
        </script>
7636
        ' . api_get_build_js("glossary_auto.js") . '
7637
    ';
7638
}
7639
7640
7641