Skip to content

Commit 0d51e00

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into 4.8
2 parents c9dcbd2 + 5a24c0c commit 0d51e00

8 files changed

Lines changed: 208 additions & 36 deletions

File tree

system/Autoloader/FileLocator.php

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -330,19 +330,7 @@ public function listFiles(string $path): array
330330
helper('filesystem');
331331

332332
foreach ($this->getNamespaces() as $namespace) {
333-
$fullPath = $namespace['path'] . $path;
334-
$resolvedPath = realpath($fullPath);
335-
$fullPath = $resolvedPath !== false ? $resolvedPath : $fullPath;
336-
337-
if (! is_dir($fullPath)) {
338-
continue;
339-
}
340-
341-
$tempFiles = get_filenames($fullPath, true, false, false);
342-
343-
if ($tempFiles !== []) {
344-
$files = array_merge($files, $tempFiles);
345-
}
333+
$files = array_merge($files, get_filenames($namespace['path'] . $path, true, false, false));
346334
}
347335

348336
return $files;

system/CodeIgniter.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,29 @@ public function resetForWorkerMode(): void
194194
// Reset timing
195195
$this->startTime = null;
196196
$this->totalTime = 0;
197+
198+
$this->resetKintForWorkerMode();
199+
}
200+
201+
/**
202+
* Resets Kint request-specific state for worker mode.
203+
*/
204+
private function resetKintForWorkerMode(): void
205+
{
206+
if (! CI_DEBUG || ! class_exists(Kint::class, false)) {
207+
return;
208+
}
209+
210+
$csp = service('csp');
211+
if ($csp->enabled()) {
212+
RichRenderer::$js_nonce = $csp->getScriptNonce();
213+
RichRenderer::$css_nonce = $csp->getStyleNonce();
214+
} else {
215+
RichRenderer::$js_nonce = null;
216+
RichRenderer::$css_nonce = null;
217+
}
218+
219+
RichRenderer::$needs_pre_render = true;
197220
}
198221

199222
/**

tests/system/CodeIgniterTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use Config\Filters as FiltersConfig;
3131
use Config\Modules;
3232
use Config\Routing;
33+
use Kint\Renderer\RichRenderer;
3334
use PHPUnit\Framework\Attributes\BackupGlobals;
3435
use PHPUnit\Framework\Attributes\DataProvider;
3536
use PHPUnit\Framework\Attributes\Group;
@@ -1270,6 +1271,15 @@ public function testRouteAttributesDisabledInConfig(): void
12701271

12711272
public function testResetForWorkerMode(): void
12721273
{
1274+
$this->resetServices();
1275+
1276+
$appConfig = config(App::class);
1277+
$appConfig->CSPEnabled = true;
1278+
1279+
RichRenderer::$js_nonce = 'stale-script-nonce';
1280+
RichRenderer::$css_nonce = 'stale-style-nonce';
1281+
RichRenderer::$needs_pre_render = false;
1282+
12731283
$config = new App();
12741284
$codeigniter = new MockCodeIgniter($config);
12751285

@@ -1289,5 +1299,11 @@ public function testResetForWorkerMode(): void
12891299
$this->assertNull($this->getPrivateProperty($codeigniter, 'controller'));
12901300
$this->assertNull($this->getPrivateProperty($codeigniter, 'method'));
12911301
$this->assertNull($this->getPrivateProperty($codeigniter, 'output'));
1302+
1303+
$csp = service('csp');
1304+
1305+
$this->assertSame($csp->getScriptNonce(), RichRenderer::$js_nonce);
1306+
$this->assertSame($csp->getStyleNonce(), RichRenderer::$css_nonce);
1307+
$this->assertTrue(RichRenderer::$needs_pre_render);
12921308
}
12931309
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Version switcher for the user guide sidebar.
3+
*
4+
* Injects the sphinx_rtd_theme's native ".switch-menus > .version-switch"
5+
* scaffolding above the search box in the left sidebar, containing a <select>
6+
* so readers can jump between:
7+
* - stable: https://codeigniter.com/user_guide/
8+
* - latest (in-progress): https://codeigniter4.github.io/userguide/
9+
* - local build: file:// or localhost, etc. (only shown when not on a deployed host)
10+
*/
11+
(() => {
12+
const VERSIONS = [
13+
{
14+
slug: 'stable',
15+
label: 'Stable',
16+
host: 'codeigniter.com',
17+
pathPrefix: '/user_guide/',
18+
},
19+
{
20+
slug: 'latest (in-progress)',
21+
label: 'Latest (in-progress)',
22+
host: 'codeigniter4.github.io',
23+
pathPrefix: '/userguide/',
24+
},
25+
];
26+
27+
const LOCAL = Object.freeze({
28+
slug: 'local build',
29+
label: 'Local build',
30+
host: null,
31+
pathPrefix: null,
32+
});
33+
34+
const detect_current = () =>
35+
VERSIONS.find((version) => version.host === window.location.hostname) ?? LOCAL;
36+
37+
/*
38+
* Derive the absolute URL of the documentation root by inspecting the
39+
* <script> tag Sphinx always injects for documentation_options.js. Its
40+
* resolved .src is absolute, so we can back out the directory that
41+
* contains _static/, which is the doc root. This works identically for
42+
* http(s), file://, and localhost.
43+
*/
44+
const doc_root_url = () => {
45+
const opts = document.querySelector('script[src*="documentation_options.js"]');
46+
47+
if (!opts) {
48+
return null;
49+
}
50+
51+
const idx = opts.src.lastIndexOf('/_static/');
52+
53+
return idx < 0 ? null : opts.src.slice(0, idx + 1);
54+
};
55+
56+
const current_page_path = () => {
57+
const root = doc_root_url();
58+
59+
if (!root) {
60+
return '';
61+
}
62+
63+
const here = window.location.href.split('#')[0].split('?')[0];
64+
65+
return here.startsWith(root) ? here.slice(root.length) : '';
66+
};
67+
68+
const url_for = ({ host, pathPrefix }) =>
69+
`https://${host}${pathPrefix}${current_page_path()}${window.location.hash}`;
70+
71+
const build = () => {
72+
const searchArea = document.querySelector('.wy-side-nav-search');
73+
const searchForm = searchArea?.querySelector('[role="search"]');
74+
75+
if (!searchArea || !searchForm) {
76+
return;
77+
}
78+
79+
if (searchArea.querySelector('.switch-menus')) {
80+
return;
81+
}
82+
83+
const current = detect_current();
84+
const entries = current === LOCAL ? [LOCAL, ...VERSIONS] : VERSIONS;
85+
86+
const select = document.createElement('select');
87+
select.setAttribute('aria-label', 'Select documentation version');
88+
89+
for (const entry of entries) {
90+
const option = document.createElement('option');
91+
option.value = entry.host ? url_for(entry) : '';
92+
option.textContent = entry.label;
93+
option.selected = entry.slug === current.slug;
94+
select.append(option);
95+
}
96+
97+
select.addEventListener('change', () => {
98+
if (select.value) {
99+
window.location.href = select.value;
100+
}
101+
});
102+
103+
const versionSwitch = document.createElement('div');
104+
versionSwitch.className = 'version-switch';
105+
versionSwitch.append(select);
106+
107+
const switchMenus = document.createElement('div');
108+
switchMenus.className = 'switch-menus';
109+
switchMenus.append(versionSwitch);
110+
111+
searchArea.insertBefore(switchMenus, searchForm);
112+
};
113+
114+
if (document.readyState === 'loading') {
115+
document.addEventListener('DOMContentLoaded', build, { once: true });
116+
} else {
117+
build();
118+
}
119+
})();

