Skip to content

Commit 659dbeb

Browse files
committed
Merge branch 'master' of github.com:chamilo/chamilo-lms
2 parents 9758041 + dd6b0ed commit 659dbeb

10 files changed

Lines changed: 503 additions & 94 deletions

File tree

assets/css/app.scss

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -644,9 +644,6 @@ form .field {
644644
td {
645645
padding: 12px;
646646
}
647-
input[type="checkbox"] {
648-
transform: scale(1.2);
649-
}
650647
.save-button {
651648
background-color: #4CAF50;
652649
color: white;
@@ -659,10 +656,6 @@ form .field {
659656
.save-button:hover {
660657
background-color: #45a049;
661658
}
662-
input[type="checkbox"] {
663-
transform: scale(1);
664-
margin: 0 auto;
665-
}
666659
}
667660

668661
.files-info-page {
@@ -1036,11 +1029,6 @@ img.course-tool__icon {
10361029
textarea {
10371030
@apply focus:outline-0 focus:border-primary focus:rounded-md focus:transition;
10381031
}
1039-
1040-
input[type="checkbox"],
1041-
input[type="radio"] {
1042-
@apply focus:outline-2 focus:outline-primary rounded-full;
1043-
}
10441032
}
10451033
@layer components {
10461034
.form-group:focus-within,

assets/css/scss/atoms/_checkbox.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,12 @@
8585
@apply text-body-2 w-5 h-5;
8686
}
8787
}
88+
89+
/* legacy checkboxes */
90+
.p-checkbox-box .p-checkbox-icon {
91+
@apply hidden;
92+
}
93+
94+
.p-checkbox-input:checked + .p-checkbox-box .p-checkbox-icon {
95+
@apply block;
96+
}

public/main/inc/lib/display.lib.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,28 @@ public static function input($type, $name, $value, $attributes = [])
873873
$attributes['value'] = $value;
874874
}
875875

876+
if ('checkbox' === $type) {
877+
$attributes['class'] ??= 'p-checkbox-input';
878+
}
879+
880+
$inputBase = self::tag('input', '', $attributes);
881+
882+
if ('checkbox' === $type) {
883+
$isChecked = isset($attributes['checked']) && 'checked' === $attributes['checked'];
884+
$compontentCheckedClass = $isChecked ? 'p-checkbox-checked' : '';
885+
886+
return <<<HTML
887+
<div class="p-checkbox p-component $compontentCheckedClass">
888+
$inputBase
889+
<div class="p-checkbox-box">
890+
<svg class="p-icon p-checkbox-icon" width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
891+
<path d="M4.86199 11.5948C4.78717 11.5923 4.71366 11.5745 4.64596 11.5426C4.57826 11.5107 4.51779 11.4652 4.46827 11.4091L0.753985 7.69483C0.683167 7.64891 0.623706 7.58751 0.580092 7.51525C0.536478 7.44299 0.509851 7.36177 0.502221 7.27771C0.49459 7.19366 0.506156 7.10897 0.536046 7.03004C0.565935 6.95111 0.613367 6.88 0.674759 6.82208C0.736151 6.76416 0.8099 6.72095 0.890436 6.69571C0.970973 6.67046 1.05619 6.66385 1.13966 6.67635C1.22313 6.68886 1.30266 6.72017 1.37226 6.76792C1.44186 6.81567 1.4997 6.8786 1.54141 6.95197L4.86199 10.2503L12.6397 2.49483C12.7444 2.42694 12.8689 2.39617 12.9932 2.40745C13.1174 2.41873 13.2343 2.47141 13.3251 2.55705C13.4159 2.64268 13.4753 2.75632 13.4938 2.87973C13.5123 3.00315 13.4888 3.1292 13.4271 3.23768L5.2557 11.4091C5.20618 11.4652 5.14571 11.5107 5.07801 11.5426C5.01031 11.5745 4.9368 11.5923 4.86199 11.5948Z" fill="currentColor"></path>
892+
</svg>
893+
</div>
894+
</div>
895+
HTML;
896+
}
897+
876898
if ('text' === $type) {
877899
$attributes['class'] = isset($attributes['class'])
878900
? $attributes['class'].' p-inputtext p-component '

public/main/inc/lib/internationalization.lib.php

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,18 +1020,19 @@ function api_to_system_encoding($string, $from_encoding = null, $check_utf8_vali
10201020
*
10211021
* @see http://php.net/manual/en/function.htmlentities
10221022
*/
1023-
function api_htmlentities($string, $quote_style = ENT_COMPAT)
1023+
function api_htmlentities($string, $quote_style = ENT_COMPAT): string
10241024
{
1025-
switch ($quote_style) {
1026-
case ENT_COMPAT:
1027-
$string = str_replace(['&', '"', '<', '>'], ['&amp;', '&quot;', '&lt;', '&gt;'], $string);
1028-
break;
1029-
case ENT_QUOTES:
1030-
$string = str_replace(['&', '\'', '"', '<', '>'], ['&amp;', '&#039;', '&quot;', '&lt;', '&gt;'], $string);
1031-
break;
1025+
$flags = ENT_HTML401;
1026+
1027+
if ($quote_style === ENT_QUOTES) {
1028+
$flags |= ENT_QUOTES;
1029+
} else {
1030+
$flags |= ENT_COMPAT;
10321031
}
10331032

1034-
return mb_convert_encoding($string, 'HTML-ENTITIES', 'UTF-8');
1033+
$flags |= ENT_SUBSTITUTE;
1034+
1035+
return htmlentities($string, $flags, 'UTF-8');
10351036
}
10361037

10371038
/**

public/main/inc/lib/pear/HTML/QuickForm/checkbox.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,12 @@ public function toHtml()
139139
$checkClass = $this->checkboxClass;
140140
$name = $this->_attributes['name'];
141141
$id = $this->getAttribute('id');
142-
$this->setAttribute(
143-
'onchange',
144-
'this.nextElementSibling.querySelector(\'.p-icon\').style.display = this.checked ? \'block\' : \'none\';'
145-
);
146142

147143
return '<div id="'.$name.'" class="'.$checkClass.'">
148-
<div class="p-checkbox p-component">
144+
<div class="p-checkbox p-component '.($this->getChecked() ? 'p-checkbox-checked' : '').'">
149145
'.parent::toHtml().'
150146
<div class="p-checkbox-box">
151-
<svg style="display: '.($this->getChecked() ? 'block:' : 'none').';" class="p-icon p-checkbox-icon" width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
147+
<svg class="p-icon p-checkbox-icon" width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
152148
<path d="M4.86199 11.5948C4.78717 11.5923 4.71366 11.5745 4.64596 11.5426C4.57826 11.5107 4.51779 11.4652 4.46827 11.4091L0.753985 7.69483C0.683167 7.64891 0.623706 7.58751 0.580092 7.51525C0.536478 7.44299 0.509851 7.36177 0.502221 7.27771C0.49459 7.19366 0.506156 7.10897 0.536046 7.03004C0.565935 6.95111 0.613367 6.88 0.674759 6.82208C0.736151 6.76416 0.8099 6.72095 0.890436 6.69571C0.970973 6.67046 1.05619 6.66385 1.13966 6.67635C1.22313 6.68886 1.30266 6.72017 1.37226 6.76792C1.44186 6.81567 1.4997 6.8786 1.54141 6.95197L4.86199 10.2503L12.6397 2.49483C12.7444 2.42694 12.8689 2.39617 12.9932 2.40745C13.1174 2.41873 13.2343 2.47141 13.3251 2.55705C13.4159 2.64268 13.4753 2.75632 13.4938 2.87973C13.5123 3.00315 13.4888 3.1292 13.4271 3.23768L5.2557 11.4091C5.20618 11.4652 5.14571 11.5107 5.07801 11.5426C5.01031 11.5745 4.9368 11.5923 4.86199 11.5948Z" fill="currentColor"></path>
153149
</svg>
154150
</div>

public/main/inc/lib/sortable_table.class.php

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,24 +1053,23 @@ public function filter_data($row)
10531053
{
10541054
$url_params = $this->get_sortable_table_param_string().'&'.$this->get_additional_url_paramstring();
10551055
foreach ($this->column_filters as $column => &$function) {
1056-
$firstParam = isset($row[$column]) ? $row[$column] : 0;
1056+
$firstParam = $row[$column] ?? 0;
10571057
$row[$column] = call_user_func($function, $firstParam, $url_params, $row);
10581058
}
1059-
if (count($this->form_actions) > 0) {
1060-
if (strlen($row[0]) > 0) {
1061-
$row[0] = '<div class="checkbox" ><label><input type="checkbox" name="'.$this->checkbox_name.'[]" value="'.$row[0].'"';
1062-
if (isset($_GET[$this->param_prefix.'selectall'])) {
1063-
$row[0] .= ' checked="checked"';
1064-
}
1065-
$row[0] .= '/><span class="checkbox-material"><span class="check"></span></span></label></div>';
1059+
if (count($this->form_actions) > 0 && strlen($row[0]) > 0) {
1060+
$checkboxParams = [];
1061+
1062+
if (isset($_GET[$this->param_prefix.'selectall'])) {
1063+
$checkboxParams['checked'] = 'checked';
10661064
}
1065+
1066+
$row[0] = Display::input('checkbox', $this->checkbox_name.'[]', $row[0], $checkboxParams);
10671067
}
10681068
if (is_array($row)) {
1069-
foreach ($row as &$value) {
1070-
if (empty($value)) {
1071-
$value = '-';
1072-
}
1073-
}
1069+
$row = array_map(
1070+
fn($value) => empty($value) ? '-' : $value,
1071+
$row
1072+
);
10741073
}
10751074

10761075
return $row;

public/service-worker.js

Lines changed: 34 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,38 @@
11
/**
22
* Chamilo Service Worker
33
* Handles:
4-
* - Offline caching
54
* - Push notifications
5+
* - (Optional) Network fallback response when offline
66
*
77
* Notes:
8-
* - We avoid caching or intercepting dynamic endpoints (/api, /themes) to prevent noisy console logs
8+
* - We avoid intercepting dynamic endpoints (/api, /themes) to prevent noisy console logs
99
* when responses are 404 (expected in strict checks) or 202 (async endpoints).
10-
* - We only cache successful GET responses (res.ok).
10+
* - We do NOT store responses. This prevents stale navigation/session issues.
1111
*/
1212

13-
const CACHE_NAME = "chamilo-cache-v1"
1413
const PRECACHE_URLS = [
1514
"/",
1615
"/manifest.json",
1716
"/img/pwa-icons/icon-192.png",
1817
"/img/pwa-icons/icon-512.png",
1918
]
2019

21-
// PWA: Cache basic files on install
20+
// PWA: Keep install/activate flow (no persistent storage)
2221
self.addEventListener("install", (event) => {
2322
console.log("[Service Worker] Install event")
2423

24+
// Validate basic URLs at install time to fail fast if something is missing.
25+
// We intentionally do not store anything.
2526
event.waitUntil(
26-
caches.open(CACHE_NAME).then((cache) => {
27-
return cache.addAll(PRECACHE_URLS)
28-
})
27+
Promise.all(
28+
PRECACHE_URLS.map(async (url) => {
29+
try {
30+
await fetch(url, { cache: "no-store" })
31+
} catch (e) {
32+
// Best-effort: do not block SW install if a non-critical asset fails
33+
}
34+
})
35+
)
2936
)
3037

3138
// Activate updated SW ASAP
@@ -35,22 +42,7 @@ self.addEventListener("install", (event) => {
3542
self.addEventListener("activate", (event) => {
3643
console.log("[Service Worker] Activate event")
3744

38-
event.waitUntil(
39-
(async () => {
40-
// Cleanup old caches if cache name changes in the future
41-
const keys = await caches.keys()
42-
await Promise.all(
43-
keys.map((key) => {
44-
if (key !== CACHE_NAME) {
45-
return caches.delete(key)
46-
}
47-
return Promise.resolve()
48-
})
49-
)
50-
51-
await self.clients.claim()
52-
})()
53-
)
45+
event.waitUntil(self.clients.claim())
5446
})
5547

5648
/**
@@ -60,28 +52,32 @@ self.addEventListener("activate", (event) => {
6052
function shouldBypass(request) {
6153
const url = new URL(request.url)
6254

55+
// Do not handle non-GET requests
56+
if (request.method !== "GET") return true
57+
6358
// Ignore internal redirect helper routes
6459
if (url.pathname.startsWith("/r/")) return true
6560

66-
// Avoid intercepting theme assets (prevents console noise for strict 404 checks)
61+
// Avoid intercepting theme assets/endpoints
6762
if (url.pathname.startsWith("/themes/")) return true
6863

69-
// Avoid intercepting API calls (can return 202 or other statuses that shouldn't be cached)
64+
// Avoid intercepting API calls
7065
if (url.pathname.startsWith("/api/")) return true
7166

72-
// Avoid strict probing calls anywhere (expected 404 should not produce SW noise)
67+
// Avoid strict probing calls anywhere
7368
if (url.searchParams.has("strict")) return true
7469

75-
// Do not handle non-GET requests
76-
if (request.method !== "GET") return true
70+
// Never intercept HTML navigations/documents (prevents any navigation impact)
71+
if (request.mode === "navigate" || request.destination === "document") return true
72+
const accept = request.headers.get("accept") || ""
73+
if (accept.includes("text/html")) return true
7774

7875
return false
7976
}
8077

8178
/**
82-
* Cache-first for static navigations/assets, network fallback.
83-
* - Only caches successful responses (res.ok).
84-
* - If network fails, returns cache if available.
79+
* Network-first (no storage), with graceful offline responses.
80+
* This avoids stale resources while still providing a friendly offline fallback.
8581
*/
8682
self.addEventListener("fetch", (event) => {
8783
const request = event.request
@@ -94,37 +90,21 @@ self.addEventListener("fetch", (event) => {
9490
event.respondWith(
9591
(async () => {
9692
try {
97-
const cachedResponse = await caches.match(request)
98-
if (cachedResponse) {
99-
return cachedResponse
100-
}
101-
102-
const response = await fetch(request)
103-
104-
// Cache only successful responses
105-
if (response && response.ok) {
106-
const cache = await caches.open(CACHE_NAME)
107-
await cache.put(request, response.clone())
108-
}
109-
110-
return response
93+
// Always go to network (no SW storage)
94+
return await fetch(request)
11195
} catch (err) {
112-
// Network failure: try cache
113-
const cachedResponse = await caches.match(request)
114-
if (cachedResponse) {
115-
return cachedResponse
116-
}
117-
118-
// As last resort, provide a friendly offline response for navigations
96+
// Offline fallback
11997
const accept = request.headers.get("accept") || ""
98+
99+
// If someone requested HTML (rare here because we bypass HTML), return a friendly page anyway
120100
if (accept.includes("text/html")) {
121101
return new Response(
122102
"<h1>Offline</h1><p>The application is currently offline.</p>",
123103
{ headers: { "Content-Type": "text/html; charset=utf-8" }, status: 503 }
124104
)
125105
}
126106

127-
// For other assets, just fail gracefully
107+
// For assets, fail gracefully
128108
return new Response("Offline", { status: 503, statusText: "Service Unavailable" })
129109
}
130110
})()

0 commit comments

Comments
 (0)