|
1 | 1 | <?php |
| 2 | + |
| 3 | +declare(strict_types=1); |
2 | 4 | /* For licensing terms, see /license.txt */ |
3 | 5 |
|
4 | 6 | use Chamilo\CoreBundle\Entity\Asset; |
|
11 | 13 | api_protect_course_script(); |
12 | 14 |
|
13 | 15 | $allow = api_is_allowed_to_edit(null, true); |
14 | | -$lpId = !empty($_GET['lp_id']) ? (int) $_GET['lp_id'] : 0; |
| 16 | +$lpId = !empty($_GET['lp_id']) ? (int) $_GET['lp_id'] : 0; |
15 | 17 |
|
16 | 18 | if (!$allow || $lpId <= 0) { |
17 | 19 | api_not_allowed(true); |
18 | 20 | } |
19 | 21 |
|
20 | 22 | $courseInfo = api_get_course_info(); |
21 | | -$userId = api_get_user_id(); |
| 23 | +$userId = api_get_user_id(); |
22 | 24 |
|
23 | 25 | // Prevent open redirects: allow only absolute URLs under WEB_PATH or relative paths. |
24 | 26 | $sanitizeReturnTo = static function (?string $url): string { |
25 | 27 | $url = trim((string) $url); |
26 | | - if ($url === '') { |
| 28 | + if ('' === $url) { |
27 | 29 | return ''; |
28 | 30 | } |
29 | 31 |
|
30 | 32 | // Allow relative internal paths. |
31 | | - if (0 === strpos($url, '/')) { |
| 33 | + if (str_starts_with($url, '/')) { |
32 | 34 | return $url; |
33 | 35 | } |
34 | 36 |
|
35 | 37 | // Allow absolute URLs that start with the platform WEB_PATH. |
36 | 38 | $webPath = api_get_path(WEB_PATH); |
37 | | - if ($webPath && 0 === strpos($url, $webPath)) { |
| 39 | + if ($webPath && str_starts_with($url, $webPath)) { |
38 | 40 | return $url; |
39 | 41 | } |
40 | 42 |
|
41 | 43 | return ''; |
42 | 44 | }; |
43 | 45 |
|
44 | 46 | $returnToRaw = $_GET['returnTo'] ?? ''; |
45 | | -$returnTo = $sanitizeReturnTo($returnToRaw); |
| 47 | +$returnTo = $sanitizeReturnTo($returnToRaw); |
46 | 48 |
|
47 | 49 | $lpRepo = Container::getLpRepository(); |
| 50 | + |
48 | 51 | /** @var CLp|null $lpEntity */ |
49 | 52 | $lpEntity = $lpRepo->find($lpId); |
50 | 53 |
|
51 | | -if (!$lpEntity || (int) $lpEntity->getLpType() !== CLp::SCORM_TYPE) { |
| 54 | +if (!$lpEntity || CLp::SCORM_TYPE !== (int) $lpEntity->getLpType()) { |
52 | 55 | Display::addFlash(Display::return_message(get_lang('No learning path found'), 'error')); |
53 | 56 | api_not_allowed(true); |
54 | 57 | } |
55 | 58 |
|
56 | 59 | // Breadcrumbs. |
57 | 60 | $interbreadcrumb[] = [ |
58 | | - 'url' => 'lp_controller.php?action=list&'.api_get_cidreq(), |
| 61 | + 'url' => 'lp_controller.php?action=list&'.api_get_cidreq(), |
59 | 62 | 'name' => get_lang('Learning paths'), |
60 | 63 | ]; |
61 | 64 | $interbreadcrumb[] = [ |
62 | | - 'url' => api_get_self().'?'.api_get_cidreq().'&lp_id='.$lpId, |
| 65 | + 'url' => api_get_self().'?'.api_get_cidreq().'&lp_id='.$lpId, |
63 | 66 | 'name' => Security::remove_XSS((string) $lpEntity->getTitle()), |
64 | 67 | ]; |
65 | 68 |
|
|
69 | 72 | api_get_self().'?'.api_get_cidreq().'&lp_id='.$lpId.($returnTo ? '&returnTo='.urlencode($returnTo) : ''), |
70 | 73 | '', |
71 | 74 | [ |
72 | | - 'id' => 'upload_form', |
| 75 | + 'id' => 'upload_form', |
73 | 76 | 'enctype' => 'multipart/form-data', |
74 | 77 | ] |
75 | 78 | ); |
|
90 | 93 | /** |
91 | 94 | * Map PHP upload error codes to readable messages. |
92 | 95 | */ |
93 | | -$uploadErrorToMessage = static function (int $code): string { |
94 | | - // Messages in English as requested. |
| 96 | +$uploadErrorToMessageKey = static function (int $code): string { |
95 | 97 | return match ($code) { |
96 | | - UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds upload_max_filesize.', |
97 | | - UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive.', |
98 | | - UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded.', |
99 | | - UPLOAD_ERR_NO_FILE => 'No file was uploaded.', |
100 | | - UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder on the server.', |
101 | | - UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.', |
102 | | - UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload.', |
103 | | - default => 'Unknown upload error.', |
| 98 | + \UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds upload_max_filesize.', |
| 99 | + \UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive.', |
| 100 | + \UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded.', |
| 101 | + \UPLOAD_ERR_NO_FILE => 'No file was uploaded.', |
| 102 | + \UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder on the server.', |
| 103 | + \UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.', |
| 104 | + \UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload.', |
| 105 | + default => 'Unknown upload error.', |
104 | 106 | }; |
105 | 107 | }; |
106 | 108 |
|
107 | 109 | $isPost = ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST'; |
108 | 110 |
|
109 | | -// detect the classic "post_max_size exceeded" case. |
| 111 | +// Detect the classic "post_max_size exceeded" case. |
110 | 112 | // When that happens, PHP gives you empty $_POST and empty $_FILES. |
111 | 113 | if ($isPost && empty($_POST) && empty($_FILES) && !empty($_SERVER['CONTENT_LENGTH'])) { |
112 | 114 | $cl = (int) $_SERVER['CONTENT_LENGTH']; |
|
116 | 118 |
|
117 | 119 | Display::addFlash( |
118 | 120 | Display::return_message( |
119 | | - 'Upload failed. The file is likely larger than the server limit (post_max_size).', |
| 121 | + get_lang('Upload failed. The file is likely larger than the server limit (post_max_size).'), |
120 | 122 | 'error' |
121 | 123 | ) |
122 | 124 | ); |
|
127 | 129 |
|
128 | 130 | if (!is_array($file)) { |
129 | 131 | error_log("lp_update_scorm.php - No user_file found in \$_FILES. lp_id={$lpId}, user_id={$userId}"); |
130 | | - Display::addFlash(Display::return_message('Update failed. No file received by the server.', 'error')); |
| 132 | + Display::addFlash(Display::return_message(get_lang('Update failed. No file received by the server.'), 'error')); |
131 | 133 | } else { |
132 | | - $fileErr = (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE); |
133 | | - if ($fileErr !== UPLOAD_ERR_OK) { |
134 | | - $msg = $uploadErrorToMessage($fileErr); |
135 | | - error_log("lp_update_scorm.php - Upload error: {$msg} (code={$fileErr}) lp_id={$lpId}, user_id={$userId}"); |
136 | | - Display::addFlash(Display::return_message('Update failed. '.$msg, 'error')); |
| 134 | + $fileErr = (int) ($file['error'] ?? \UPLOAD_ERR_NO_FILE); |
| 135 | + if (\UPLOAD_ERR_OK !== $fileErr) { |
| 136 | + $msgKey = $uploadErrorToMessageKey($fileErr); |
| 137 | + $msgUi = get_lang($msgKey); |
| 138 | + |
| 139 | + error_log("lp_update_scorm.php - Upload error: {$msgKey} (code={$fileErr}) lp_id={$lpId}, user_id={$userId}"); |
| 140 | + |
| 141 | + Display::addFlash( |
| 142 | + Display::return_message( |
| 143 | + sprintf(get_lang('Update failed. %s'), $msgUi), |
| 144 | + 'error' |
| 145 | + ) |
| 146 | + ); |
137 | 147 | } else { |
138 | 148 | $uploadedName = (string) ($file['name'] ?? ''); |
139 | | - $tmpPath = (string) ($file['tmp_name'] ?? ''); |
140 | | - $size = (int) ($file['size'] ?? 0); |
| 149 | + $tmpPath = (string) ($file['tmp_name'] ?? ''); |
| 150 | + $size = (int) ($file['size'] ?? 0); |
141 | 151 |
|
142 | | - if ($tmpPath === '' || !is_uploaded_file($tmpPath) || $size <= 0) { |
| 152 | + if ('' === $tmpPath || !is_uploaded_file($tmpPath) || $size <= 0) { |
143 | 153 | error_log("lp_update_scorm.php - Invalid upload payload. tmp='{$tmpPath}', size={$size} lp_id={$lpId}, user_id={$userId}"); |
144 | | - Display::addFlash(Display::return_message('Update failed. Invalid uploaded file payload.', 'error')); |
| 154 | + Display::addFlash(Display::return_message(get_lang('Update failed. Invalid uploaded file payload.'), 'error')); |
145 | 155 | } else { |
146 | 156 | // Enforce same base name rule to ensure replacement targets the same folder. |
147 | | - $expectedPath = trim((string) $lpEntity->getPath()); |
148 | | - $expectedFirstDir = $expectedPath !== '' ? trim((string) strtok($expectedPath, '/')) : ''; |
| 157 | + $expectedPath = trim((string) $lpEntity->getPath()); |
| 158 | + $expectedFirstDir = '' !== $expectedPath ? trim((string) strtok($expectedPath, '/')) : ''; |
149 | 159 |
|
150 | 160 | $pi = pathinfo($uploadedName); |
151 | 161 | $uploadedBase = trim((string) ($pi['filename'] ?? '')); |
152 | 162 |
|
153 | | - if ($expectedFirstDir !== '' && $uploadedBase !== '') { |
| 163 | + if ('' !== $expectedFirstDir && '' !== $uploadedBase) { |
154 | 164 | $expectedSafe = api_replace_dangerous_char($expectedFirstDir); |
155 | 165 | $uploadedSafe = api_replace_dangerous_char($uploadedBase); |
156 | 166 |
|
157 | | - if ($expectedSafe !== '' && $uploadedSafe !== '' && $expectedSafe !== $uploadedSafe) { |
| 167 | + if ('' !== $expectedSafe && '' !== $uploadedSafe && $expectedSafe !== $uploadedSafe) { |
158 | 168 | error_log( |
159 | 169 | "lp_update_scorm.php - Update rejected: zip base name mismatch. expected='{$expectedSafe}', got='{$uploadedSafe}' ". |
160 | 170 | "(lp_id={$lpId}, user_id={$userId})" |
161 | 171 | ); |
162 | 172 |
|
163 | 173 | Display::addFlash( |
164 | 174 | Display::return_message( |
165 | | - 'Update failed. The uploaded ZIP file name must match the original SCORM package name.', |
| 175 | + get_lang('Update failed. The uploaded ZIP file name must match the original SCORM package name.'), |
166 | 176 | 'error' |
167 | 177 | ) |
168 | 178 | ); |
|
172 | 182 | $tpl = new Template(null); |
173 | 183 | $tpl->assign('content', $content); |
174 | 184 | $tpl->display_one_col_template(); |
| 185 | + |
175 | 186 | exit; |
176 | 187 | } |
177 | 188 | } |
|
187 | 198 |
|
188 | 199 | error_log( |
189 | 200 | "lp_update_scorm.php - Starting SCORM replace. lp_id={$lpId}, user_id={$userId}, file='{$uploadedName}', size={$size}, old_asset_id=". |
190 | | - ($oldAssetId !== null ? $oldAssetId : 'null') |
| 201 | + (null !== $oldAssetId ? $oldAssetId : 'null') |
191 | 202 | ); |
192 | 203 |
|
193 | | - // Replace mode |
| 204 | + // Replace mode. |
194 | 205 | $oScorm = new scorm($lpEntity, $courseInfo, $userId); |
195 | 206 |
|
196 | 207 | try { |
|
218 | 229 |
|
219 | 230 | $newAssetId = method_exists($oScorm->asset, 'getId') ? (string) $oScorm->asset->getId() : null; |
220 | 231 | error_log( |
221 | | - "lp_update_scorm.php - Replace succeeded. LP asset linked. new_asset_id=". |
| 232 | + 'lp_update_scorm.php - Replace succeeded. LP asset linked. new_asset_id='. |
222 | 233 | (is_string($newAssetId) ? $newAssetId : 'null') |
223 | 234 | ); |
224 | 235 | } else { |
|
230 | 241 |
|
231 | 242 | Display::addFlash(Display::return_message(get_lang('Update successful'))); |
232 | 243 |
|
233 | | - if ($returnTo !== '') { |
| 244 | + if ('' !== $returnTo) { |
234 | 245 | header('Location: '.$returnTo); |
| 246 | + |
235 | 247 | exit; |
236 | 248 | } |
237 | 249 |
|
238 | 250 | header('Location: lp_controller.php?action=list&'.api_get_cidreq()); |
| 251 | + |
239 | 252 | exit; |
240 | 253 | } |
241 | 254 |
|
|
0 commit comments