user_guide_src/source/changelogs/v4.7.2.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ Bugs Fixed
1515
**********
1616

1717
- **Security:** Fixed a bug where the CSRF filter could corrupt JSON request bodies after successful
18-
verification when the CSRF token was provided via the ``X-CSRF-TOKEN`` header.
19-
This caused ``IncomingRequest::getJSON()`` to fail on valid ``application/json`` requests.
18+
verification when the CSRF token was provided via the ``X-CSRF-TOKEN`` header.
19+
This caused ``IncomingRequest::getJSON()`` to fail on valid ``application/json`` requests.
2020

2121
See the repo's
2222
`CHANGELOG.md <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>`_

user_guide_src/source/changelogs/v4.7.3.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ Changes
2525
*******
2626

2727
- **Commands:** The ``-h`` option for the ``routes`` command is renamed to ``--sort-by-handler`` to avoid conflict with the common use of ``-h`` as a shortcut for ``--help``.
28-
The old ``-h`` option will continue to work until v4.8.0, at which point it will be removed and repurposed as a shortcut for ``--help``.
29-
A warning message is displayed when using the old ``-h`` option to encourage users to switch to the new ``--sort-by-handler`` option.
28+
The old ``-h`` option will continue to work until v4.8.0, at which point it will be removed and repurposed as a shortcut for ``--help``.
29+
A warning message is displayed when using the old ``-h`` option to encourage users to switch to the new ``--sort-by-handler`` option.
3030

3131
************
3232
Deprecations
@@ -40,6 +40,7 @@ Bugs Fixed
4040
- **CLI:** Fixed a bug where ``CLI::generateDimensions()`` leaked ``stty`` error output (e.g., ``stty: 'standard input': Inappropriate ioctl for device``) to stderr when stdin was not a TTY.
4141
- **Commands:** Fixed a bug in the ``env`` command where passing options only would cause the command to throw a ``TypeError`` instead of showing the current environment.
4242
- **Common:** Fixed a bug where the ``command()`` helper function did not properly clean up output buffers, which could lead to risky tests when exceptions were thrown.
43+
- **Kint:** Fixed a bug where stale Content Security Policy nonces were reused in worker mode, causing browser CSP violations for Debug Toolbar assets.
4344
- **Validation:** Fixed a bug where ``Validation::getValidated()`` dropped fields whose validated value was explicitly ``null``.
4445

