Compare commits
356 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fe8d759b17 | |||
| f72244a8c2 | |||
| d09f737c7d | |||
| b654282b18 | |||
| bdb5a4ba40 | |||
| 419d7f40c3 | |||
| 7be05aae41 | |||
| 04261e8722 | |||
| a53121b74a | |||
| 6814c42260 | |||
| 658953e34f | |||
| c2c03f203f | |||
| 9a1a58e593 | |||
| 0f63155d58 | |||
| 25e00b5e1f | |||
| b2bc3b9e7e | |||
| 38ed732727 | |||
| 463f16617e | |||
| 260924aeb8 | |||
| b38413afe4 | |||
| 05ab53734b | |||
| 708732ed72 | |||
| 121d6fb20a | |||
| e4acb387bb | |||
| ff74bb2682 | |||
| 17fb4afaab | |||
| 60541d90e5 | |||
| 4b26df011f | |||
| fbecc7132d | |||
| bbea6e1d31 | |||
| 9e3ca2a866 | |||
| 40ee4ab3fa | |||
| d40fc8708a | |||
| 2a60bc604f | |||
| e8237560d3 | |||
| 258783a754 | |||
| 81de0469c1 | |||
| 638cbfac93 | |||
| 85c70539df | |||
| 5b442418ad | |||
| 9c641bf38e | |||
| f9797aca63 | |||
| d4b1ecbed4 | |||
| d140771110 | |||
| 762df4050c | |||
| bee2d552b7 | |||
| f172248f20 | |||
| db71e79e05 | |||
| 05c9ce4bd8 | |||
| ade9f7177a | |||
| 05846ae405 | |||
| e186ad572d | |||
| 9f636a3de3 | |||
| d79ad5de6a | |||
| b7c8f7f723 | |||
| 46192ca77e | |||
| d68ea95f26 | |||
| 85af2e6a7c | |||
| 136aef1fe6 | |||
| 79ea6a2d22 | |||
| e2f5104676 | |||
| bc133f6fc3 | |||
| bea6119692 | |||
| b04fcb4527 | |||
| b4278ca6b0 | |||
| d20c47f89c | |||
| d29433ddc6 | |||
| 0a494aa9f6 | |||
| a418e9e53a | |||
| 3f10ec31b5 | |||
| 488a653e50 | |||
| 7fa08a0b35 | |||
| 6a89731462 | |||
| fd8b7fc730 | |||
| 006eaeef08 | |||
| 2862cce2a9 | |||
| e3d76202cd | |||
| 6441cca4d1 | |||
| 04892e4707 | |||
| 17282deeea | |||
| c2e8975523 | |||
| c5e3ce289e | |||
| 83e442cc69 | |||
| b8f0f87b5f | |||
| 539e5db429 | |||
| 4c3523d987 | |||
| a0951e25b3 | |||
| 70a95b8eae | |||
| 5705496179 | |||
| e77912a7c5 | |||
| fb13f6dd01 | |||
| fbeef47a58 | |||
| 8fa5a3008d | |||
| 2c94380acf | |||
| 8cd66034e4 | |||
| 0bb3a2f20a | |||
| 96f06326f8 | |||
| 8b6d43705e | |||
| eb0575859b | |||
| a6ce3c0758 | |||
| a9bfc2075b | |||
| 6a1644abff | |||
| 1bbc0425e3 | |||
| cb18237b5c | |||
| c898074fd8 | |||
| 14b5d0694f | |||
| 54d0894ec3 | |||
| 0b326e9e72 | |||
| eae154a081 | |||
| 42022a1d26 | |||
| c59c0f906b | |||
| e82f1d43c8 | |||
| 6ff6327a6b | |||
| 4b34028221 | |||
| edbbe29706 | |||
| fc35bc5876 | |||
| 6c5f358ca2 | |||
| 895189107f | |||
| d8c4a57ce8 | |||
| 3d5c60ad26 | |||
| a2cfa93011 | |||
| e40f4e4d83 | |||
| 1f0f185176 | |||
| d5d2c59161 | |||
| c2c357dfb2 | |||
| fccf80c3a3 | |||
| ee26c16e90 | |||
| d38211efc6 | |||
| dd844035ac | |||
| 778b8f51b1 | |||
| 4d28563eeb | |||
| 89f3fced22 | |||
| 1ed0cb2562 | |||
| 7e1589b2ee | |||
| bc5d145dfb | |||
| 5aaba2850f | |||
| 3116b46192 | |||
| 3a1aed3578 | |||
| a2544fd5ca | |||
| ebab82c9f3 | |||
| 40e56b3adc | |||
| 0a360e1eb2 | |||
| 59aa25852c | |||
| ba60319c94 | |||
| ad836b6d4c | |||
| 72d7395c96 | |||
| d376741b93 | |||
| ca991dbe29 | |||
| 3a7594c71a | |||
| 9f88fb2125 | |||
| f29c4e36a4 | |||
| a87db5a60d | |||
| 07bbe2fa5e | |||
| 1c11297195 | |||
| 5f59e2a61e | |||
| 7e4119de7e | |||
| c30c1ca9cd | |||
| f50c83ac37 | |||
| c54b573bd0 | |||
| 1f5e140ae9 | |||
| f65d067dec | |||
| dbe914883c | |||
| 783dc4cdb2 | |||
| 26e91219e8 | |||
| 15526541b3 | |||
| c78364eb73 | |||
| 0c9bc880c1 | |||
| 93f282a345 | |||
| 7d4ba18a65 | |||
| e78cbaa6c4 | |||
| 9b401dfc29 | |||
| 3e179325e9 | |||
| 60727a7cdf | |||
| 4d59c788ee | |||
| d5993fffd4 | |||
| 136bd2c4c4 | |||
| b2d8615725 | |||
| 4e880779f9 | |||
| e5efe8b6d1 | |||
| da4a2d6191 | |||
| 84ec153634 | |||
| b69c42db5d | |||
| 55d5f7756d | |||
| b262a67c89 | |||
| 05492ddbb3 | |||
| 185856927d | |||
| 935c2509f1 | |||
| a502fbcd1e | |||
| 061186c215 | |||
| eac49ffa4c | |||
| db82329f6c | |||
| 875ad48c9d | |||
| c627e1a4de | |||
| 3b0165ec67 | |||
| 96bfaf67fd | |||
| fc4354c7e3 | |||
| b5c49b8ac9 | |||
| 2721a7cdc3 | |||
| ab79a0e972 | |||
| 275af6c114 | |||
| dcc8300ef4 | |||
| af62e9773c | |||
| 0ac35f9248 | |||
| a028969791 | |||
| 7bc41babf8 | |||
| 4e08cbd42e | |||
| d3427d45fa | |||
| 67a690d176 | |||
| 81d72254b5 | |||
| b2e58aed7b | |||
| 5ca36b0731 | |||
| b105ec5188 | |||
| a85f7c200b | |||
| f09f15e0be | |||
| e35be12fb7 | |||
| 2f1a67685f | |||
| d3bc17fe56 | |||
| adfd93a303 | |||
| 82f9838f28 | |||
| a18057fd06 | |||
| dbc285e49e | |||
| 31b409d920 | |||
| 501c58729f | |||
| 3cc283a25e | |||
| 188b5021fb | |||
| be7a698223 | |||
| 60a1c97289 | |||
| 92afaa484f | |||
| 9fdb460330 | |||
| ccfc156b43 | |||
| 10f10ee269 | |||
| 47bf3467a2 | |||
| 04d7b13fc4 | |||
| a3e7f7fd14 | |||
| 745b504426 | |||
| 89913c7eeb | |||
| eb658e33c3 | |||
| 03e68d81b4 | |||
| ae80f632bf | |||
| e2079313e2 | |||
| b4bade5ca9 | |||
| 10883ef268 | |||
| 19e1e834a7 | |||
| f30b0eee9b | |||
| 0ad939f062 | |||
| e8f534c4f4 | |||
| 7e22a9c2c7 | |||
| c542a77f11 | |||
| db52a8bc31 | |||
| 4800afbc3e | |||
| 990b740ec1 | |||
| b1d436a281 | |||
| 8bf31f2277 | |||
| 97fc41ebcb | |||
| bac3ffbe79 | |||
| dc7dd96189 | |||
| 67c567062e | |||
| b7ee271c8b | |||
| 81cdd8db81 | |||
| 90d7b45f5f | |||
| 632d4faeec | |||
| aac3b19bff | |||
| a65a8fc40a | |||
| c0bedfd833 | |||
| b72551c475 | |||
| 437967f78e | |||
| e05747e353 | |||
| 4865c40790 | |||
| d63fd0365a | |||
| f054958593 | |||
| 7ee0ee6206 | |||
| cf715c550a | |||
| 440a27c5de | |||
| 2130e4ff8b | |||
| 676626064c | |||
| 655ac0b6a6 | |||
| 28233e5b3d | |||
| e001508f68 | |||
| 9b96653fc5 | |||
| 920ddcb70f | |||
| 82d1033f43 | |||
| 6856da7f09 | |||
| 150d134fa2 | |||
| d30fa9918f | |||
| 343ad024c0 | |||
| 4f8778cfc7 | |||
| b770c56b6d | |||
| 156953f115 | |||
| 07a6698d01 | |||
| 1ec619db69 | |||
| 9f5f8d4a0a | |||
| 810f134cdc | |||
| 7b1a0a0e65 | |||
| 447530591d | |||
| 34223e32ef | |||
| ac3cda51f0 | |||
| f25ac24adf | |||
| c3ba14a052 | |||
| bd844a184c | |||
| c968b1236f | |||
| ff2d542295 | |||
| bf68987951 | |||
| e2049d770c | |||
| 021bdd0e4b | |||
| 9bb59ad004 | |||
| fd1035ecf0 | |||
| 0602fdc0cb | |||
| 1294e309dc | |||
| 596bc2c2bf | |||
| 09568062fd | |||
| b205b758e8 | |||
| 55077e45bb | |||
| 9c4f0d31f2 | |||
| c220ad62dd | |||
| ffc8c116fd | |||
| 8679a15604 | |||
| 9995038273 | |||
| 70ee7b1beb | |||
| 5cc2ab26eb | |||
| cf0446155b | |||
| 87c42699d9 | |||
| 97bcb22dba | |||
| ecd82700a7 | |||
| daa598b1b4 | |||
| 13eb20d8d1 | |||
| d0a9c57c0b | |||
| 21b11cdb3d | |||
| d4b2c50163 | |||
| bd50ac1876 | |||
| da4204c491 | |||
| 780b99229c | |||
| bebde352fe | |||
| 1adf99597e | |||
| 70c1b02809 | |||
| 335c9c920d | |||
| b1ad4fb830 | |||
| 2b5073a222 | |||
| 731b150241 | |||
| 228ee621e6 | |||
| 16f65ba275 | |||
| 165918258d | |||
| 3392a198d7 | |||
| 7cba44007c | |||
| 0004f31e2e | |||
| 83865c7979 | |||
| b43222dc67 | |||
| e857efcf28 | |||
| e7ab69fa26 | |||
| f4743bbb8d | |||
| d1b63c5e18 | |||
| cf5065ed71 | |||
| bf380e2d46 | |||
| 4b40f8273d | |||
| c0f2ff773f | |||
| 2ef18bce0d | |||
| 536780ce38 |
+1
-3
@@ -1,5 +1,3 @@
|
||||
**/yui/build/** -diff
|
||||
**/amd/build/** -diff
|
||||
**.js.map -diff
|
||||
**-min.js -diff
|
||||
**.min.js -diff
|
||||
lib/dml/oci_native_moodle_package.sql text eol=lf
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
# Primary donations pages.
|
||||
custom: ["https://moodle.com/donations/", "https://fund.moodle.org"]
|
||||
custom: ["https://moodle.com/donations/", moodle.org]
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
*** PLEASE DO NOT OPEN PULL REQUESTS VIA GITHUB ***
|
||||
|
||||
The moodle.git repository at Github is just a mirror of the official repository. We do not accept pull requests at Github.
|
||||
|
||||
See CONTRIBUTING.md guidelines for how to contribute patches for Moodle. Thank you.
|
||||
|
||||
--
|
||||
@@ -1,6 +0,0 @@
|
||||
> [!IMPORTANT]
|
||||
> PLEASE DO NOT PUBLISH SECURITY FINDINGS PUBLICLY.
|
||||
|
||||
If you discover a potential security issue, please report it via our [Security Submission Form](https://moodle.org/security/report).
|
||||
|
||||
For more information about our security processes and responsible disclosure policy, see the [Security Procedures documentation](https://moodledev.io/general/development/process/security).
|
||||
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1231.24 314.44" preserveAspectRatio="xMinYMid meet"><defs><style>.cls-1{fill:#f48020;}.cls-2,.cls-3{fill:#383738;stroke:#4a4a4c;}.cls-2{stroke-width:2.37px;}.cls-3{stroke-width:2.38px;}.cls-4{fill:#585858;}</style></defs><title>Moodle_Logo_UseonBlack_RGB</title><path class="cls-1" d="M1086.3,235.34a53.42,53.42,0,0,0,16.71,29q13.26,12.27,29.48,12.27,11.29,0,24.56-5.89,16.23-6.88,16.22-16.22l58-.49q-12.29,29-41.76,45.2-27.52,15.23-60.43,15.24-42.25,0-70-26.29t-27.75-68.54q0-43.73,27.75-69.51t72-25.8q48.15,0,75.17,31.94,25.07,29.47,25.06,79.1Zm89.91-33.91q-3.43-17.69-14.73-29-12.8-12.28-29.49-12.29-17.21,0-28.74,11.79t-15.47,29.48Z"/><path class="cls-1" d="M965.45,308.54V0h55V308.54Z"/><path class="cls-1" d="M892.69,308.54V290.36q-7.38,9.83-25.06,15.72a92.31,92.31,0,0,1-30,5.41q-39.31,0-63.13-27T750.7,217.65q0-39.3,23.34-65.84t62.15-26.53q43.71,0,56.5,23.58V0h53.55V308.54Zm0-102.68q0-14.75-14-28.26t-28.74-13.51q-21.12,0-33.41,17.2-10.8,15.24-10.8,37.33,0,21.63,10.8,36.85,12.29,17.71,33.41,17.69,12.78,0,27.76-12t15-24.81Z"/><path class="cls-1" d="M647,313.45q-41.77,0-69-26.53t-27.27-68.3q0-41.73,27.27-68.28t69-26.53q41.75,0,69.27,26.53t27.52,68.28q0,41.79-27.52,68.3T647,313.45ZM647,168q-19.89,0-30.58,15.07t-10.68,35.79q0,20.73,9.94,35.05,11.43,16.31,31.32,16.28t31.32-16.28q10.44-14.31,10.44-35.05t-9.94-35Q667.42,168,647,168Z"/><path class="cls-1" d="M448.07,313.45q-41.76,0-69-26.53t-27.27-68.3q0-41.73,27.27-68.28t69-26.53q41.74,0,69.28,26.53t27.5,68.28q0,41.79-27.5,68.3T448.07,313.45Zm0-145.43q-19.89,0-30.59,15.07t-10.69,35.79q0,20.73,9.94,35.05,11.43,16.31,31.34,16.28t31.32-16.28q10.42-14.31,10.44-35.05t-9.94-35Q468.45,168,448.07,168Z"/><path class="cls-1" d="M288.89,309.11V201q0-33.9-28-33.9t-28,33.9V309.11h-55V201q0-33.9-27.52-33.9-28,0-28,33.9V309.11h-55V194.65q0-35.37,24.56-53.54,21.61-16.22,58.46-16.22,37.35,0,55,19.16,15.23-19.16,55.51-19.16,36.86,0,58.45,16.22,24.57,18.16,24.57,53.54V309.11Z"/><path class="cls-2" d="M.76,145.76"/><path class="cls-3" d="M1.73,145.32"/><path class="cls-4" d="M173.86,116.85l55.08-40.1-.7-2.44C135,83.92,80.42,96.44,0,144.13l.73,2.38,21-.67c15.17.52,30.85,1.26,46,1.32-1,24.09.83,32.65.83,32.65,51.92,17.58,101.22,1.53,127.14-43.9A248.72,248.72,0,0,0,173.86,116.85Z"/><path class="cls-4" d="M113.78,110c-.3-1.08-2.23-1.48-4.28-.9a4.73,4.73,0,0,0-3.14,2.19c-18.53,5-49.16,13.72-70.73,21l-1.33.45c-16.68,5.61-24.73,8.49-25.22,12.55-2.29,18.44-.87,42.18-.86,42.48l.19,6.4c-9.74,27.56.06,49.6,8.38,69.77.06-24.92,1.07-45.22-6-71-.2-3.82-1.49-30.36.61-47.34.34-2.75,11.65-6.56,23.63-10.59l1.33-.44c22-7.42,53.57-16.39,71.9-21.29a6.75,6.75,0,0,0,2.29-.25C112.64,112.43,114.08,111.09,113.78,110Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -1,31 +0,0 @@
|
||||
name: 'Close Pull Requests'
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: opened
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
close:
|
||||
# Avoid forks to run this job
|
||||
if: github.repository_owner == 'moodle'
|
||||
name: Close Pull Requests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/repo-lockdown@v4
|
||||
with:
|
||||
process-only: prs
|
||||
close-pr: true
|
||||
skip-closed-pr-comment: true
|
||||
pr-comment: >
|
||||
**Please, don't open pull requests via GitHub.**
|
||||
|
||||
|
||||
This repository (moodle.git) at GitHub is just a mirror of the official Moodle repository.
|
||||
|
||||
|
||||
For how to contribute patches for Moodle see
|
||||
[CONTRIBUTING.md](https://github.com/moodle/moodle/blob/main/CONTRIBUTING.md).
|
||||
|
||||
|
||||
Closing this pull request.
|
||||
@@ -45,7 +45,7 @@ $CFG->admin = 'admin';
|
||||
$CFG->directorypermissions = 0777;
|
||||
|
||||
// Debug options - possible to be controlled by flag in future.
|
||||
$CFG->debug = (E_ALL); // DEBUG_DEVELOPER.
|
||||
$CFG->debug = (E_ALL | E_STRICT); // DEBUG_DEVELOPER.
|
||||
$CFG->debugdisplay = 1;
|
||||
$CFG->debugstringids = 1; // Add strings=1 to url to get string ids.
|
||||
$CFG->perfdebug = 15;
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
name: One by One Testing
|
||||
# Run all the individual unit tests one by one, with
|
||||
# fully independent PHPUnit executions. Useful to
|
||||
# detect issues with some tests that are using stuff
|
||||
# that has been made available by others, but is not
|
||||
# available when running individually.
|
||||
#
|
||||
# Note that we aren't using PHPUnit's own isolation
|
||||
# here but completely separated runs, one for each
|
||||
# test.
|
||||
#
|
||||
# The workflow will fail reporting all the tests
|
||||
# that have failed (and will pass if no failure is
|
||||
# detected, of course).
|
||||
#
|
||||
# It's only executed via workflow dispatch (automated
|
||||
# or manual), not by push/tag. And acceptd configuration
|
||||
# of phpunit, specially useful to run it with PHPUnit's
|
||||
# own isolation or any other option.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
phpunit_extra_options:
|
||||
description: Additional options to apply to PHPUnit
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
env:
|
||||
chunks: 7
|
||||
|
||||
jobs:
|
||||
collect:
|
||||
name: Collect individual unit tests
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{steps.individual-tests.outputs.matrix }}
|
||||
|
||||
steps:
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Looking for all individual tests
|
||||
id: individual-tests
|
||||
run: |
|
||||
count=0 # Number of individual tests found.
|
||||
while read -r testfile; do # For each test file.
|
||||
while read -r testname; do # For each unit test in a file.
|
||||
count=$((count + 1))
|
||||
# Sent it to the correct chunk file.
|
||||
chunk=$(((($count % $chunks)) + 1))
|
||||
echo "$testname $testfile" >> ./chunk_$chunk.txt
|
||||
done < <(grep "function test_" "${testfile}" | sed -r "s/^.*function (test_[a-zA-Z0-9_]+).*/\1/")
|
||||
done < <(find . -name "*_test.php")
|
||||
# Generate the matrix to run tests.
|
||||
echo "matrix=$(ls -1 chunk_*.txt | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
|
||||
echo "$count individual tests collected in $chunks files"
|
||||
|
||||
- name: Upload individual tests files
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: individual_tests
|
||||
path: chunk_*.txt
|
||||
retention-days: 1
|
||||
|
||||
test:
|
||||
name: Run tests
|
||||
needs: collect
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
exttests:
|
||||
image: moodlehq/moodle-exttests
|
||||
ports:
|
||||
- 8080:80
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
file: ${{ fromJson(needs.collect.outputs.matrix) }}
|
||||
|
||||
steps:
|
||||
- name: Setting up DB pgsql
|
||||
uses: m4nu56/postgresql-action@v1
|
||||
with:
|
||||
postgresql version: 14
|
||||
postgresql db: test
|
||||
postgresql user: test
|
||||
postgresql password: test
|
||||
|
||||
- name: Setting up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.4
|
||||
ini-values: max_input_vars=5000
|
||||
coverage: none
|
||||
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download individual test files
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: individual_tests # Make all the chunk files available for the next steps.
|
||||
|
||||
- name: Setting up PHPUnit
|
||||
env:
|
||||
dbtype: pgsql
|
||||
run: |
|
||||
echo "pathtophp=$(which php)" >> $GITHUB_ENV
|
||||
cp .github/workflows/config-template.php config.php
|
||||
mkdir ../moodledata
|
||||
sudo locale-gen en_AU.UTF-8
|
||||
php admin/tool/phpunit/cli/init.php --no-composer-self-update
|
||||
|
||||
- name: Run PHPUnit test (one by one)
|
||||
env:
|
||||
dbtype: pgsql
|
||||
run: |
|
||||
status=0
|
||||
count=0
|
||||
while read -r line; do # For each line in the chunk file
|
||||
count=$((count + 1))
|
||||
filter="${line% *}"
|
||||
file="${line#* }"
|
||||
# Run the individual unit test and report problems if needed to.
|
||||
if ! php vendor/bin/phpunit \
|
||||
--fail-on-empty-test-suite \
|
||||
--fail-on-warning \
|
||||
--fail-on-risky \
|
||||
--filter "$filter" ${{ inputs.phpunit_extra_options }} \
|
||||
"$file" >/dev/null 2>&1; then
|
||||
if [ $status -eq 0 ]; then
|
||||
echo "Problems found, list of PHPUnit commands failing:"
|
||||
fi
|
||||
echo "vendor/bin/phpunit --filter '${filter}' ${{ inputs.phpunit_extra_options }} $file"
|
||||
status=$((status + 1))
|
||||
fi
|
||||
done < ${{ matrix.file }}
|
||||
echo "Finished: $count individual tests executed, $status tests failed"
|
||||
exit $status
|
||||
@@ -3,7 +3,7 @@ name: Core
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- main
|
||||
- master
|
||||
- MOODLE_[0-9]+_STABLE
|
||||
tags-ignore:
|
||||
- v[0-9]+.[0-9]+.[0-9]+*
|
||||
@@ -15,7 +15,7 @@ on:
|
||||
default: ''
|
||||
|
||||
env:
|
||||
php: 8.4
|
||||
php: 8.2
|
||||
|
||||
jobs:
|
||||
Grunt:
|
||||
@@ -23,10 +23,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Configuring node & npm
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
@@ -62,12 +62,12 @@ jobs:
|
||||
include:
|
||||
# MySQL builds always run with the lowest PHP supported version.
|
||||
- os: ubuntu-22.04
|
||||
php: 8.2
|
||||
php: 8.0
|
||||
extensions:
|
||||
db: mysqli
|
||||
# PostgreSQL builds always run with the highest PHP supported version.
|
||||
- os: ubuntu-22.04
|
||||
php: 8.4
|
||||
php: 8.2
|
||||
db: pgsql
|
||||
|
||||
steps:
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
uses: moodlehq/mysql-action@v1
|
||||
with:
|
||||
collation server: utf8mb4_bin
|
||||
mysql version: 8.4
|
||||
mysql version: 8.0
|
||||
mysql database: test
|
||||
mysql user: test
|
||||
mysql password: test
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
if: ${{ matrix.db == 'pgsql' }}
|
||||
uses: m4nu56/postgresql-action@v1
|
||||
with:
|
||||
postgresql version: 14
|
||||
postgresql version: 13
|
||||
postgresql db: test
|
||||
postgresql user: test
|
||||
postgresql password: test
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
coverage: none
|
||||
|
||||
- name: Checking out code from ${{ env.GITHUB_REF_SLUG }}
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setting up PHPUnit
|
||||
env:
|
||||
@@ -122,6 +122,3 @@ jobs:
|
||||
dbtype: ${{ matrix.db }}
|
||||
phpunit_options: ${{ secrets.phpunit_options }}
|
||||
run: vendor/bin/phpunit $phpunit_options ${{ inputs.phpunit_extra_options }}
|
||||
|
||||
- name: Git status
|
||||
run: git status
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
name: Web Installer Testing
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
InstallationTest:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
php: [8.2, 8.4]
|
||||
steps:
|
||||
- name: Construct repository URL
|
||||
run: echo "REPOSITORY_URL=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- uses: moodlehq/moodle-webinstaller-test@main
|
||||
with:
|
||||
repository: ${{ env.REPOSITORY_URL }}
|
||||
branch: ${{ github.ref_name }}
|
||||
php: ${{ matrix.php }}
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
required: false
|
||||
default: ''
|
||||
env:
|
||||
php: 8.4
|
||||
php: 8.2
|
||||
|
||||
jobs:
|
||||
Grunt:
|
||||
@@ -21,10 +21,10 @@ jobs:
|
||||
git config --global core.eol lf
|
||||
|
||||
- name: Checking out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Configuring node & npm
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
php: 8.4
|
||||
php: 8.2
|
||||
# Ideally we should use mysql/mariadb, but they are 4x slower without tweaks and configuration
|
||||
# so let's run only postgres (1.5h vs 6h) only, If some day we want to improve the mysql runs,
|
||||
# this is the place to enable them.
|
||||
@@ -59,16 +59,16 @@ jobs:
|
||||
db: pgsql
|
||||
extensions: exif, fileinfo, gd, intl, pgsql, mysql, redis, soap, sodium, zip
|
||||
- os: windows-latest
|
||||
php: 8.2
|
||||
php: 8.0
|
||||
db: pgsql
|
||||
extensions: exif, fileinfo, gd, intl, pgsql, mysql, redis, soap, sodium, zip
|
||||
extensions: exif, fileinfo, gd, intl, pgsql, mysql, redis, soap, sodium
|
||||
|
||||
steps:
|
||||
- name: Setting up DB mysql
|
||||
if: ${{ matrix.db == 'mysqli' }}
|
||||
uses: shogo82148/actions-setup-mysql@v1
|
||||
with:
|
||||
mysql-version: 8.4
|
||||
mysql-version: 8.0
|
||||
user: test
|
||||
password: test
|
||||
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
git config --global core.eol lf
|
||||
|
||||
- name: Checking out code from ${{ env.GITHUB_REF_SLUG }}
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Needs to be done after php is available, git configured and Moodle checkout has happened.
|
||||
- name: Setting up moodle-exttests service
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
|
||||
- name: Setting up redis service
|
||||
run: |
|
||||
choco install redis --version 5.0.7 --no-progress
|
||||
choco install redis-64 --version 3.0.503 --no-progress
|
||||
nssm install redis redis-server
|
||||
nssm start redis
|
||||
|
||||
|
||||
@@ -26,9 +26,6 @@
|
||||
/.patches/
|
||||
/.idea/
|
||||
.phpstorm.*
|
||||
!/.phpstorm.meta.php/
|
||||
/.phpstorm.meta.php/*
|
||||
!/.phpstorm.meta.php/di.php
|
||||
/nbproject/
|
||||
CVS
|
||||
.DS_Store
|
||||
@@ -58,5 +55,3 @@ moodle-plugin-ci.phar
|
||||
.hugo_build.lock
|
||||
phpcs.xml
|
||||
jsconfig.json
|
||||
UPGRADING-CURRENT.md
|
||||
.phpunit.cache
|
||||
|
||||
+11
-49
@@ -40,20 +40,9 @@ const fetchComponentData = () => {
|
||||
if (!Object.entries(componentData).length) {
|
||||
componentData.subsystems = {};
|
||||
componentData.pathList = [];
|
||||
componentData.components = {};
|
||||
componentData.standardComponents = {};
|
||||
|
||||
// Fetch the component definiitions from the distributed JSON file.
|
||||
const components = JSON.parse(fs.readFileSync(`${gruntFilePath}/lib/components.json`));
|
||||
const pluginData = JSON.parse(fs.readFileSync(`${gruntFilePath}/lib/plugins.json`));
|
||||
|
||||
componentData.pluginTypes = components.plugintypes;
|
||||
|
||||
const standardPlugins = Object.entries(pluginData.standard).map(
|
||||
([pluginType, pluginNames]) => {
|
||||
return pluginNames.map(pluginName => `${pluginType}_${pluginName}`);
|
||||
}
|
||||
).reduce((acc, val) => acc.concat(val), []);
|
||||
|
||||
// Build the list of moodle subsystems.
|
||||
componentData.subsystems.lib = 'core';
|
||||
@@ -66,19 +55,8 @@ const fetchComponentData = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// The list of components includes the list of subsystems.
|
||||
componentData.components = {...componentData.subsystems};
|
||||
|
||||
const subpluginAdder = (subpluginType, subpluginTypePath) => {
|
||||
glob.sync(`${subpluginTypePath}/*/version.php`).forEach(versionPath => {
|
||||
const componentPath = fs.realpathSync(path.dirname(versionPath));
|
||||
const componentName = path.basename(componentPath);
|
||||
const frankenstyleName = `${subpluginType}_${componentName}`;
|
||||
|
||||
componentData.components[`${subpluginTypePath}/${componentName}`] = frankenstyleName;
|
||||
componentData.pathList.push(componentPath);
|
||||
});
|
||||
};
|
||||
// The list of components incldues the list of subsystems.
|
||||
componentData.components = componentData.subsystems;
|
||||
|
||||
// Go through each of the plugintypes.
|
||||
Object.entries(components.plugintypes).forEach(([pluginType, pluginTypePath]) => {
|
||||
@@ -95,36 +73,20 @@ const fetchComponentData = () => {
|
||||
if (fs.existsSync(subPluginConfigurationFile)) {
|
||||
const subpluginList = JSON.parse(fs.readFileSync(fs.realpathSync(subPluginConfigurationFile)));
|
||||
|
||||
if (subpluginList.subplugintypes) {
|
||||
Object.entries(subpluginList.subplugintypes).forEach(([subpluginType, subpluginTypePath]) => {
|
||||
subpluginAdder(
|
||||
subpluginType,
|
||||
`${pluginTypePath}/${componentName}/${subpluginTypePath}`
|
||||
);
|
||||
Object.entries(subpluginList.plugintypes).forEach(([subpluginType, subpluginTypePath]) => {
|
||||
glob.sync(`${subpluginTypePath}/*/version.php`).forEach(versionPath => {
|
||||
const componentPath = fs.realpathSync(path.dirname(versionPath));
|
||||
const componentName = path.basename(componentPath);
|
||||
const frankenstyleName = `${subpluginType}_${componentName}`;
|
||||
|
||||
componentData.components[`${subpluginTypePath}/${componentName}`] = frankenstyleName;
|
||||
componentData.pathList.push(componentPath);
|
||||
});
|
||||
} else if (subpluginList.plugintypes) {
|
||||
Object.entries(subpluginList.plugintypes).forEach(([subpluginType, subpluginTypePath]) => {
|
||||
subpluginAdder(subpluginType, subpluginTypePath);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Create a list of the standard subsystem and plugins.
|
||||
componentData.standardComponents = Object.fromEntries(
|
||||
Object.entries(componentData.components).filter(([, name]) => {
|
||||
if (name === 'core' || name.startsWith('core_')) {
|
||||
return true;
|
||||
}
|
||||
return standardPlugins.indexOf(name) !== -1;
|
||||
})
|
||||
);
|
||||
|
||||
componentData.componentMapping = Object.fromEntries(
|
||||
Object.entries(componentData.components).map(([path, name]) => [name, path])
|
||||
);
|
||||
}
|
||||
|
||||
return componentData;
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import * as Components from '../../components.js';
|
||||
|
||||
const componentData = Components.fetchComponentData();
|
||||
|
||||
/**
|
||||
* The standard components shipped with core Moodle.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
export const standardComponents = componentData.standardComponents;
|
||||
|
||||
/**
|
||||
* All components of the current Moodle instance.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
export const allComponents = componentData.components;
|
||||
|
||||
/**
|
||||
* Get all components of the current Moodle instance.
|
||||
*
|
||||
* @returns {Array}
|
||||
*/
|
||||
export const getAllComponents = () => {
|
||||
let components = new Map(Object.entries(componentData.pluginTypes).map(([value, path]) => ([path,{
|
||||
path,
|
||||
value,
|
||||
name: `${value} (plugin type)`,
|
||||
}])));
|
||||
|
||||
Object
|
||||
.entries(componentData.components)
|
||||
.filter(([path, value]) => Object.values(componentData.standardComponents).includes(value))
|
||||
.forEach(([path, value]) => {
|
||||
const entry = {
|
||||
path,
|
||||
value,
|
||||
name: value,
|
||||
};
|
||||
if (Object.values(componentData.subsystems).includes(value)) {
|
||||
if (components.has(path)) {
|
||||
entry.name = `${value} (subsystem / plugintype)`;
|
||||
} else {
|
||||
entry.name = `${value} (subsystem)`;
|
||||
}
|
||||
}
|
||||
|
||||
components.set(path, entry);
|
||||
});
|
||||
|
||||
return Array.from(components.values());
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the specified component is a standard component shipped with core Moodle.
|
||||
*
|
||||
* @param {string} componentName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isStandardComponent = (componentName) => {
|
||||
if (Object.values(componentData.standardComponents).includes(componentName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Object.keys(componentData.pluginTypes).includes(componentName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const rewritePlugintypeAsSubsystem = (componentName) => {
|
||||
if (Object.keys(componentData.pluginTypes).includes(componentName)) {
|
||||
const pluginTypePath = componentData.pluginTypes[componentName];
|
||||
if (Object.keys(componentData.subsystems).includes(pluginTypePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the specified component is a community component.
|
||||
*
|
||||
* @param {string} componentName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isCommunityComponent = (componentName) => {
|
||||
if (isStandardComponent(componentName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Object.values(componentData.components).indexOf(componentName) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sort method for components.
|
||||
*
|
||||
* This method sorts components putting `core` first, followed by core subsystems, then everything else.
|
||||
*
|
||||
* @param {String} a
|
||||
* @param {String} b
|
||||
* @returns {Number}
|
||||
*/
|
||||
export const sortComponents = (a, b) => {
|
||||
// Always put 'core' first.
|
||||
if (a === 'core') {
|
||||
return -1;
|
||||
} else if (b === 'core') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Put core subsystems next.
|
||||
if (a.startsWith('core_') && !b.startsWith('core_')) {
|
||||
return -1;
|
||||
} else if (b.startsWith('core_') && !a.startsWith('core_')) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Sort alphabetically for everything else.
|
||||
return a.localeCompare(b);
|
||||
};
|
||||
@@ -1,85 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { createNote } from './note.mjs';
|
||||
import { getInitialValues } from './helpers.mjs';
|
||||
import * as Prompts from './prompts.mjs';
|
||||
import logger from './logger.mjs';
|
||||
|
||||
export default async (options) => {
|
||||
// Processs the initial values.
|
||||
const initialValues = getInitialValues(options);
|
||||
|
||||
// Fetch information.
|
||||
const messages = [];
|
||||
const { issueNumber } = await inquirer.prompt([
|
||||
Prompts.getIssuePrompt(),
|
||||
], initialValues);
|
||||
|
||||
let selection = {};
|
||||
let notePath;
|
||||
do {
|
||||
selection = {};
|
||||
selection = await inquirer.prompt([
|
||||
Prompts.getComponentsPrompt(),
|
||||
Prompts.getTypePrompt(),
|
||||
Prompts.getMessagePromptInput(),
|
||||
], initialValues);
|
||||
if (selection.message === '') {
|
||||
selection = Object.assign(
|
||||
selection,
|
||||
await inquirer.prompt([
|
||||
Prompts.getMessagePromptEditor(),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`
|
||||
Creating upgrade note with the following options:
|
||||
|
||||
- Issue: ${chalk.bold(issueNumber)}
|
||||
- Component: ${chalk.bold(selection.components)}
|
||||
- Type: ${chalk.bold(selection.type)}
|
||||
- Message:
|
||||
${chalk.bold(selection.message)}
|
||||
`);
|
||||
|
||||
messages.push({
|
||||
components: [selection.components],
|
||||
type: selection.type,
|
||||
message: selection.message,
|
||||
});
|
||||
|
||||
// Save the note so far.
|
||||
if (notePath) {
|
||||
await createNote(issueNumber, messages, notePath);
|
||||
logger.info(`Updated note at: ${chalk.underline(chalk.bold(notePath))}`);
|
||||
} else {
|
||||
notePath = await createNote(issueNumber, messages);
|
||||
logger.info(`Note created at: ${chalk.underline(chalk.bold(notePath))}`);
|
||||
}
|
||||
|
||||
selection = Object.assign(
|
||||
selection,
|
||||
await inquirer.prompt([
|
||||
Prompts.getAddAnotherPrompt(),
|
||||
], initialValues),
|
||||
);
|
||||
} while (selection.addAnother);
|
||||
};
|
||||
@@ -1,357 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { getAllComponents } from './components.mjs';
|
||||
import { getCombinedNotesByComponent, deleteAllNotes } from './note.mjs';
|
||||
import { getNoteName } from './noteTypes.mjs';
|
||||
import { writeFile, readFile, unlink } from 'fs/promises';
|
||||
import { join as joinPath } from 'path';
|
||||
import logger from './logger.mjs';
|
||||
import { getCurrentVersion } from './helpers.mjs';
|
||||
|
||||
/**
|
||||
* Helper to fetch the current notes from a file.
|
||||
*
|
||||
* @param {string} file
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
const getCurrentNotes = async (file) => {
|
||||
try {
|
||||
return await readFile(file, 'utf8');
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the UPGRADING.md file.
|
||||
*
|
||||
* @param {string} upgradeNotes
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.deleteNotes
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const updateUpgradeNotes = async (upgradeNotes, options) => {
|
||||
const fileName = 'UPGRADING.md';
|
||||
// Write the notes to a file.
|
||||
logger.info(`Writing notes to ${chalk.underline(chalk.bold(fileName))}`);
|
||||
// Prepend to the existing file.
|
||||
const existingContent = await getCurrentNotes(fileName);
|
||||
if (existingContent) {
|
||||
await writeFile(fileName, getUpdatedNotes(existingContent, upgradeNotes));
|
||||
} else {
|
||||
// This should not normally happen.
|
||||
await writeFile(fileName, upgradeNotes);
|
||||
}
|
||||
|
||||
if (options.deleteNotes) {
|
||||
logger.warn(`>>> Deleting all notes <<<`)
|
||||
// Delete the notes.
|
||||
deleteAllNotes();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the current summary notes.
|
||||
*
|
||||
* @param {string} upgradeNotes
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const createCurrentSummary = async (upgradeNotes) => {
|
||||
const fileName = 'UPGRADING-CURRENT.md';
|
||||
const notes = `# Moodle upgrade notes\n\n${upgradeNotes}`;
|
||||
await writeFile(fileName, notes);
|
||||
|
||||
logger.info(`Running upgrade notes written to ${chalk.underline(chalk.bold(fileName))}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the indexes of the lines that contain the version headings.
|
||||
*
|
||||
* @param {array<string>} lines
|
||||
* @returns {array<object>}
|
||||
*/
|
||||
const getVersionLineIndexes = (lines) => {
|
||||
const h2Indexes = [];
|
||||
lines.forEach((line, index) => {
|
||||
const matches = line.match(/^##\s(?<version>.*)$/);
|
||||
if (matches) {
|
||||
h2Indexes.push({
|
||||
index,
|
||||
line,
|
||||
version: matches.groups.version,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return h2Indexes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the index of the Unreleased heading.
|
||||
*
|
||||
* @param {array<object>} versionHeadings
|
||||
* @returns {number}
|
||||
*/
|
||||
const findUnreleasedHeadingIndex = (versionHeadings) => versionHeadings.findIndex((heading) => {
|
||||
if (heading.version === 'Unreleased') {
|
||||
// Used if version cannot be guessed.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (heading.version.endsWith('+')) {
|
||||
// Weekly release for a stable branch.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (heading.version.match(/beta|rc\d/)) {
|
||||
// Beta and RC rolls are treated as weeklies.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (heading.version.endsWith('dev')) {
|
||||
// Development version.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the before and after content, to facilitate replacing any existing Unreleased notes.
|
||||
*
|
||||
* @param {array<string>} lines
|
||||
* @returns {Object} {beforeContent: string, afterContent: string}
|
||||
*/
|
||||
const getBeforeAndAfterContent = (lines) => {
|
||||
const existingLines = lines.split('\n');
|
||||
const versionHeadings = getVersionLineIndexes(existingLines);
|
||||
|
||||
if (versionHeadings.length > 0) {
|
||||
const unreleasedHeadingIndex = findUnreleasedHeadingIndex(versionHeadings);
|
||||
if (unreleasedHeadingIndex !== -1) {
|
||||
const beforeContent = existingLines.slice(0, versionHeadings[unreleasedHeadingIndex].index).join('\n');
|
||||
if (versionHeadings.length > unreleasedHeadingIndex + 1) {
|
||||
const afterContent = existingLines.slice(versionHeadings[unreleasedHeadingIndex + 1].index).join('\n');
|
||||
return {
|
||||
beforeContent,
|
||||
afterContent,
|
||||
};
|
||||
}
|
||||
return {
|
||||
beforeContent,
|
||||
afterContent: '',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
beforeContent: existingLines.slice(0, versionHeadings[0].index).join('\n'),
|
||||
afterContent: existingLines.slice(versionHeadings[0].index).join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
beforeContent: existingLines.join('\n'),
|
||||
afterContent: '',
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the notes for the component.
|
||||
*
|
||||
* @param {string} types
|
||||
* @param {Number} headingLevel
|
||||
* @returns {string}
|
||||
*/
|
||||
const getNotesForComponent = (types, headingLevel) => {
|
||||
let upgradeNotes = '';
|
||||
Object.entries(types).forEach(([type, notes]) => {
|
||||
upgradeNotes += '#'.repeat(headingLevel);
|
||||
upgradeNotes += ` ${getNoteName(type)}\n\n`;
|
||||
notes.forEach(({ message, issueNumber }) => {
|
||||
// Split the message into lines, removing empty lines.
|
||||
const messageLines = message
|
||||
.split('\n')
|
||||
// Remove empty lines between tables, and list entries, but not after lists.
|
||||
.filter((line, index, lines) => {
|
||||
if (line.trim().length === 0) {
|
||||
// This line is empty.
|
||||
|
||||
// If it's the first line in the file, remove it.
|
||||
if (index === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is the last line in the file, remove it.
|
||||
if (index === lines.length - 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the previous line relates to a table, remove this line.
|
||||
if (lines[index - 1].match(/^\s*\|/)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the next line is also empty, do not remove this line.
|
||||
if (lines[index + 1].trim().length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do not remove the line if the previous line was a list item.
|
||||
if (lines[index - 1].match(/^\s*[-*]\s/)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lines[index - 1].match(/^\s*\d+\.\s/)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Preserve all other empty lines by default.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Keep any line which has content.
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
const firstLine = messageLines.shift().trim();
|
||||
upgradeNotes += `- ${firstLine}\n`;
|
||||
|
||||
messageLines
|
||||
.forEach((line) => {
|
||||
upgradeNotes += ` ${line}`.trimEnd() + `\n`;
|
||||
});
|
||||
upgradeNotes += `\n For more information see [${issueNumber}](https://tracker.moodle.org/browse/${issueNumber})\n`;
|
||||
});
|
||||
upgradeNotes += '\n';
|
||||
});
|
||||
|
||||
return upgradeNotes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the updated notes mixed with existing content.
|
||||
*
|
||||
* @param {string} existingContent
|
||||
* @param {string} upgradeNotes
|
||||
*/
|
||||
const getUpdatedNotes = (existingContent, upgradeNotes) => {
|
||||
const { beforeContent, afterContent } = getBeforeAndAfterContent(existingContent);
|
||||
const newContent = `${beforeContent}\n${upgradeNotes}\n${afterContent}`
|
||||
.split('\n')
|
||||
.filter((line, index, lines) => {
|
||||
if (line === '' && lines[index - 1] === '') {
|
||||
// Remove multiple consecutive empty lines.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return newContent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the notes for each component.
|
||||
*/
|
||||
const updateComponentNotes = (
|
||||
notes,
|
||||
version,
|
||||
notesFileName = 'UPGRADING.md',
|
||||
removeEmpty = false,
|
||||
) => {
|
||||
return getAllComponents().map(async (component) => {
|
||||
logger.verbose(`Updating notes for ${component.name} into ${component.path}`);
|
||||
const fileName = joinPath(component.path, notesFileName);
|
||||
|
||||
const existingContent = await getCurrentNotes(fileName);
|
||||
|
||||
if (!existingContent) {
|
||||
if (!notes[component.value]) {
|
||||
// No existing notes, and no new notes to add.
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!notes[component.value]) {
|
||||
// There is existing content, but nothing to add.
|
||||
if (removeEmpty) {
|
||||
logger.verbose(`Removing empty notes file ${fileName}`);
|
||||
await unlink(fileName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const componentNotes = notes[component.value];
|
||||
let upgradeNotes = `## ${version}\n\n`;
|
||||
upgradeNotes += getNotesForComponent(componentNotes, 3);
|
||||
|
||||
if (existingContent) {
|
||||
await writeFile(fileName, getUpdatedNotes(existingContent, upgradeNotes));
|
||||
} else {
|
||||
await writeFile(
|
||||
fileName,
|
||||
`# ${component.name} Upgrade notes\n\n${upgradeNotes}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the upgrade notes for a new release.
|
||||
*
|
||||
* @param {string|undefined} version
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.generateUpgradeNotes
|
||||
* @param {boolean} options.deleteNotes
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export default async (version, options = {}) => {
|
||||
const notes = await getCombinedNotesByComponent();
|
||||
|
||||
if (Object.keys(notes).length === 0) {
|
||||
logger.warn('No notes to generate');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!version) {
|
||||
version = await getCurrentVersion();
|
||||
}
|
||||
|
||||
// Generate the upgrade notes for this release.
|
||||
// We have
|
||||
// - a title with the release name
|
||||
// - the change types
|
||||
// - which contain the components
|
||||
// - which document each change
|
||||
let upgradeNotes = `## ${version}\n\n`;
|
||||
|
||||
Object.entries(notes).forEach(([component, types]) => {
|
||||
upgradeNotes += `### ${component}\n\n`;
|
||||
upgradeNotes += getNotesForComponent(types, 4);
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
createCurrentSummary(upgradeNotes),
|
||||
...updateComponentNotes(notes, version, 'UPGRADING-CURRENT.md', true),
|
||||
]);
|
||||
if (options.generateUpgradeNotes) {
|
||||
await Promise.all(updateComponentNotes(notes, version));
|
||||
await updateUpgradeNotes(upgradeNotes, options);
|
||||
}
|
||||
};
|
||||
@@ -1,230 +0,0 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { isStandardComponent, isCommunityComponent, rewritePlugintypeAsSubsystem } from './components.mjs';
|
||||
import { isValidNoteName } from './noteTypes.mjs';
|
||||
import logger from './logger.mjs';
|
||||
import { readFile } from 'fs/promises';
|
||||
|
||||
/**
|
||||
* Validate an issue number input
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {string|boolean}
|
||||
*/
|
||||
export const validateIssueNumber = (input) => {
|
||||
if (!input) {
|
||||
return 'You must provide a tracker issue number';
|
||||
}
|
||||
|
||||
if (input.match(/^[a-zA-Z]*-\d+$/)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (input.match(/^\d+$/)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'The issue number was not recognised as a valid issue number';
|
||||
};
|
||||
|
||||
/**
|
||||
* Format an issue number input.
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {string}
|
||||
*/
|
||||
export const formatIssueNumber = (input) => {
|
||||
if (input.match(/^[a-zA-Z]*-\d+$/)) {
|
||||
return input;
|
||||
}
|
||||
|
||||
if (input.match(/^\d+$/)) {
|
||||
return `MDL-${input}`;
|
||||
}
|
||||
|
||||
return input;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate a component.
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {string|boolean}
|
||||
*/
|
||||
export const validateComponent = (input) => {
|
||||
if (isStandardComponent(input)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isCommunityComponent(input)) {
|
||||
return 'Currently only core plugins are supported.';
|
||||
}
|
||||
|
||||
return 'The component was not recognised as a standard component';
|
||||
};
|
||||
|
||||
export const formatComponent = (input) => {
|
||||
if (rewritePlugintypeAsSubsystem(input)) {
|
||||
return `core_${input}`;
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the initial values from the options.
|
||||
*
|
||||
* @param {object} options
|
||||
* @returns {object}
|
||||
*/
|
||||
export const getInitialValues = (options) => {
|
||||
const initialValues = {};
|
||||
|
||||
const type = getInitialTypeValue(options);
|
||||
if (type) {
|
||||
initialValues.type = type;
|
||||
}
|
||||
|
||||
const issueNumber = getInitialIssueValue(options);
|
||||
if (issueNumber) {
|
||||
initialValues.issueNumber = issueNumber;
|
||||
}
|
||||
|
||||
const component = getInitialComponentValue(options);
|
||||
if (component) {
|
||||
initialValues.components = component;
|
||||
}
|
||||
|
||||
const message = getInitialMessageValue(options);
|
||||
if (message) {
|
||||
initialValues.message = message
|
||||
initialValues.addAnother = false;
|
||||
}
|
||||
|
||||
return initialValues;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the initial type value.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
const getInitialTypeValue = (options) => {
|
||||
if (!options.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.type = options.type.trim().toLowerCase();
|
||||
|
||||
if (isValidNoteName(options.type)) {
|
||||
return options.type;
|
||||
}
|
||||
|
||||
logger.warn(`Note type "${chalk.underline(chalk.red(options.type))}" is not valid.`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the initial issue number value.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
|
||||
const getInitialIssueValue = (options) => {
|
||||
if (!options.issue) {
|
||||
return;
|
||||
}
|
||||
options.issue = options.issue.trim().toUpperCase();
|
||||
|
||||
const issueNumberValidated = validateIssueNumber(options.issue);
|
||||
if (issueNumberValidated === true) {
|
||||
const issueNumber = formatIssueNumber(options.issue);
|
||||
if (issueNumber !== options.issue) {
|
||||
logger.warn(
|
||||
`Issue number "${chalk.underline(chalk.red(options.issue))}" was updated to ` +
|
||||
`"${chalk.underline(chalk.green(issueNumber))}"`
|
||||
);
|
||||
}
|
||||
|
||||
return issueNumber;
|
||||
} else {
|
||||
logger.warn(`Issue number "${chalk.underline(chalk.red(options.issue))}" is not valid: ${issueNumberValidated}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the initial component value.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
const getInitialComponentValue = (options) => {
|
||||
if (!options.component) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.component = options.component.trim().toLowerCase();
|
||||
const componentValidated = validateComponent(options.component);
|
||||
if (componentValidated === true) {
|
||||
const component = formatComponent(options.component);
|
||||
if (component !== options.component) {
|
||||
logger.warn(
|
||||
`Component "${chalk.underline(chalk.red(options.component))}" was updated to ` +
|
||||
`"${chalk.underline(chalk.green(component))}"`
|
||||
);
|
||||
}
|
||||
|
||||
return component;
|
||||
} else {
|
||||
logger.warn(`Component "${chalk.underline(chalk.red(options.component))}" is not valid: ${componentValidated}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the initial message value.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
|
||||
const getInitialMessageValue = (options) => {
|
||||
if (!options.message) {
|
||||
return;
|
||||
}
|
||||
|
||||
return options.message.trim();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current version from the project /version.php file.
|
||||
*
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export const getCurrentVersion = async () => {
|
||||
const versionRegex = new RegExp(/^ *\$release *= *['\"](?<release>[^ \+]+\+?) *\(Build:.*/m);
|
||||
try {
|
||||
const versionFile = await readFile('version.php', 'utf8');
|
||||
const match = versionFile.match(versionRegex);
|
||||
if (match) {
|
||||
return match.groups.release;
|
||||
}
|
||||
} catch(error) {
|
||||
logger.error('Unable to read the version file');
|
||||
}
|
||||
|
||||
return "Unreleased";
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import winston from 'winston';
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple()
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.Console(),
|
||||
],
|
||||
});
|
||||
|
||||
export default logger;
|
||||
@@ -1,194 +0,0 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import yaml from 'js-yaml';
|
||||
import path from 'path';
|
||||
import { writeFile, mkdir, readdir, readFile, unlink } from 'fs/promises';
|
||||
import { isValidNoteName, sortNoteTypes } from './noteTypes.mjs';
|
||||
import { sortComponents } from './components.mjs';
|
||||
|
||||
const unreleasedPath = path.resolve('.upgradenotes');
|
||||
|
||||
/**
|
||||
* Get the filename for the note.
|
||||
*
|
||||
* @param {string} issueNumnber The issue number
|
||||
* @returns {string}
|
||||
*/
|
||||
const getFilename = (issueNumber) => {
|
||||
const dateTimeFormat = new Intl.DateTimeFormat(undefined, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
fractionalSecondDigits: 2,
|
||||
timeZone: 'UTC',
|
||||
});
|
||||
|
||||
const date = Object.fromEntries(
|
||||
dateTimeFormat.formatToParts(new Date())
|
||||
.filter((p) => p.type !== 'literal')
|
||||
.map((p) => ([p.type, p.value]))
|
||||
);
|
||||
|
||||
const dateString = [
|
||||
date.year,
|
||||
date.month,
|
||||
date.day,
|
||||
date.hour,
|
||||
date.minute,
|
||||
date.second,
|
||||
date.fractionalSecond,
|
||||
].join('');
|
||||
|
||||
return `${issueNumber}-${dateString}.yml`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new note.
|
||||
*
|
||||
* @param {string} issueNumber
|
||||
* @param {[type: string]: {message: string}} messages
|
||||
* @returns {string} The path to the note on disk
|
||||
*/
|
||||
export const createNote = async (
|
||||
issueNumber,
|
||||
messages,
|
||||
notePath,
|
||||
) => {
|
||||
const note = {
|
||||
issueNumber,
|
||||
notes: {},
|
||||
};
|
||||
|
||||
messages.forEach(({components, type, message}) => {
|
||||
note.notes[components] = note.notes[components] || [];
|
||||
note.notes[components].push({message, type});
|
||||
});
|
||||
|
||||
if (!notePath) {
|
||||
notePath = path.resolve(unreleasedPath, getFilename(issueNumber));
|
||||
}
|
||||
const noteContent = yaml.dump(note);
|
||||
|
||||
await mkdir(unreleasedPath, {recursive: true});
|
||||
await writeFile(notePath, noteContent);
|
||||
|
||||
return notePath;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all unreleased notes.
|
||||
*
|
||||
* @returns {Promise<{issueNumber: string, components: string[], types: {[type: string]: {message: string}[]}}[]>
|
||||
*/
|
||||
export const getAllNotes = async () => {
|
||||
const files = await readdir(unreleasedPath);
|
||||
const notes = files
|
||||
.filter((file) => file.endsWith('.yml'))
|
||||
.map(async (file) => {
|
||||
const filePath = path.resolve(unreleasedPath, file);
|
||||
const fileContent = await readFile(filePath, 'utf8');
|
||||
|
||||
return {
|
||||
...yaml.load(fileContent),
|
||||
filePath,
|
||||
};
|
||||
});
|
||||
|
||||
return Promise.all(notes);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of notes, grouped by note type, then component.
|
||||
*
|
||||
* @returns {Promise<{[type: string]: {[components: string]: {message: string, issueNumber: string}[]}}>}
|
||||
*/
|
||||
export const getCombinedNotes = async () => {
|
||||
const notes = await getAllNotes();
|
||||
const combinedNotes = {};
|
||||
|
||||
notes.forEach((note) => {
|
||||
Object.entries(note.notes).forEach(([components, data]) => {
|
||||
data.forEach((entry) => {
|
||||
if (!isValidNoteName(entry.type)) {
|
||||
throw new Error(`Invalid note type: "${entry.type}" in file ${note.filePath}`);
|
||||
}
|
||||
combinedNotes[entry.type] = combinedNotes[entry.type] || {};
|
||||
combinedNotes[entry.type][components] = combinedNotes[entry.type][components] || [];
|
||||
combinedNotes[entry.type][components].push({message: entry.message, issueNumber: note.issueNumber});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return combinedNotes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of notes, grouped by component, then by note type.
|
||||
*
|
||||
* @returns {Promise<{[component: string]: {[type: string]: {message: string, issueNumber: string}[]}}>}
|
||||
*/
|
||||
export const getCombinedNotesByComponent = async () => {
|
||||
const notes = await getAllNotes();
|
||||
const combinedNotes = {};
|
||||
|
||||
notes.forEach((note) => {
|
||||
Object.entries(note.notes).forEach(([component, data]) => {
|
||||
combinedNotes[component] = combinedNotes[component] || {};
|
||||
data.forEach((entry) => {
|
||||
if (!isValidNoteName(entry.type)) {
|
||||
throw new Error(`Invalid note type: "${entry.type}" in file ${note.filePath}`);
|
||||
}
|
||||
combinedNotes[component][entry.type] = combinedNotes[component][entry.type] || [];
|
||||
combinedNotes[component][entry.type].push({
|
||||
component,
|
||||
message: entry.message,
|
||||
issueNumber: note.issueNumber,
|
||||
type: entry.type,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Sort notes by note type.
|
||||
Object.entries(combinedNotes).forEach(([component, types]) => {
|
||||
combinedNotes[component] = Object.fromEntries(
|
||||
Object.entries(types).sort(([a], [b]) => sortNoteTypes(a, b))
|
||||
);
|
||||
});
|
||||
|
||||
// Sort components.
|
||||
return Object.fromEntries(
|
||||
Object.entries(combinedNotes).sort(([a], [b]) => sortComponents(a, b))
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete all unreleased notes.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const deleteAllNotes = async () => {
|
||||
const files = await readdir(unreleasedPath);
|
||||
return Promise.all(
|
||||
files
|
||||
.filter((item, index) => files.indexOf(item) === index)
|
||||
.filter((file) => file.endsWith('.yml'))
|
||||
.map((file) => unlink(`${unreleasedPath}/${file}`))
|
||||
);
|
||||
};
|
||||
@@ -1,80 +0,0 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* The note types.
|
||||
*
|
||||
* Matches the suggested order in https://keepachangelog.com/en/1.1.0/.
|
||||
* @type {Object<string, string>}
|
||||
*/
|
||||
const noteTypes = {
|
||||
'improved': 'Added',
|
||||
'changed': 'Changed',
|
||||
'deprecated': 'Deprecated',
|
||||
'removed': 'Removed',
|
||||
'fixed': 'Fixed',
|
||||
};
|
||||
|
||||
/**
|
||||
* The preferred order of note types.
|
||||
*
|
||||
* @type {string[]}
|
||||
*/
|
||||
const preferredOrder = Object.keys(noteTypes);
|
||||
|
||||
/**
|
||||
* Comparison method to sort note types.
|
||||
*
|
||||
* @param {String} a
|
||||
* @param {String} b
|
||||
* @returns {Number}
|
||||
*/
|
||||
export const sortNoteTypes = (a, b) => {
|
||||
const aIndex = preferredOrder.indexOf(a);
|
||||
const bIndex = preferredOrder.indexOf(b);
|
||||
|
||||
if (aIndex === -1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (bIndex === -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return aIndex - bIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the note names.
|
||||
*
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export const getNoteNames = () => Object.keys(noteTypes);
|
||||
|
||||
/**
|
||||
* Get the human-readable note name.
|
||||
*
|
||||
* @param {string} type
|
||||
* @returns {string}
|
||||
*/
|
||||
export const getNoteName = (type) => noteTypes[type];
|
||||
|
||||
/**
|
||||
* Whether the note name is valid.
|
||||
*
|
||||
* @param {string} type
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isValidNoteName = (type) => noteTypes[type] !== undefined;
|
||||
@@ -1,153 +0,0 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import inquirer from 'inquirer';
|
||||
import SearchList from 'inquirer-search-list';
|
||||
|
||||
import { getNoteNames } from './noteTypes.mjs';
|
||||
import { getAllComponents } from './components.mjs';
|
||||
import {
|
||||
formatComponent,
|
||||
formatIssueNumber,
|
||||
validateComponent,
|
||||
validateIssueNumber,
|
||||
} from './helpers.mjs';
|
||||
|
||||
/**
|
||||
* A Search List which accepts an initial value.
|
||||
*/
|
||||
class SearchListWithInitialValue extends SearchList {
|
||||
constructor(options, ...args) {
|
||||
super(options, ...args);
|
||||
|
||||
if (options.default) {
|
||||
const pointer = this.filterList.findIndex((item) => {
|
||||
return item.value === options.default;
|
||||
});
|
||||
if (pointer > -1) {
|
||||
this.pointer = pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inquirer.registerPrompt('search-list', SearchListWithInitialValue);
|
||||
|
||||
/**
|
||||
* Get the issue type prompt.
|
||||
*
|
||||
* @param {string} defaultData The initially selected value
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const getTypePrompt = (defaultData) => ({
|
||||
default: defaultData,
|
||||
type: 'search-list',
|
||||
message: 'Type of change',
|
||||
name: 'type',
|
||||
choices: getNoteNames(),
|
||||
validate: (selection) => {
|
||||
if (selection.length < 1) {
|
||||
return 'You must select at least one type of change';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the component prompt.
|
||||
*
|
||||
* @param {string} [defaultValue='core'] The initally selected value.
|
||||
* @returns
|
||||
*/
|
||||
export const getComponentsPrompt = (defaultValue) => {
|
||||
if (!defaultValue ) {
|
||||
defaultValue = 'core';
|
||||
}
|
||||
|
||||
return {
|
||||
choices: getAllComponents(),
|
||||
default: defaultValue,
|
||||
type: 'search-list',
|
||||
message: 'Component',
|
||||
name: 'components',
|
||||
validate: validateComponent,
|
||||
filter: formatComponent,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the issue number prompt as an inline input.
|
||||
*
|
||||
* @param {string} defaultData
|
||||
* @returns {object}
|
||||
*/
|
||||
export const getIssuePrompt = (defaultData) => ({
|
||||
default: defaultData,
|
||||
type: 'input',
|
||||
message: 'Tracker issue number',
|
||||
name: 'issueNumber',
|
||||
validate: validateIssueNumber,
|
||||
filter: formatIssueNumber,
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a message prompt.
|
||||
*
|
||||
* @param {string} defaultData
|
||||
* @returns
|
||||
*/
|
||||
export const getMessagePromptEditor = (defaultData) => ({
|
||||
default: defaultData,
|
||||
type: process.stdin.isTTY ? 'editor' : 'input',
|
||||
postfix: '.md',
|
||||
message: 'Message',
|
||||
name: 'message',
|
||||
waitUserInput: false,
|
||||
validate: (input) => {
|
||||
if (!input) {
|
||||
return 'You must provide a message';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// Remove any trailing whitespace.
|
||||
filter: (input) => input.split('\n').map((line) => line.trimEnd()).join('\n'),
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a message prompt.
|
||||
*
|
||||
* @param {string} defaultData
|
||||
* @returns
|
||||
*/
|
||||
export const getMessagePromptInput = (defaultData) => ({
|
||||
default: defaultData,
|
||||
type: 'input',
|
||||
message: 'Message (leave empty to use editor)',
|
||||
name: 'message',
|
||||
filter: (input) => input.trim(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a prompt to ask the user if they wish to add another entry.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const getAddAnotherPrompt = () => ({
|
||||
type: 'confirm',
|
||||
message: 'Do you want to add another note?',
|
||||
default: false,
|
||||
name: 'addAnother',
|
||||
});
|
||||
@@ -21,7 +21,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Function to generate the destination for the minification task
|
||||
* Function to generate the destination for the uglify task
|
||||
* (e.g. build/file.min.js). This function will be passed to
|
||||
* the rename property of files array when building dynamically:
|
||||
* http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically
|
||||
@@ -60,6 +60,7 @@ module.exports = grunt => {
|
||||
grunt.registerTask('js', ['amd', 'yui']);
|
||||
|
||||
// Register NPM tasks.
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-rollup');
|
||||
|
||||
@@ -103,7 +104,6 @@ module.exports = grunt => {
|
||||
// The queue runner will run the next `size` items in the queue.
|
||||
const runQueue = (size = 1) => {
|
||||
queue.splice(0, size).forEach(resolve => {
|
||||
grunt.log.debug(`Item resolved. Kicking off next one.`);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
@@ -113,17 +113,15 @@ module.exports = grunt => {
|
||||
|
||||
// The options hook is run in parallel.
|
||||
// We can return an unresolved Promise which is queued for later resolution.
|
||||
options: async(options) => {
|
||||
options: async() => {
|
||||
return new Promise(resolve => {
|
||||
queue.push(resolve);
|
||||
startQueue();
|
||||
return options;
|
||||
});
|
||||
},
|
||||
|
||||
// When an item in the queue completes, start the next item in the queue.
|
||||
generateBundle: (options, bundle) => {
|
||||
grunt.log.debug(`Finished output phase for ${Object.keys(bundle).join(', ')}`);
|
||||
buildEnd: () => {
|
||||
runQueue();
|
||||
},
|
||||
};
|
||||
@@ -157,6 +155,16 @@ module.exports = grunt => {
|
||||
],
|
||||
presets: [
|
||||
['@babel/preset-env', {
|
||||
targets: {
|
||||
browsers: [
|
||||
">0.3%",
|
||||
"last 2 versions",
|
||||
"not ie >= 0",
|
||||
"not op_mini all",
|
||||
"not Opera > 0",
|
||||
"not dead"
|
||||
]
|
||||
},
|
||||
modules: false,
|
||||
useBuiltIns: false
|
||||
}]
|
||||
|
||||
+12
-28
@@ -20,33 +20,17 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
module.exports = (grunt) => {
|
||||
const path = require('path');
|
||||
|
||||
grunt.registerTask('jsdoc', 'Generate JavaScript documentation using jsdoc', function() {
|
||||
const done = this.async();
|
||||
const configuration = path.resolve('.grunt/jsdoc/jsdoc.conf.js');
|
||||
|
||||
grunt.util.spawn({
|
||||
cmd: 'jsdoc',
|
||||
args: [
|
||||
'--configure',
|
||||
configuration,
|
||||
]
|
||||
}, function(error, result, code) {
|
||||
if (result.stdout) {
|
||||
grunt.log.write(result.stdout);
|
||||
}
|
||||
|
||||
if (result.stderr) {
|
||||
grunt.log.error(result.stderr);
|
||||
}
|
||||
if (error) {
|
||||
grunt.fail.fatal(`JSDoc failed with error code ${code}`);
|
||||
} else {
|
||||
grunt.log.write('JSDoc completed successfully'.green);
|
||||
}
|
||||
done();
|
||||
});
|
||||
module.exports = grunt => {
|
||||
// Project configuration.
|
||||
grunt.config.merge({
|
||||
jsdoc: {
|
||||
dist: {
|
||||
options: {
|
||||
configure: ".grunt/jsdoc/jsdoc.conf.js",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-jsdoc');
|
||||
};
|
||||
|
||||
@@ -29,7 +29,6 @@ module.exports = grunt => {
|
||||
// Use a fully-qualified path.
|
||||
src: files,
|
||||
options: {
|
||||
quietDeprecationWarnings: true,
|
||||
configOverrides: {
|
||||
rules: {
|
||||
// These rules have to be disabled in .stylelintrc for scss compat.
|
||||
@@ -46,10 +45,7 @@ module.exports = grunt => {
|
||||
return {
|
||||
stylelint: {
|
||||
scss: {
|
||||
options: {
|
||||
quietDeprecationWarnings: true,
|
||||
customSyntax: 'postcss-scss',
|
||||
},
|
||||
options: {syntax: 'scss'},
|
||||
src: files,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Argument, Option, program } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { getNoteNames } from './notes/src/noteTypes.mjs';
|
||||
import createAction from './notes/src/create.mjs';
|
||||
import generateAction from './notes/src/generate.mjs';
|
||||
import logger from './notes/src/logger.mjs';
|
||||
|
||||
console.log(`
|
||||
${chalk.bold(chalk.underline(chalk.green('Moodle Upgrade Notes Generator')))}
|
||||
|
||||
This tool is used to generate the upgrade notes for changes you make in Moodle.
|
||||
|
||||
Please remember that the intended audience of these changes is
|
||||
${chalk.italic('plugin developers')} who need to know how to update their plugins
|
||||
for a new Moodle version.
|
||||
|
||||
Upgrade notes should not be used to document changes for site administrators, or
|
||||
for internal API changes which are not expected to be used outside of the
|
||||
relevant component.
|
||||
`)
|
||||
|
||||
program.configureHelp({
|
||||
helpWidth: 100,
|
||||
});
|
||||
|
||||
program.on('option:verbose', () => {
|
||||
logger.level = 'verbose';
|
||||
});
|
||||
|
||||
program.addOption(
|
||||
new Option(
|
||||
'-v, --verbose',
|
||||
'Output more information during the generation process',
|
||||
)
|
||||
.default(false)
|
||||
);
|
||||
|
||||
// Define the command line options.
|
||||
program
|
||||
.command('create')
|
||||
.summary('Generate a new upgrade note')
|
||||
.addOption(
|
||||
new Option('-t, --type <type>', `The type of change to document. Valid types are: ${getNoteNames().join(', ')}`)
|
||||
)
|
||||
.addOption(new Option('-i, --issue <issue>', 'The tracker issue number'))
|
||||
.addOption(new Option('-c, --component <component>', 'The component to write a note for'))
|
||||
.addOption(new Option(
|
||||
'-m, --message <message>',
|
||||
'The message to use for the upgrade note',
|
||||
))
|
||||
.action((options) => createAction(options));
|
||||
|
||||
program
|
||||
.command('summary')
|
||||
.summary('Generate a local copy of the upgrade notes summary')
|
||||
.addArgument(
|
||||
new Argument('[version]', 'The Moodle version to create the summary notes for')
|
||||
)
|
||||
.action((version) => generateAction(version));
|
||||
|
||||
program
|
||||
.command('release')
|
||||
.summary('Generate the markdown copies of the upgrade notes for a Moodle release')
|
||||
.addArgument(
|
||||
new Argument('[version]', 'The Moodle version to create the release notes for')
|
||||
)
|
||||
.addOption(
|
||||
new Option(
|
||||
'--generate-upgrade-notes',
|
||||
'Generate the UPGRADING.md notes for the release. ' +
|
||||
'Note: This option is intended for use by the release manager when generating the upgrade notes.',
|
||||
)
|
||||
.default(true)
|
||||
)
|
||||
.addOption(
|
||||
new Option(
|
||||
'-d, --delete-notes',
|
||||
'Delete the notes after generating the UPGRADING.md notes for the release. ' +
|
||||
'Note: This option is intended for use by the release manager when generating the upgrade notes.' +
|
||||
'This option has no effect unless --generate-upgrade-notes is also set.'
|
||||
)
|
||||
.default(false)
|
||||
)
|
||||
.action((version, options) => generateAction(version, options));
|
||||
|
||||
program.parse();
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Helper file for PhpStorm, and other IDEs, to provide better code completion.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace PHPSTORM_META;
|
||||
|
||||
// Assume that anything returned by \core\di::get('...') is an instance of the first argument.
|
||||
override(\core\di::get(0), map([
|
||||
'' => '@',
|
||||
]));
|
||||
|
||||
// Assume that anything returned by \Psr\Container\ContainerInterface::get('...') is an instance of the first argument.
|
||||
override(\Psr\Container\ContainerInterface::get(0), map([
|
||||
'' => '@',
|
||||
]));
|
||||
|
||||
// Assume that anything returned by \DI\Container::get('...') is an instance of the first argument.
|
||||
override(\DI\Container::get(0), map([
|
||||
'' => '@',
|
||||
]));
|
||||
+4
-16
@@ -1,14 +1,9 @@
|
||||
{
|
||||
"customSyntax": "postcss-scss",
|
||||
"plugins": [
|
||||
"stylelint-csstree-validator"
|
||||
],
|
||||
"rules": {
|
||||
"csstree/validator": {
|
||||
"syntaxExtensions": [
|
||||
"sass"
|
||||
]
|
||||
},
|
||||
"csstree/validator": true,
|
||||
"at-rule-empty-line-before": [ "always",
|
||||
{"except": [ "blockless-after-blockless"], ignore: ["after-comment", "inside-block"]}
|
||||
],
|
||||
@@ -50,7 +45,7 @@
|
||||
"function-name-case": "lower",
|
||||
"function-parentheses-newline-inside": "always-multi-line",
|
||||
"function-parentheses-space-inside": "never-single-line",
|
||||
"function-url-scheme-disallowed-list": ["data"],
|
||||
"function-url-scheme-blacklist": ["data"],
|
||||
"function-whitespace-after": "always",
|
||||
"indentation": 4,
|
||||
"keyframe-declaration-no-important": true,
|
||||
@@ -89,19 +84,12 @@
|
||||
"selector-type-no-unknown": true,
|
||||
"string-no-newline": true,
|
||||
"time-min-milliseconds": 100,
|
||||
"unit-disallowed-list": ["pt"],
|
||||
"unit-blacklist": ["pt"],
|
||||
"unit-case": "lower",
|
||||
"unit-no-unknown": true,
|
||||
"value-keyword-case": ["lower", {"ignoreKeywords": ["/(@|$)/"]}],
|
||||
"value-list-comma-newline-after": "always-multi-line",
|
||||
"value-list-comma-space-after": "always-single-line",
|
||||
"value-list-comma-space-before": "never",
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/yui/**/*.css"],
|
||||
"rules": {
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
issueNumber: MDL-46739
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
The {user_preferences}.value database field is now TEXT instead of CHAR.
|
||||
This means that any queries with a condition on this field in a WHERE or
|
||||
JOIN statement will need updating to use `$DB->sql_compare_text()`. See
|
||||
the `$newusers` query in
|
||||
`\core\task\send_new_users_password_task::execute` for an example.
|
||||
type: changed
|
||||
@@ -1,6 +0,0 @@
|
||||
issueNumber: MDL-61232
|
||||
notes:
|
||||
core:
|
||||
- message: |
|
||||
moodle_process_email() has been deprecated with the removal of the unused and non-functioning admin/process_email.php.
|
||||
type: removed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-66873
|
||||
notes:
|
||||
core_backup:
|
||||
- message: Remove all MODE_HUB related code.
|
||||
type: removed
|
||||
@@ -1,21 +0,0 @@
|
||||
issueNumber: MDL-68806
|
||||
notes:
|
||||
mod_quiz:
|
||||
- message: >-
|
||||
quiz_attempt now has 2 additional state values, NOT_STARTED and
|
||||
SUBMITTED. These represent attempts when an attempt has been
|
||||
type: improved
|
||||
- message: >
|
||||
quiz_attempt_save_started now sets the IN_PROGRESS state, timestarted,
|
||||
and saves the attempt, while the new quiz_attempt_save_not_started
|
||||
function sets the NOT_STARTED state and saves the attempt.
|
||||
type: changed
|
||||
- message: >
|
||||
quiz_attempt::process_finish is now deprecated, and its functionality is
|
||||
split between ::process_submit, which saves the
|
||||
|
||||
submission, sets the finish time and sets the SUBMITTED status, and
|
||||
::process_grade_submission which performs automated
|
||||
|
||||
grading and sets the FINISHED status.
|
||||
type: deprecated
|
||||
@@ -1,20 +0,0 @@
|
||||
issueNumber: MDL-68806
|
||||
notes:
|
||||
mod_quiz:
|
||||
- message: >
|
||||
New quiz setting "precreateperiod" controls the period before timeopen
|
||||
during which attempts will be pre-created using the new
|
||||
|
||||
NOT_STARTED state. This setting is marked advanced and locked by
|
||||
default, so can only be set by administrators. This setting
|
||||
|
||||
is read by the \mod_quiz\task\precreate_attempts task to identify
|
||||
quizzes due for pre-creation.
|
||||
type: improved
|
||||
- message: >
|
||||
quiz_attempt_save_started Now takes an additional $timenow parameter, to
|
||||
specify the timestart of the attempt. This was previously
|
||||
|
||||
set in quiz_create_attempt, but is now set in quiz_attempt_save_started
|
||||
and quiz_attempt_save_not_started.
|
||||
type: changed
|
||||
@@ -1,15 +0,0 @@
|
||||
issueNumber: MDL-68806
|
||||
notes:
|
||||
core_question:
|
||||
- message: >
|
||||
question_attempt_step's constructor now accepts the class constant
|
||||
TIMECREATED_ON_FIRST_RENDER as a value for the
|
||||
|
||||
$timecreated parameter. Calling question_attempt::render for the first
|
||||
time will now set the first step's timecreated
|
||||
|
||||
to the current time if it is set to this value. Note, null could not be
|
||||
used here as it is already used to indicate
|
||||
|
||||
timecreated should be set to the current time.
|
||||
type: changed
|
||||
@@ -1,24 +0,0 @@
|
||||
issueNumber: MDL-68806
|
||||
notes:
|
||||
mod_quiz:
|
||||
- message: >+
|
||||
The webservice function `mod_quiz_get_user_attempts` is now deprecated
|
||||
in favour of `mod_quiz_get_user_quiz_attempts`.
|
||||
|
||||
|
||||
With the introduction of the new NOT_STARTED quiz attempt state,
|
||||
`mod_quiz_get_user_attempts` has been modified to not return NOT_STARTED
|
||||
attempts, allowing clients such as the mobile app to continue working
|
||||
without modifications.
|
||||
|
||||
|
||||
`mod_quiz_get_user_quiz_attempts` will return attempts in all states, as
|
||||
`mod_quiz_get_user_attempts` did before. Once clients are updated to
|
||||
handle NOT_STARTED attempts, they can migrate to use this function.
|
||||
|
||||
|
||||
A minor modification to `mod_quiz_start_attempt` has been made to allow
|
||||
it to transparently start an existing attempt that is in the NOT_STARTED
|
||||
state, rather than creating a new one.
|
||||
|
||||
type: deprecated
|
||||
@@ -1,14 +0,0 @@
|
||||
issueNumber: MDL-71257
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
The methods `want_read_slave` and `perf_get_reads_slave` in
|
||||
`lib/dml/moodle_database.php` have been deprecated in favour of
|
||||
renamed versions that substitute `slave` for `replica`.
|
||||
type: deprecated
|
||||
- message: >-
|
||||
The trait `moodle_read_slave_trait` has been deprecated in favour of a
|
||||
functionally identical version called `moodle_read_replica_trait`.
|
||||
The renamed trait substitutes the terminology of `slave` with `replica`,
|
||||
and `master` with `primary`.
|
||||
type: deprecated
|
||||
@@ -1,17 +0,0 @@
|
||||
issueNumber: MDL-71378
|
||||
notes:
|
||||
tool_brickfield:
|
||||
- message: >
|
||||
tool_brickfield\local\areas\core_question\answerbase::find_system_areas
|
||||
|
||||
|
||||
No replacement. System context no longer a valid context to assign a
|
||||
question category
|
||||
type: deprecated
|
||||
- message: >
|
||||
tool_brickfield\local\areas\core_question\base::find_system_areas
|
||||
|
||||
|
||||
No replacement. System context no longer a valid context to assign a
|
||||
question category
|
||||
type: deprecated
|
||||
@@ -1,42 +0,0 @@
|
||||
issueNumber: MDL-71378
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
question_make_default_categories()
|
||||
|
||||
|
||||
No longer creates a default category in either CONTEXT_SYSTEM,
|
||||
CONTEXT_COURSE, or CONTEXT_COURSECAT.
|
||||
|
||||
Superceded by question_get_default_category which can optionally create one if it does not exist.
|
||||
type: deprecated
|
||||
- message: |
|
||||
question_delete_course()
|
||||
|
||||
No replacement. Course contexts no longer hold question categories.
|
||||
type: deprecated
|
||||
- message: |
|
||||
question_delete_course_category()
|
||||
|
||||
Course category contexts no longer hold question categories.
|
||||
type: deprecated
|
||||
qbank_bulkmove:
|
||||
- message: >
|
||||
qbank_bulkmove/helper::get_displaydata
|
||||
|
||||
|
||||
Superceded by a modal and webservice, see
|
||||
qbank_bulkmove/modal_question_bank_bulkmove and
|
||||
core_question_external\move_questions
|
||||
type: deprecated
|
||||
- message: |
|
||||
qbank_bulkmove\output\renderer::render_bulk_move_form
|
||||
|
||||
Superceded by qbank_bulkmove\output\bulk_move
|
||||
type: deprecated
|
||||
core_question:
|
||||
- message: |
|
||||
question_type::generate_test
|
||||
|
||||
No replacement, not used anywhere in core.
|
||||
type: deprecated
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-71472
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
The method `site_registration_form::add_select_with_email()` has been
|
||||
finally deprecated and will now throw an exception if called.
|
||||
type: removed
|
||||
@@ -1,9 +0,0 @@
|
||||
issueNumber: MDL-72293
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
The `core/sortable_list` Javascript module now emits native events,
|
||||
removing the jQuery dependency from calling code that wants to listen
|
||||
for the events. Backwards compatibility with existing code using jQuery
|
||||
is preserved
|
||||
type: improved
|
||||
@@ -1,13 +0,0 @@
|
||||
issueNumber: MDL-74488
|
||||
notes:
|
||||
core_reportbuilder:
|
||||
- message: >-
|
||||
New `report` helper class `get_report_row_count` method for retrieving
|
||||
row count of custom or system report, without having to retrieve the
|
||||
report content
|
||||
type: improved
|
||||
- message: >-
|
||||
The `schedule` helper class `get_schedule_report_count` method is now
|
||||
deprecated, existing code should instead use
|
||||
`report::get_report_row_count`
|
||||
type: deprecated
|
||||
@@ -1,21 +0,0 @@
|
||||
issueNumber: MDL-75075
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
The `core_renderer::tag_list` function now has a new parameter named `displaylink`.
|
||||
When `displaylink` is set to `true`, the tag name will be displayed as a clickable hyperlink.
|
||||
Otherwise, it will be rendered as plain text.
|
||||
type: changed
|
||||
core_tag:
|
||||
- message: >-
|
||||
The `core_tag\taglist` class now includes a new property called `displaylink`,
|
||||
which has a default value of `true`. When `displaylink` is set to `true`,
|
||||
the tag name will be displayed as a clickable hyperlink. If `displaylink` is set to `false`,
|
||||
the tag name will be rendered as plain text instead.
|
||||
type: changed
|
||||
mod_quiz:
|
||||
- message: >-
|
||||
The `quiz_question_tostring` method now includes a new boolean parameter, `displaytaglink`.
|
||||
This parameter specifies whether the tag name in the question bank should be displayed
|
||||
as a clickable hyperlink (`true`) or as plain text (`false`).
|
||||
type: changed
|
||||
@@ -1,24 +0,0 @@
|
||||
issueNumber: MDL-75189
|
||||
notes:
|
||||
mod_data:
|
||||
- message: |
|
||||
Final deprecation and removal of the following classes:
|
||||
- data_preset_importer
|
||||
- data_preset_existing_importer
|
||||
- data_preset_upload_importer
|
||||
- data_import_preset_zip_form
|
||||
type: removed
|
||||
- message: >
|
||||
- Final deprecation of \mod_data_renderer::import_setting_mappings(). Please use \mod_data_renderer::importing_preset() instead.
|
||||
- Final deprecation of data_print_template() function. Please use mod_data\manager::get_template and mod_data\template::parse_entries instead.
|
||||
- Final deprecation of data_preset_name(). Please use preset::get_name_from_plugin() instead.
|
||||
- Final deprecation of data_get_available_presets(). Please use manager::get_available_presets() instead.
|
||||
- Final deprecation of data_get_available_site_presets(). Please use manager::get_available_saved_presets() instead.
|
||||
- Final deprecation of data_delete_site_preset(). Please use preset::delete() instead.
|
||||
- Final deprecation of is_directory_a_preset(). Please use preset::is_directory_a_preset() instead.
|
||||
- Final deprecation of data_presets_save(). Please use preset::save() instead.
|
||||
- Final deprecation of data_presets_generate_xml(). Please use preset::generate_preset_xml() instead.
|
||||
- Final deprecation of data_presets_export(). Please use preset::export() instead.
|
||||
- Final deprecation of data_user_can_delete_preset(). Please use preset::can_manage() instead.
|
||||
- Final deprecation of data_view(). Please use mod_data\manager::set_module_viewed() instead.
|
||||
type: removed
|
||||
@@ -1,19 +0,0 @@
|
||||
issueNumber: MDL-75610
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
`\core\output\activity_header` now uses the `is_title_allowed()` method
|
||||
when setting the title in the constructor.
|
||||
|
||||
|
||||
This method has been improved to give priority to the 'notitle' option
|
||||
in the theme config for the current page layout, over the top-level
|
||||
option in the theme.
|
||||
|
||||
|
||||
For example, the Boost theme sets
|
||||
`$THEME->activityheaderconfig['notitle'] = true;` by default, but in its
|
||||
`secure` pagelayout, it has `'notitle' = false`.
|
||||
|
||||
This prevents display of the title in all layouts except `secure`.
|
||||
type: improved
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-75875
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
Remove support deprecated boolean 'primary' parameter in
|
||||
\core\output\single_button. The 4th parameter is now a string and not a
|
||||
boolean (the use was to set it to true to have a primary button)
|
||||
type: removed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-77107
|
||||
notes:
|
||||
gradereport_grader:
|
||||
- message: >-
|
||||
The `behat_gradereport_grader::get_grade_item_id` step helper has been
|
||||
removed, please use the equivalent `behat_grades` method instead
|
||||
type: removed
|
||||
@@ -1,18 +0,0 @@
|
||||
issueNumber: MDL-77293
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
url class now correctly supports multi level query parameter parsing and
|
||||
output.
|
||||
|
||||
|
||||
This was previously supported in some methods such as get_query_string,
|
||||
but not others. This has been fixed to be properly supported.
|
||||
|
||||
|
||||
For example https://example.moodle.net?test[2]=a&test[0]=b will be
|
||||
parsed as ['test' => [2 => 'a', 0 => 'b']]
|
||||
|
||||
|
||||
All parameter values that are not arrays are casted to strings.
|
||||
type: fixed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-77668
|
||||
notes:
|
||||
core_grades:
|
||||
- message: Removed unused grade_edit_tree_column_select class
|
||||
type: removed
|
||||
@@ -1,10 +0,0 @@
|
||||
issueNumber: MDL-77713
|
||||
notes:
|
||||
core_question:
|
||||
- message: >-
|
||||
Deprecated method
|
||||
`mod_quiz\question\bank\qbank_helper::get_version_options` in favour of
|
||||
`core_question\local\bank\version_options::get_version_options` so that
|
||||
the method is in core rather than a module, and can safely be used from
|
||||
anywhere as required.
|
||||
type: deprecated
|
||||
@@ -1,22 +0,0 @@
|
||||
issueNumber: MDL-78090
|
||||
notes:
|
||||
core_question:
|
||||
- message: |
|
||||
Final deprecation of:
|
||||
- core_question\local\bank\action_column_base::class
|
||||
- core_question\local\bank\menu_action_column_base::class
|
||||
- core_question\local\bank\menuable_action::interface
|
||||
- core_question\local\bank\view::print_choose_category_message()
|
||||
- core_question\local\bank\view::display_advanced_search_form()
|
||||
- core_question\local\bank\view::display_showtext_checkbox()
|
||||
- core_question\local\bank\view::init_search_conditions()
|
||||
- core_question\local\bank\view::get_current_category()
|
||||
- core_question\local\bank\view::display_options_form()
|
||||
- core_question\local\bank\view::start_table()
|
||||
- core_question\local\bank\view::end_table()
|
||||
- core_question\statistics\questions\all_calculated_for_qubaid_condition::TIME_TO_CACHE
|
||||
- core_question\statistics\responses\analyser::TIME_TO_CACHE
|
||||
- core_question_bank_renderer::render_category_condition_advanced()
|
||||
- core_question_bank_renderer::render_hidden_condition_advanced()
|
||||
- core_question_bank_renderer::render_category_condition()
|
||||
type: removed
|
||||
@@ -1,17 +0,0 @@
|
||||
issueNumber: MDL-78090
|
||||
notes:
|
||||
qbank:
|
||||
- message: |
|
||||
Final deprecation of:
|
||||
- qbank_managecategories\output\renderer::class
|
||||
- qbank_statistics\helper::calculate_average_question_discriminative_efficiency()
|
||||
- qbank_statistics\helper::calculate_average_question_discrimination_index()
|
||||
- qbank_statistics\helper::get_all_places_where_questions_were_attempted()
|
||||
- qbank_statistics\helper::calculate_average_question_stats_item()
|
||||
- qbank_statistics\helper::calculate_average_question_facility()
|
||||
- qbank_statistics\helper::load_statistics_for_place()
|
||||
- qbank_statistics\helper::extract_item_value()
|
||||
- template qbank_managecategories/category_condition_advanced
|
||||
- template qbank_managecategories/category_condition
|
||||
- template qbank_managecategories/listitem
|
||||
type: removed
|
||||
@@ -1,32 +0,0 @@
|
||||
issueNumber: MDL-78118
|
||||
notes:
|
||||
core_reportbuilder:
|
||||
- message: >-
|
||||
New `get_deprecated_tables` method in base entity, to be overridden when
|
||||
an entity no longer uses a table (due to column/filter re-factoring,
|
||||
etc) in order to avoid breaking third-party reports
|
||||
type: improved
|
||||
- message: >-
|
||||
The following deprecated report entity elements have been removed:
|
||||
|
||||
|
||||
- `comment:context`
|
||||
|
||||
- `comment:contexturl`
|
||||
|
||||
- `enrolment:method` (plus enrolment formatter `enrolment_name` method)
|
||||
|
||||
- `enrolment:role`
|
||||
|
||||
- `file:context`
|
||||
|
||||
- `file:contexturl`
|
||||
|
||||
- `instance:context` (tag)
|
||||
|
||||
- `instance:contexturl` (tag)
|
||||
|
||||
|
||||
Use of the `context` table is also deprecated in the `file` and
|
||||
`instance` (tag) entities
|
||||
type: removed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-78304
|
||||
notes:
|
||||
core_mnet:
|
||||
- message: 'Remove deprecated mnet_peer::get_public_key()'
|
||||
type: removed
|
||||
@@ -1,10 +0,0 @@
|
||||
issueNumber: MDL-78449
|
||||
notes:
|
||||
mod_choice:
|
||||
- message: >
|
||||
The WebService `mod_choice_get_choice_results` has a new parameter
|
||||
`groupid` that allows specifying the group to get the results for. The
|
||||
default behaviour hasn't changed: if a choice has groups and the
|
||||
parameter isn't specified the WebService will return the results for the
|
||||
active group.
|
||||
type: changed
|
||||
@@ -1,9 +0,0 @@
|
||||
issueNumber: MDL-78449
|
||||
notes:
|
||||
mod_choice:
|
||||
- message: >
|
||||
The function `choice_get_response_data` has a new parameter that allows
|
||||
specifying the group to get the results for. The default behaviour
|
||||
hasn't changed: if a choice has groups and the parameter isn't used, the
|
||||
function will return the results for the active group.
|
||||
type: changed
|
||||
@@ -1,10 +0,0 @@
|
||||
issueNumber: MDL-78489
|
||||
notes:
|
||||
core_courseformat:
|
||||
- message: >-
|
||||
Protected function
|
||||
`core_courseformat\output\local\content\section\availability::availability_info()`
|
||||
has been fully removed. Use
|
||||
`core_courseformat\output\local\content\section\availability::get_availability_data()`
|
||||
instead.
|
||||
type: removed
|
||||
@@ -1,10 +0,0 @@
|
||||
issueNumber: MDL-78635
|
||||
notes:
|
||||
mod_imscp:
|
||||
- message: >-
|
||||
Final removal of deprecated `imscp_libxml_disable_entity_loader`
|
||||
function
|
||||
type: removed
|
||||
mod_lti:
|
||||
- message: Final removal of deprecated `lti_libxml_disable_entity_loader` function
|
||||
type: removed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-78706
|
||||
notes:
|
||||
core_repository:
|
||||
- message: Final removal of base `repository` class method `get_file_size`
|
||||
type: removed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-78711
|
||||
notes:
|
||||
core_course:
|
||||
- message: Final deprecation of edit_default_completion()
|
||||
type: removed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-78778
|
||||
notes:
|
||||
core_auth:
|
||||
- message: >-
|
||||
Cas authentication is removed from core and added to the following git repository:
|
||||
https://github.com/moodlehq/moodle-auth_cas
|
||||
type: removed
|
||||
@@ -1,12 +0,0 @@
|
||||
issueNumber: MDL-78780
|
||||
notes:
|
||||
core_grades:
|
||||
- message: >-
|
||||
The previously deprecated `grade_helper::get_lang_string` method has
|
||||
been removed
|
||||
type: removed
|
||||
gradereport:
|
||||
- message: >-
|
||||
The previously deprecated `grade_report::get_lang_string` method has
|
||||
been removed
|
||||
type: removed
|
||||
@@ -1,14 +0,0 @@
|
||||
issueNumber: MDL-78869
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
Final removal of the following constants/methods from the
|
||||
`\core\encyption` class, removing support for OpenSSL fallback:
|
||||
|
||||
|
||||
- `METHOD_OPENSSL`
|
||||
|
||||
- `OPENSSL_CIPHER`
|
||||
|
||||
- `is_sodium_installed`
|
||||
type: removed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-78890
|
||||
notes:
|
||||
gradereport_grader:
|
||||
- message: >-
|
||||
The method `gradereport_grader::get_right_avg_row()` has been finally
|
||||
deprecated and will now throw an exception if called.
|
||||
type: deprecated
|
||||
@@ -1,13 +0,0 @@
|
||||
issueNumber: MDL-78926
|
||||
notes:
|
||||
core_course:
|
||||
- message: Final removal of core_course\output\activity_information
|
||||
type: removed
|
||||
- message: Final deprecation of core_course_renderer\render_activity_information()
|
||||
type: removed
|
||||
core:
|
||||
- message: Final deprecation of core_renderer\activity_information()
|
||||
type: removed
|
||||
mod_wiki:
|
||||
- message: Final deprecation of mod_wiki_renderer\wiki_info()
|
||||
type: removed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-79086
|
||||
notes:
|
||||
core:
|
||||
- message: 'Final removal of `share_activity()` in `core\moodlenet\activity_sender`, please use `share_resource()` instead.'
|
||||
type: removed
|
||||
@@ -1,20 +0,0 @@
|
||||
issueNumber: MDL-79121
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
A new core\ip_utils::normalize_internet_address() method is created to
|
||||
sanitize an IP address, a range of IP addresses, a domain name or a
|
||||
wildcard domain matching pattern.
|
||||
|
||||
|
||||
Moodle previously allowed entries such as 192.168. or .moodle.org for
|
||||
certain variables (eg: $CFG->proxybypass). Since MDL-74289, these
|
||||
formats are no longer allowed. This method converts this informations
|
||||
into an authorized format. For example, 192.168. becomes 192.168.0.0/16
|
||||
and .moodle.org becomes *.moodle.org.
|
||||
|
||||
|
||||
Also a new core\ip_utils::normalize_internet_address_list() method is
|
||||
created. Based on core\ip_utils::normalize_internet_address(), this
|
||||
method normalizes a string containing a series of Internet addresses.
|
||||
type: improved
|
||||
@@ -1,14 +0,0 @@
|
||||
issueNumber: MDL-79124
|
||||
notes:
|
||||
core_user:
|
||||
- message: >-
|
||||
Final removal of the following user preference helpers, please use the
|
||||
`core_user/repository` module instead:
|
||||
|
||||
|
||||
- `user_preference_allow_ajax_update`
|
||||
|
||||
- `M.util.set_user_preference`
|
||||
|
||||
- `lib/ajax/setuserpref.php`
|
||||
type: removed
|
||||
@@ -1,9 +0,0 @@
|
||||
issueNumber: MDL-79162
|
||||
notes:
|
||||
core_badges:
|
||||
- message: |-
|
||||
The following previously deprecated renderer methods have been removed:
|
||||
|
||||
* `print_badge_table_actions`
|
||||
* `render_badge_management`
|
||||
type: removed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-79214
|
||||
notes:
|
||||
mod_folder:
|
||||
- message: >-
|
||||
Method htmllize_tree() has been removed. Please use
|
||||
renderable_tree_elements instead
|
||||
type: removed
|
||||
@@ -1,9 +0,0 @@
|
||||
issueNumber: MDL-79281
|
||||
notes:
|
||||
core_question:
|
||||
- message: >-
|
||||
The `get_bulk_actions()` method on the base `plugin_features_base` class
|
||||
has been changed to allow a qbank view object to be passed through. This
|
||||
is nullable and therefore optional for qbank plugins which don't need
|
||||
to do so.
|
||||
type: improved
|
||||
@@ -1,9 +0,0 @@
|
||||
issueNumber: MDL-79434
|
||||
notes:
|
||||
core_calendar:
|
||||
- message: >
|
||||
Final removal of calendar functions:
|
||||
- calendar_top_controls()
|
||||
- calendar_get_link_previous()
|
||||
- calendar_get_link_next()
|
||||
type: removed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-79434
|
||||
notes:
|
||||
core_calendar:
|
||||
- message: >-
|
||||
Initial deprecation of calendar_sub_month.
|
||||
Use \core_calendar\type_factory::get_calendar_instance()->get_prev_month() instead.
|
||||
type: deprecated
|
||||
@@ -1,12 +0,0 @@
|
||||
issueNumber: MDL-79122
|
||||
notes:
|
||||
core_question:
|
||||
- message: >-
|
||||
Behat steps `behat_qbank_comment::i_should_see_on_the_column`
|
||||
and `behat_qbank_comment::i_click_on_the_row_containing` have been
|
||||
deprecated in favour of the new component named selectors,
|
||||
`qbank_comment > Comment count link` and
|
||||
`qbank_comment > Comment count text` which can be used with the
|
||||
standard `should exist` and `I click on` steps to replace the
|
||||
custom steps.
|
||||
type: deprecated
|
||||
@@ -1,10 +0,0 @@
|
||||
issueNumber: MDL-79907
|
||||
notes:
|
||||
core_grades:
|
||||
- message: >-
|
||||
Final deprecation of
|
||||
grade_structure::get_element_type_string(),
|
||||
grade_structure::get_element_header(),
|
||||
grade_structure::get_element_icon(),
|
||||
grade_structure::get_activity_link()
|
||||
type: removed
|
||||
@@ -1,12 +0,0 @@
|
||||
issueNumber: MDL-80116
|
||||
notes:
|
||||
core_courseformat:
|
||||
- message: >-
|
||||
The state actions section_move and all related functions are final
|
||||
deprecated and cannot be used anymore. Use the newer section_move_after
|
||||
from now on.
|
||||
type: deprecated
|
||||
- message: >-
|
||||
The core_courseformat::base get_section_number and set_section_number
|
||||
are now final deprecated. Use get_sectionum and set_sectionnum instead.
|
||||
type: deprecated
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-80156
|
||||
notes:
|
||||
theme_boost:
|
||||
- message: Remove SCSS deprecated in 4.4
|
||||
type: removed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-80173
|
||||
notes:
|
||||
core_reportbuilder:
|
||||
- message: >-
|
||||
Various Oracle-specific support/workarounds in APIs and component report
|
||||
entities have been removed
|
||||
type: removed
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-80430
|
||||
notes:
|
||||
core_reportbuilder:
|
||||
- message: >-
|
||||
Final removal of support for `get_default_table_aliases` method.
|
||||
Entities must now implement `get_default_tables`, which is now abstract,
|
||||
to define the tables they use
|
||||
type: removed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-80491
|
||||
notes:
|
||||
core_enrol:
|
||||
- message: Final removal of base `enrol_plugin` class method `update_communication`
|
||||
type: removed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-80519
|
||||
notes:
|
||||
theme_boost:
|
||||
- message: >-
|
||||
Added new bs4-compat SCSS file (initially deprecated) to help
|
||||
third-party plugins the migration process from BS4 to BS5
|
||||
type: deprecated
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-80944
|
||||
notes:
|
||||
mod_quiz:
|
||||
- message: Final removal of quiz_delete_override() and quiz_delete_all_overrides()
|
||||
type: removed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-80995
|
||||
notes:
|
||||
tool_mfa:
|
||||
- message: >-
|
||||
The previously deprecated `setup_factor` renderer method has been
|
||||
removed
|
||||
type: removed
|
||||
@@ -1,6 +0,0 @@
|
||||
issueNumber: MDL-81155
|
||||
notes:
|
||||
report_log:
|
||||
- message: >-
|
||||
Support for the $grouplist public member in the report_log_renderable class has been removed.
|
||||
type: removed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-81185
|
||||
notes:
|
||||
enrol_self:
|
||||
- message: >-
|
||||
Final removal of `enrol_self_plugin::get_welcome_email_contact` method,
|
||||
please use `enrol_plugin::get_welcome_message_contact` instead
|
||||
type: removed
|
||||
@@ -1,19 +0,0 @@
|
||||
issueNumber: MDL-81308
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
All uses of the following PHPUnit methods have been removed as these
|
||||
methods are
|
||||
|
||||
deprecated upstream without direct replacement:
|
||||
|
||||
|
||||
- `withConsecutive`
|
||||
|
||||
- `willReturnConsecutive`
|
||||
|
||||
- `onConsecutive`
|
||||
|
||||
|
||||
Any plugin using these methods must update their uses.
|
||||
type: changed
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-81328
|
||||
notes:
|
||||
mod_book:
|
||||
- message: |-
|
||||
The method book_get_nav_classes has been finally
|
||||
deprecated and will now throw an exception if called.
|
||||
type: deprecated
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-81437
|
||||
notes:
|
||||
core_files:
|
||||
- message: |
|
||||
Adds a new ad-hoc task `core_files\task\asynchronous_mimetype_upgrade_task` to upgrade the mimetype of files
|
||||
asynchronously during core upgrades. The upgradelib also comes with a new utility function
|
||||
`upgrade_create_async_mimetype_upgrade_task` for creating said ad-hoc task.
|
||||
type: improved
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-81509
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
Final deprecation of methods `task_base::is_blocking` and
|
||||
`task_base::set_blocking`.
|
||||
type: removed
|
||||
@@ -1,19 +0,0 @@
|
||||
issueNumber: MDL-81520
|
||||
notes:
|
||||
mod_assign:
|
||||
- message: >
|
||||
The unit test for the privacy provider has been marked as final.
|
||||
|
||||
|
||||
A number of core tests had been incorrectly configured to extend this
|
||||
test
|
||||
|
||||
but should instead be extending `\mod_assign\tests\provider_testcase`.
|
||||
|
||||
|
||||
Any community plugins extending the `\mod_assign\privacy\provider_test`
|
||||
test
|
||||
|
||||
class should be updated to extend `\mod_assign\tests\provider_testcase`
|
||||
instead.
|
||||
type: fixed
|
||||
@@ -1,19 +0,0 @@
|
||||
issueNumber: MDL-81521
|
||||
notes:
|
||||
mod_quiz:
|
||||
- message: >
|
||||
The `\mod_quiz\attempt_walkthrough_from_csv_test` unit test has been
|
||||
marked as final and should not be extended by other tests.
|
||||
|
||||
|
||||
All shared functionality has been moved to a new autoloadable test-case:
|
||||
|
||||
`\mod_quiz\tests\attempt_walkthrough_testcase`.
|
||||
|
||||
|
||||
To support this testcase the existing `$files` instance property should be replaced with a
|
||||
new static method, `::get_test_files`.
|
||||
|
||||
Both the existing instance property and the new static method can co-exist.
|
||||
|
||||
type: changed
|
||||
@@ -1,10 +0,0 @@
|
||||
issueNumber: MDL-81664
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
PHPSpreadSheet has been updated to version 4.0.0.
|
||||
|
||||
|
||||
All library usage should be via the Moodle wrapper and no change should
|
||||
be required.
|
||||
type: changed
|
||||
@@ -1,46 +0,0 @@
|
||||
issueNumber: MDL-81714
|
||||
notes:
|
||||
core:
|
||||
- message: >
|
||||
The stored progress API has been updated. The
|
||||
`\core\output\stored_progress_bar` class has
|
||||
|
||||
now has a `store_pending()` method, which will create a record for the
|
||||
stored process, but
|
||||
|
||||
without a start time or progress percentage.
|
||||
|
||||
`\core\task\stored_progress_task_trait` has been updated with a new
|
||||
`initialise_stored_progress()` method,
|
||||
|
||||
which will call `store_pending()` for the task's progress bar. This
|
||||
allows the progress bar to be displayed
|
||||
|
||||
in a "pending" state, to show that a process has been queued but not
|
||||
started.
|
||||
type: improved
|
||||
- message: >
|
||||
A new `\core\output\task_indicator` component has been added to display
|
||||
a progress bar and message
|
||||
|
||||
for a background task using `\core\task\stored_progress_task_trait`. See
|
||||
the "Task indicator"
|
||||
|
||||
page in the component library for usage details.
|
||||
type: improved
|
||||
core_grades:
|
||||
- message: >
|
||||
`grade_regrade_final_grades()` now has an additional `async` parameter,
|
||||
which allows full course
|
||||
|
||||
regrades to be performed in the background. This avoids blocking the
|
||||
user for long periods and
|
||||
|
||||
while making changes to a large course. The actual regrade is performed
|
||||
using the
|
||||
|
||||
`\core_course\task\regrade_final_grades` adhoc task, which calls
|
||||
`grade_regrade_final_grades()`
|
||||
|
||||
with `async: false`.
|
||||
type: improved
|
||||
@@ -1,11 +0,0 @@
|
||||
issueNumber: MDL-81744
|
||||
notes:
|
||||
mod_feedback:
|
||||
- message: >-
|
||||
The 'mode' parameter has been deprecated from 'edit_template_action_bar'
|
||||
and 'templates_table' contructors.
|
||||
type: deprecated
|
||||
- message: >-
|
||||
The 'use_template' template has been removed as it is not needed
|
||||
anymore.
|
||||
type: removed
|
||||
@@ -1,5 +0,0 @@
|
||||
issueNumber: MDL-81745
|
||||
notes:
|
||||
mod_feedback:
|
||||
- message: Added new `mod_feedback_questions_reorder` external function
|
||||
type: improved
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-81825
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
The 'core_renderer::sr_text()' function has been deprecated, use
|
||||
'core_renderer::visually_hidden_text()' instead.
|
||||
type: deprecated
|
||||
@@ -1,9 +0,0 @@
|
||||
issueNumber: MDL-82341
|
||||
notes:
|
||||
core_courseformat:
|
||||
- message: >-
|
||||
All course editing YUI modules are now deprecated. All course formats
|
||||
not using components must migrate before 6.0. Follow the devdocs guide
|
||||
https://moodledev.io/docs/5.0/apis/plugintypes/format/migration to
|
||||
know how to proceed.
|
||||
type: deprecated
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-82342
|
||||
notes:
|
||||
core_course:
|
||||
- message: >-
|
||||
The core_course_edit_module and core_course_edit_section external
|
||||
functions are now deprecated. Use core_courseformat_update_course
|
||||
instead
|
||||
type: deprecated
|
||||
@@ -1,8 +0,0 @@
|
||||
issueNumber: MDL-82342
|
||||
notes:
|
||||
core_course:
|
||||
- message: >-
|
||||
The core_course_get_module external function is now deprecated. Use
|
||||
fragment API using component core_courseformat and fragment cmitem
|
||||
instead
|
||||
type: deprecated
|
||||
@@ -1,10 +0,0 @@
|
||||
issueNumber: MDL-82349
|
||||
notes:
|
||||
core_courseformat:
|
||||
- message: >-
|
||||
A new core_courseformat\base::get_generic_section_name method is
|
||||
created to know how a specific format name the sections.
|
||||
This method is also used by plugins to know how to name the sections
|
||||
instead of using using a direct get_string on "sectionnamer" that
|
||||
may not exists.
|
||||
type: improved
|
||||
@@ -1,7 +0,0 @@
|
||||
issueNumber: MDL-82351
|
||||
notes:
|
||||
core_course:
|
||||
- message: >-
|
||||
The course_format_ajax_support function is now deprecated. Use
|
||||
course_get_format($course)->supports_ajax() instead.
|
||||
type: deprecated
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user