4546
See the repo's

user_guide_src/source/conf.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@
111111
# A list of JS files.
112112
html_js_files = [
113113
'js/citheme.js',
114-
'js/carbon.js'
114+
'js/carbon.js',
115+
'js/version_switcher.js'
115116
]
116117

117118
# -- Options for LaTeX output --------------------------------------------------

user_guide_src/source/general/ajax.rst

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
AJAX Requests
33
##############
44

5-
The ``IncomingRequest::isAJAX()`` method uses the ``X-Requested-With`` header to define whether the request is XHR or normal. However, the most recent JavaScript implementations (i.e., fetch) no longer send this header along with the request, thus the use of ``IncomingRequest::isAJAX()`` becomes less reliable, because without this header it is not possible to define whether the request is or not XHR.
5+
The ``IncomingRequest::isAJAX()`` method uses the ``X-Requested-With`` header to define whether the request is XHR or normal. However, modern JavaScript APIs such as ``fetch`` no longer send this header by default, so ``IncomingRequest::isAJAX()`` becomes less reliable without additional configuration.
66

7-
To get around this problem, the most efficient solution (so far) is to manually define the request header, forcing the information to be sent to the server, which will then be able to identify that the request is XHR.
7+
To work around this problem, manually define the request header so the server can identify the request as XHR.
88

9-
Here's how to force the ``X-Requested-With`` header to be sent in the Fetch API and other JavaScript libraries.
9+
Here are common ways to send the ``X-Requested-With`` header in the Fetch API and other JavaScript libraries.
1010

1111
.. contents::
1212
:local:
@@ -28,41 +28,54 @@ Fetch API
2828
Axios
2929
=====
3030

31-
If you are using Axios, it also does not include the ``X-Requested-With`` header by default.
31+
Axios does not include the ``X-Requested-With`` header by default.
3232
You can add it globally as follows:
3333

3434
.. code-block:: javascript
3535
3636
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
3737
3838
39-
jQuery
40-
======
41-
42-
For libraries like jQuery for example, it is not necessary to make explicit the sending of this header, because according to the `official documentation <https://api.jquery.com/jquery.ajax/>`_ it is a standard header for all requests ``$.ajax()``. But if you still want to force the shipment to not take risks, just do it as follows:
39+
If you prefer to avoid global defaults, create an Axios instance instead:
4340

4441
.. code-block:: javascript
4542
46-
$.ajax({
47-
url: "your url",
48-
headers: {'X-Requested-With': 'XMLHttpRequest'}
43+
const api = axios.create({
44+
headers: {
45+
'X-Requested-With': 'XMLHttpRequest'
46+
}
4947
});
5048
51-
VueJS
52-
=====
5349
54-
In VueJS you just need to add the following code to the ``created`` function, as long as you are using Axios for this type of request.
50+
Vue.js
51+
-------
52+
53+
Vue does not require a specific HTTP client. If your Vue app uses Axios, configure Axios once during application bootstrap or in a shared API module, and reuse that configuration throughout the app.
54+
55+
React
56+
-----
57+
58+
React also does not provide a built-in HTTP client. If your React app uses Axios, reuse the shared Axios configuration above, or set the header for an individual request when needed:
5559

5660
.. code-block:: javascript
5761
58-
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
62+
axios.get('your url', {
63+
headers: {
64+
'X-Requested-With': 'XMLHttpRequest'
65+
}
66+
})
5967
60-
React
61-
=====
68+
jQuery
69+
======
70+
71+
For libraries like jQuery for example, it is not necessary to make explicit the sending of this header, because according to the `official documentation <https://api.jquery.com/jquery.ajax/>`_ it is a standard header for all requests ``$.ajax()``. But if you still want to force the shipment to not take risks, just do it as follows:
6272

6373
.. code-block:: javascript
6474
65-
axios.get("your url", {headers: {'Content-Type': 'application/json'}})
75+
$.ajax({
76+
url: "your url",
77+
headers: {'X-Requested-With': 'XMLHttpRequest'}
78+
});
6679
6780
htmx
6881
====
@@ -74,3 +87,14 @@ You can use `ajax-header <https://github.com/bigskysoftware/htmx-extensions/blob
7487
<body hx-ext="ajax-header">
7588
...
7689
</body>
90+
91+
92+
Or you can set the header manually with ``hx-headers``:
93+
94+
.. code-block:: html
95+
96+
<button
97+
hx-post="/your-url"
98+
hx-headers='{"X-Requested-With": "XMLHttpRequest"}'>
99+
Send Request
100+
</button>

0 commit comments

Comments
 (0)