Compare commits

..

186 Commits

Author SHA1 Message Date
Eloy Lafuente (stronk7) 62e799ac84 Moodle release 3.0.1 2015-12-19 19:03:16 +01:00
AMOS bot 2945fe403b Automatically generated installer lang files 2015-12-20 00:05:24 +08:00
Dan Poltawski 6af9a4e21c weekly release 3.0+ 2015-12-18 12:27:09 +00:00
Dan Poltawski 86c5e56baf Merge branch 'install_30_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_30_STABLE 2015-12-18 12:27:07 +00:00
Dan Poltawski abde041907 Merge branch 'MDL-52553_30' of git://github.com/dmonllao/moodle into MOODLE_30_STABLE 2015-12-18 10:56:31 +00:00
David Monllao bba453b9ed MDL-52553 question: Adding missing required constants 2015-12-18 14:43:31 +08:00
Eloy Lafuente (stronk7) 9149eaae18 Merge branch 'wip-mdl-52540-m30' of https://github.com/rajeshtaneja/moodle into MOODLE_30_STABLE 2015-12-17 10:18:58 +01:00
Rajesh Taneja 8a0c8192c8 MDL-52540 behat: mouse events are not handled well with phantomjs 2015-12-17 12:06:06 +08:00
Marina Glancy 74c19577c9 MDL-52286 core: mute erros in adodb destructor 2015-12-15 13:11:45 +08:00
AMOS bot c7231d5235 Automatically generated installer lang files 2015-12-15 00:04:13 +08:00
Dan Poltawski 3cf111c8d3 Merge branch 'MDL-52451-30' of git://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-12-14 10:46:33 +00:00
Dan Poltawski e0518a0940 Merge branch 'wip-MDL-52286-m30' of https://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-12-14 10:24:50 +00:00
Marina Glancy b000642c03 MDL-52286 core: corrected version of adodb to 5.20.1 2015-12-14 18:12:17 +08:00
Marina Glancy aae5800486 MDL-52286 core: several fixes to ADOdb 2015-12-14 18:11:48 +08:00
Dan Poltawski 9ef2475cdd Merge branch 'MDL-52499-30' of git://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-12-14 09:44:19 +00:00
Dan Poltawski 8513fdd0ba Merge branch 'MDL-52487_30' of git://github.com/dmonllao/moodle into MOODLE_30_STABLE 2015-12-14 09:23:14 +00:00
David Monllao 19da0bad13 MDL-52487 rss: PHP 7 compatible error supression
PHP5 used to update error_reporting to 0 on functions called with the
suppress errors operator (@). In PHP7 it is not updated.

Credit to Rajesh Taneja
2015-12-14 15:29:34 +08:00
Marina Glancy ff64873ece MDL-52499 core: link to php7 documentation 2015-12-14 12:56:34 +08:00
Marina Glancy 7bd6de4822 MDL-52451 forms: replace deprecated function 2015-12-14 12:43:54 +08:00
Dan Poltawski 771e49de0c Merge branch 'wip-MDL-52286-m30' of https://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-12-11 09:16:53 +00:00
David Monllao c19896ff95 Merge branch 'MDL-52472-30' of git://github.com/danpoltawski/moodle into MOODLE_30_STABLE 2015-12-11 16:03:28 +08:00
David Monllao 68494c8ef2 Merge branch 'MDL-52361-30' of git://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-12-11 15:20:48 +08:00
David Monllao 0a44e7d8cc MDL-51784 general: Adding missing php4 style constructors
Adding them to maintain backwards compatibility.
2015-12-11 14:56:31 +08:00
Marina Glancy 345f365555 MDL-51784 various: use __construct() for PHP7 compatibility 2015-12-11 13:53:59 +08:00
David Monllao 8079f3343f MDL-52471 setuplib: Removing debugging message from stables 2015-12-11 12:02:00 +08:00
David Monllao 005f7c8b24 Merge branch 'MDL-52471-30' of git://github.com/danpoltawski/moodle into MOODLE_30_STABLE 2015-12-11 11:53:37 +08:00
David Monllao a6f084c9c5 Merge branch 'wip-MDL-52475-m30' of git://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-12-11 11:24:35 +08:00
Tony Levi 0e7a5b346f MDL-52475 core: workaround PHP7 bug #70110 with preg_match 2015-12-11 08:30:37 +08:00
Dan Poltawski 8640c059d8 MDL-52471 tests: simplify setup code
Test code was not using the generator for simple enrollments, couldn't
help to fix when noticed.
2015-12-10 18:00:41 +00:00
Dan Poltawski a3f65f8b5b MDL-52471 completion: fix deprecated use of 'object'
And add a unit test covering completion_can_view_data() without
full course object passed.
2015-12-10 18:00:40 +00:00
Dan Poltawski 88cece4bd7 MDL-52471 quiz: remove broken code question_print_comment_fields() 2015-12-10 18:00:40 +00:00
Dan Poltawski ddfebd633e MDL-52471 setuplib: depreciate class object properly
Previously was marked depricated but not throwwing debugging
2015-12-10 17:57:47 +00:00
Dan Poltawski ac4baac8b9 MDL-52472 travis: remove mongo/memcached testing
At the moment the extensions aren't available in php7 env:
https://github.com/travis-ci/travis-ci/issues/5256

And really its probably not worth the effort/buildtime to
enable them IMO so simpler to just disable.
2015-12-10 17:38:56 +00:00
Dan Poltawski a0ef20cfe4 MDL-52472 travis: enable php7
Relegate 5.6 runs (following the principle of highest and lowest version
only, to conserve travis resources and get faster test completions)
2015-12-10 17:38:55 +00:00
Eloy Lafuente (stronk7) e819a58b6e Merge branch 'MDL-52284-30' of git://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-12-10 16:16:09 +01:00
Dan Poltawski 2adbbcac0a Merge branch 'MDL-52333_30' of git://github.com/dmonllao/moodle into MOODLE_30_STABLE 2015-12-10 11:03:57 +00:00
Dan Poltawski 4e5e185f80 MDL-52285 auth: Add constructors for backwards compat
These are very unlikely to be used but just to be safe..
2015-12-10 10:36:20 +00:00
Dan Poltawski 68cbd0599d Merge branch 'MDL-52285-30' of git://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-12-10 10:36:14 +00:00
David Monllao 2134746dea Merge branch 'MDL-52336-30' of git://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-12-10 17:40:10 +08:00
David Monllao 4d56fe7d60 Merge branch 'MDL-52081-30' of git://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-12-10 17:15:54 +08:00
David Monllao 1c4bc77d44 MDL-52333: qtype_calculated: Adding division by zero test 2015-12-10 16:40:16 +08:00
Tony Levi bc91b91ab1 MDL-52333 qtype: PHP7-compatibility in calculate_raw()
Code also modified by Tim Hunt
2015-12-10 16:40:08 +08:00
David Monllao 9887a4cbae Merge branch 'MDL-52335-30' of git://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-12-10 15:48:28 +08:00
David Monllao 7d8fb1962e Merge branch 'MDL-52438-30' of git://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-12-10 15:32:43 +08:00
David Monllao 07fbf4515c Merge branch 'MDL-52312_m30' of git://github.com/markn86/moodle into MOODLE_30_STABLE 2015-12-10 13:56:43 +08:00
Marina Glancy e81e12cd79 MDL-52285 auth: use __construct() for constructors 2015-12-10 13:51:11 +08:00
Marina Glancy f318ef32bc MDL-52081 forms: indicate usage of grandparent constructor 2015-12-10 13:45:18 +08:00
Marina Glancy 696f3dcc83 MDL-52081 forms: Use __construct() for constructors 2015-12-10 13:45:16 +08:00
Marina Glancy 804a5f59ce MDL-52284 core: PHP7 engine errors have type Throwable 2015-12-10 10:50:12 +08:00
Tony Levi 3f45d8fece MDL-52284 core: compatibility with Exception/Throwable changes in PHP7 2015-12-10 10:50:12 +08:00
AMOS bot dfe989c826 Automatically generated installer lang files 2015-12-10 00:04:26 +08:00
Dan Poltawski 6ace0f59ae weekly release 3.0+ 2015-12-09 12:09:37 +00:00
Dan Poltawski fbf528eb95 Merge branch 'install_30_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_30_STABLE 2015-12-09 12:09:36 +00:00
Eloy Lafuente (stronk7) 220ea7cd09 Merge branch 'MDL-51483_30' of https://github.com/jinhofer/moodle into MOODLE_30_STABLE 2015-12-09 02:22:39 +01:00
Dan Poltawski 6530c22a11 Merge branch 'MDL-51097_m30v1' of https://github.com/sbourget/moodle into MOODLE_30_STABLE 2015-12-08 10:07:46 +00:00
Dan Poltawski ab37a1c815 Merge branch 'MDL-51411_30' of https://github.com/andrewhancox/moodle into MOODLE_30_STABLE 2015-12-08 09:05:47 +00:00
Marina Glancy 75b13bba36 MDL-52438 core: allow Moodle 3.0 to be used with PHP7 2015-12-08 12:54:54 +08:00
Marina Glancy 2cebc825e9 MDL-52361 core: patch Horde library for PHP7 compatibility 2015-12-08 12:48:21 +08:00
Marina Glancy ffe7dd3769 MDL-52336 core: patch PHPExcel for PHP7 compatibility 2015-12-08 12:14:15 +08:00
Marina Glancy f41205a84c MDL-52286 core: update ADODB library to 5.21 2015-12-08 12:04:05 +08:00
Mark Nelson 3046ae85cc MDL-52312 assignfeedback_editpdf: changes to files after FPDI update
1) Change the fpdi_bridge class so that it extends the pdf class,
rather than the TCPDF class.
2) Updated the editpdf/fpdi/readme_moodle.txt file to document the changes.
3) Updated the editpdf/thirdpartylibs.xml to reflect the update to the
FPDI library.
2015-12-08 11:41:22 +08:00
Mark Nelson 536892fd4c MDL-52312 assignfeedback_editpdf: updated the FPDI libary to 1.6.1 2015-12-08 11:41:22 +08:00
Marina Glancy 2cae0d3b15 MDL-52193 questions: add missing require_once 2015-12-07 15:38:02 +00:00
jinhofer bdb1cab348 MDL-51483 lib/moodlelib.php: Added conditional checks to shorten_text 2015-12-07 09:21:26 -06:00
Dan Poltawski 2f1473ac6d Merge branch 'MDL-50258_30' of git://github.com/dmonllao/moodle into MOODLE_30_STABLE 2015-12-07 09:04:39 +00:00
David Monllao 899f21fc26 Merge branch 'MDL-52204-30' of git://github.com/danpoltawski/moodle into MOODLE_30_STABLE 2015-12-07 16:35:09 +08:00
Dan Poltawski c60a3ef656 Merge branch 'MDL-52304_30' of https://github.com/dmonllao/moodle into MOODLE_30_STABLE 2015-12-07 08:21:35 +00:00
Dan Poltawski 8262d6133a MDL-52271 install: don't allow installer to be indexed
Following guidance from:
https://support.google.com/webmasters/answer/93710?hl=en
2015-12-07 15:42:47 +08:00
David Monllao e662941230 Merge branch 'MDL-52370-30' of git://github.com/danpoltawski/moodle into MOODLE_30_STABLE 2015-12-07 15:02:03 +08:00
Dan Poltawski e18b4b0918 MDL-51582 readme: update less building instructions
I forgot to update this after the changes in 490c5cabb4
2015-12-04 16:41:24 +00:00
Eloy Lafuente (stronk7) 14d8971cd6 Merge branch 'MDL-51582-30' of git://github.com/danpoltawski/moodle into MOODLE_30_STABLE 2015-12-04 17:15:20 +01:00
AMOS bot 7c8149d6af Automatically generated installer lang files 2015-12-05 00:04:37 +08:00
Dan Poltawski e64751fb8a MDL-52204 grunt: update to grunt-contrib-jshint v0.11.3
This version of grunt-contrib-jshint itself uses a better fixed
requirement on "~2.8.0" rather than "^2.8.0" which was giving variable
results with some people updating to 2.9-rc1.

See also https://github.com/gruntjs/grunt-contrib-jshint/pull/219
2015-12-04 11:40:28 +00:00
Dan Poltawski 490c5cabb4 MDL-51582 grunt: task improvements
* Introduce the 'css' task - this will give us flexibility to do things
  in future rather than relying on the inbuilt 'less' task (e.g. sourcemap
  generating task which doesn't run by default)

* Introduce 'amd' task which runs only amd tasks

* Introduce 'js' task which runs all JS (amd/shifter) tasks

* Tweak the ordering so that slow shifter always runs last (useful for
  people like me who forget to specif the task)
2015-12-04 10:45:24 +00:00
Dan Poltawski 4269cac414 MDL-51582 css: built with new grunt task 2015-12-04 10:45:24 +00:00
Dan Poltawski cd319f3f2f MDL-51582 grunt: add less complilation support
Previously we were using recess to build bootstrap base as it was what
was used by the original project. But recess is no longer maintained.

Now we use grunt for building js, it makes sense to use it for less too.

(If you really don't want to use grunt, you almost certainly can just
use lessc -x as it uses the same less.js on backend).
2015-12-04 10:44:54 +00:00
Michael Aherne da14ce45dd MDL-52304 media: Allow full screen in Flash player 2015-12-04 10:06:52 +08:00
David Monllao 818f90abd3 MDL-50258 themes: Fix CSS source maps 2015-12-04 09:02:57 +08:00
Eloy Lafuente (stronk7) 13ce9a38a2 weekly release 3.0+ 2015-12-03 19:41:23 +01:00
Eloy Lafuente (stronk7) d5699018b0 Merge branch 'install_30_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_30_STABLE 2015-12-03 19:41:17 +01:00
Eloy Lafuente (stronk7) fbb8579c1a MDL-52141 backup: test restore_log_rule don't modify passed log anymore 2015-12-03 19:01:48 +01:00
David Monllao f248c81e5a Merge branch 'MDL-48004-30' of git://github.com/danpoltawski/moodle into MOODLE_30_STABLE 2015-12-03 19:01:47 +01:00
Andrew Nicols 7d05696b12 Merge branch 'wip-mdl-52357-m30' of https://github.com/rajeshtaneja/moodle into MOODLE_30_STABLE 2015-12-03 19:01:46 +01:00
David Monllao 716c7536cb Merge branch 'MDL-50710-30' of git://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-12-03 19:01:45 +01:00
AMOS bot 4cdf94a602 Automatically generated installer lang files 2015-12-04 00:04:23 +08:00
David Monllao 8dbe8449e0 Merge branch 'MDL-52260-30-2' of git://github.com/xow/moodle into MOODLE_30_STABLE 2015-12-03 11:53:13 +01:00
Eloy Lafuente (stronk7) 4841169211 Merge branch 'MDL-45364-30' of git://github.com/danpoltawski/moodle into MOODLE_30_STABLE 2015-12-03 11:53:12 +01:00
Dan Poltawski 3fe4b72c34 MDL-35982 behat: fix incorrectly backported test
Due to alterations with release/freeze, the lang string change
didn't get backported to 3.x, but the behat did.
2015-12-03 11:53:12 +01:00
Dan Poltawski 3c1e7a0759 MDL-52292 testing: block generator fix
Stop errors when no $options supplied
2015-12-03 11:53:11 +01:00
Dan Poltawski 98ad5c6191 Merge branch 'MDL-52292_30' of git://github.com/timhunt/moodle into MOODLE_30_STABLE 2015-12-03 11:53:10 +01:00
Dan Poltawski 0872bf3175 MDL-26935 behat: add coverage for role renaming in course 2015-12-03 11:53:10 +01:00
Russell Smith 6e8ca8a6ce MDL-52141 backup: Ensure we don't alter the log record when parsing it. 2015-12-03 11:53:09 +01:00
Dan Poltawski e2de9c6b6d Merge branch 'MDL-37308-30' of git://github.com/junpataleta/moodle into MOODLE_30_STABLE 2015-12-03 11:53:09 +01:00
Tony Levi 59b25acd22 MDL-51757 mod_scorm: Remove duplicate function param
convert invalid break calls to exceptions for PHP 7
2015-12-03 11:53:08 +01:00
Marina Glancy f3d159dea1 MDL-52335 tool_uploaduser: php7 compatibility in variable syntax 2015-12-03 09:33:24 +08:00
Marina Glancy 3af4f54efb MDL-52335 mod_data: php7 compatibility in variable syntax 2015-12-03 09:33:24 +08:00
Marina Glancy 6698b06b11 MDL-52335 mod_lti: php7 compatibility in variable syntax 2015-12-03 09:33:24 +08:00
Marina Glancy 545d2c9290 MDL-52335 core: php7 compatibility in XMPP library 2015-12-03 09:33:24 +08:00
Tim Hunt 7cac3f6fe9 MDL-52292 block testing generators: improve the API
* Refactor the block generator base class, to remove the amount
  of duplication required in base classes.

* Change the defaults that are filled in to be a little more natural.

* Make the Behat step 'Given the following "block" exist:' work.
2015-12-01 13:49:24 +00:00
Dan Poltawski db198f615a MDL-52370 role: purge coursecontacts when role is edited
This is perhaps a bit more brutal purge than you'd like, but it only
affects coursecat role edits.
2015-12-01 13:14:55 +00:00
Dan Poltawski bbf7cf2c44 MDL-52370 course: purge contacts cache on role rename
Previously teachers would rename roles and the change would not be
reflected in course listings.
2015-12-01 13:14:54 +00:00
Rajesh Taneja 0f62dd32c7 MDL-52357 behat: add 2 sec delay to ensure window is stable 2015-12-01 15:46:30 +08:00
Ryan Wyllie 67e82ebe0b MDL-52042 autocomplete: close suggestion box
Close the suggestion box when the input loses focus for more
than half a second.
2015-11-30 13:21:07 +00:00
Dan Poltawski ac16eaa936 Merge branch 'MDL-52283-30' of git://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-11-30 11:27:50 +00:00
Dan Poltawski bfe6b3cdd1 Merge branch 'MDL-52332-30' of git://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-11-30 10:22:52 +00:00
David Monllao f1bf39c2f8 Merge branch 'MDL-35982-30' of git://github.com/danpoltawski/moodle into MOODLE_30_STABLE 2015-11-30 15:46:25 +08:00
Marina Glancy 6064114c32 MDL-52332 calendar: load settings using API 2015-11-30 11:47:09 +08:00
Rajesh Taneja e0d74e610a MDL-52279 core_lib: Remove php4 style constructor
filterobject is called from core and as it's
using php4 style constructor, global USER is
not set properly because of deprecation warning
2015-11-30 11:31:33 +08:00
Jun Pataleta 8948d04695 MDL-37308 files: Additional fixes for transparency
Fixed transparency support when resizing and when not resizing.
2015-11-29 20:28:23 -06:00
Jetha Chan 9993c6bcc0 MDL-37308 files: support transparency when not resizing; new unit test 2015-11-29 20:28:23 -06:00
Adrian Greeve 1da82165ef MDL-37308 files: convert_image() now supports transparency. 2015-11-29 20:28:23 -06:00
Marina Glancy cf139e224e MDL-50710 block_glossary_random: more precise sorting 2015-11-30 09:17:03 +08:00
AMOS bot f8bce590ba Automatically generated installer lang files 2015-11-28 00:04:50 +08:00
John Okely 9c461eae4e MDL-52260 user: Fix error when saving profile with locked field 2015-11-27 11:02:45 +08:00
Tony Levi fe6a3c0531 MDL-52283 core: password_hash() deprecation in PHP7 2015-11-27 09:13:08 +08:00
Dan Poltawski 793c80165d MDL-45364 ddimageortext: refine naming & docs of new methods 2015-11-26 19:50:03 +00:00
Andrew Nicols a4f457e111 MDL-45364 qtype_ddimageortext: Fix JS Lint issues 2015-11-26 19:50:03 +00:00
Andrew Nicols 4110561f3e MDL-45364 atto_rtl: Fix JS lint 2015-11-26 19:50:03 +00:00
Dan Poltawski b1549e4127 MDL-48004 question: prevent notices on import screen
If query params were not passed the page would previously display debug
notices as setting the $requirecourseid param to question_edit_setup()
was buggy.
2015-11-26 15:03:43 +00:00
Dan Poltawski 724513e5d8 MDL-35982 behat: delete blog entry coverage 2015-11-26 14:12:22 +00:00
Dan Poltawski c14fbe69f5 MDL-35982 blog: reorder blog delete confirmation
Improves accessibility to be ordered logically in page
2015-11-26 14:12:00 +00:00
Dan Poltawski 8b4e2418f6 MDL-35982 blog: prevent index throwing errors with 0 courseid
Regression from MDL-49845 where Dave made the page not handle courseid = 0
and so now some urls will send you back to a db error..
2015-11-26 14:11:59 +00:00
David Monllao 8568798f76 weekly release 3.0+ 2015-11-26 10:54:50 +08:00
David Monllao 66533b249e Merge branch 'install_30_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_30_STABLE 2015-11-26 10:54:48 +08:00
AMOS bot 0efd3318b2 Automatically generated installer lang files 2015-11-26 00:04:31 +08:00
Andrew Hancox c83c1ee838 MDL-51411 course: Fixed warning when deleting activity with js disabled 2015-11-25 14:39:59 +00:00
Eloy Lafuente (stronk7) 95f83b70b0 MDL-52291 questions: fix upgrade from 2.5 and earlier
Because of some changes performed to the multichoice qtype
for Moodle 2.6, older sites are not able to upgrade properly.

This commits does introduce DB introspection to decide which
table and column names must be used. Nasty but enough.

Note MDL-52298 has been created about to avoid/minimize the
chances of this happening again.
2015-11-25 11:23:34 +00:00
Frederic Massart 53253aea3a MDL-52160 navigation: Mark category and module settings nodes as branch 2015-11-25 13:13:05 +08:00
AMOS bot c244cd14b7 Automatically generated installer lang files 2015-11-25 00:04:24 +08:00
David Monllao a1b7cd7090 Merge branch 'MDL-44330-m30' of git://github.com/NeillM/moodle into MOODLE_30_STABLE 2015-11-24 17:03:42 +08:00
David Monllao e4ffce07cf Merge branch 'MDL-45934-30' of git://github.com/danpoltawski/moodle into MOODLE_30_STABLE 2015-11-24 16:55:56 +08:00
Neill Magill 6b4ce43d1d MDL-44330 Assign: Assignment grading may not display expected student
Before this patch it was possible for the student displayed on the grading page to
not be the student that the user selected to grade. This would occur if:

1) The user had the table ordered by a value that could be modified,
   for example Last modified (submission), Grade, Last modified (grade)
2) Another user performed an action that was recorded in Moodle in the time
   between the user generating the table and clicking on a grade link.

If a user did not notice a different user had been loaded it could result in them giving
a grade to the incorrect user.

This patch ensures that the state of the table is cached every time it is viewed by a user
who has the capability to grade.
2015-11-24 08:41:27 +00:00
Rajesh Taneja 107ab59c29 MDL-50163 behat: wait to ensure elements are placed properly by js. 2015-11-24 12:25:16 +08:00
David Monllao 4cd0bc7ece MDL-52095 themes: Padding to the left on RTL langs 2015-11-23 19:30:56 +01:00
Eloy Lafuente (stronk7) 42952482c9 Merge branch 'MDL-52185-30' of git://github.com/danpoltawski/moodle into MOODLE_30_STABLE 2015-11-23 19:27:03 +01:00
AMOS bot 3804a59fb6 Automatically generated installer lang files 2015-11-24 00:04:33 +08:00
Dan Poltawski 174faea612 Merge branch 'wip-mdl-51063-m30' of https://github.com/rajeshtaneja/moodle into MOODLE_30_STABLE 2015-11-23 11:33:51 +00:00
Tim Hunt dec56ff204 MDL-52166 gradebook: overview report fails with separate groups 2015-11-23 09:44:25 +00:00
David Monllao c6d3f0d107 Merge branch 'MDL-52214-30-crawler' of git://github.com/mudrd8mz/moodle into MOODLE_30_STABLE 2015-11-23 17:01:51 +08:00
David Mudrák 8f36db0be5 MDL-52214 core: Fix case sensitivity in user agent comparison
The previous 2.9 implementation of is_web_crawler() used stripos() in
certain cases. The unit tests re-added in the previous commit revealed
that certain crawlers (such as BaiDuSpider) were not correctly detected
in the new refactored implementation.

It seems lesser evil and safe enough to use /i in the regex search even
though it is not 100% same logic as before - as stripos() was used in
some cases only, not always.
2015-11-23 09:43:20 +01:00
David Mudrák f554e04a27 MDL-52214 core: Fix the is_web_crawler() regression
In MDL-50891, the is_web_crawler() was refactored into a core_useragent
method and the function itself was deprecated. However, there were no
unit tests kept to check the backwards compatible behaviour. It turned
out that the deprecated function leads to PHP fatal error due to a typo.

This patch fixes the typo and brings back the previous unit tests. To be
able to explicitly check the raised debugging message, the test case
now must be subclass of advanced_testcase.

Additionally fixes missing info about the function being deprecated.
2015-11-23 09:42:27 +01:00
David Monllao 20c532caf8 Merge branch 'MDL-52180-30' of git://github.com/danpoltawski/moodle into MOODLE_30_STABLE 2015-11-23 16:40:38 +08:00
David Monllao a1b0938c16 Merge branch 'MDL-52086-30' of git://github.com/bostelm/moodle into MOODLE_30_STABLE 2015-11-23 16:10:42 +08:00
Vadim Dvorovenko 3e1da76d54 MDL-52029 installer: Unstyled installer if display_errors is on
Error in MDL-43839 solution caused inclusion of unexistent file in
install/css.php.
2015-11-23 15:44:12 +08:00
David Monllao e3dcc260fb Merge branch 'MDL-52160-30' of git://github.com/FMCorz/moodle into MOODLE_30_STABLE 2015-11-23 14:09:36 +08:00
David Monllao ae50bef5e6 Merge branch 'MDL-49490-30' of git://github.com/danpoltawski/moodle into MOODLE_30_STABLE 2015-11-23 13:55:48 +08:00
David Monllao 1b1e4d2670 Merge branch 'MDL-43069-30' of git://github.com/danpoltawski/moodle into MOODLE_30_STABLE 2015-11-23 13:32:57 +08:00
Dan Poltawski c31a650aad MDL-37834 assign: avoid newlines in status column of csv
Previously we were generating a CSV file with newlines in it, which
seems to cause problems importing into some versions of MS Excel.

Instead seperate status lines by '-' - this column is only used to
provide context to the teacher in the spreadsheet.
2015-11-20 18:36:49 +00:00
AMOS bot 76943b900c Automatically generated installer lang files 2015-11-21 00:04:19 +08:00
AMOS bot c05a3e254c Automatically generated installer lang files 2015-11-20 00:04:20 +08:00
AMOS bot 63385ff2b5 Merge branch 'MOODLE_30_STABLE' into install_30_STABLE 2015-11-20 00:04:20 +08:00
AMOS bot 42d7bc64f5 Automatically generated installer lang files 2015-11-19 19:14:29 +08:00
Dan Poltawski f0aa5b7e3b weekly release 3.0+ 2015-11-19 09:20:30 +00:00
Marina Glancy 40e33b6e75 MDL-52203 qtype_multianswer: avoid fatal error during upgrade 2015-11-19 09:45:28 +01:00
David Monllao e06768b331 MDL-42571 editpdf: Fix search comments
They are also hidden instead of destroyed.
2015-11-19 07:06:37 +00:00
Dan Poltawski ab58f3ee49 Merge branch 'wip-MDL-52194-m30' of https://github.com/marinaglancy/moodle into MOODLE_30_STABLE 2015-11-19 06:49:52 +00:00
Dan Poltawski 6a30312beb Merge branch 'MDL-52162_30' of git://github.com/dmonllao/moodle into MOODLE_30_STABLE 2015-11-19 06:34:35 +00:00
David Monllao 8c4dbdfd56 MDL-42571 editpdf: Fix comments search and multiple views drawing 2015-11-19 12:56:55 +08:00
Stephen Bourget e9b0443000 MDL-51097 Lesson: allow resume from within a cluster 2015-11-18 23:20:57 -05:00
Henning Bostelmann fab5233096 MDL-52086 output renderers: show subtree only when tab is activated 2015-11-18 16:18:59 +00:00
Pavel Sokolov 2748bf5900 MDL-43069 assign: prevent JS error on grading table
Previously submission comments failed to expand if you hid
the file submissions column.
2015-11-18 15:42:56 +00:00
Dan Poltawski 2adf914723 MDL-45934 backup: do not reset completion enabled flag
The 'completion enabled' flag does not reflect any course data, its a
course setting. Previously when doing a backup deleteting existing
course content this was forced off when it should not have been.
2015-11-18 11:56:30 +00:00
Dan Poltawski 96a2e4086f MDL-49490 assign: can't export to portfolio on non-submission 2015-11-18 09:10:45 +00:00
Marina Glancy a2d94f3d20 MDL-52194 flowplayer: do not check request
$_REQUEST variable may contain cookies in some php configurations. See also MDL-48085
2015-11-18 15:36:48 +08:00
David Monllao 33784bd64b MDL-52162 question: Removing unnecessary dependency
APIs dependencies were correctly removed during MDL-38214 review although
this unnecessary require remained here.

Credit to Marina Glancy.
2015-11-18 13:18:09 +08:00
Rajesh Taneja 4e5efb5c0c MDL-50163 behat: Return subcontext from session
Creating a new context with new mink context
with different session was keeping the session
and was not returning proper session information
2015-11-18 12:19:19 +08:00
rajesh Taneja 5cb434f62b MDL-50163 behat: Restart session after scenario with switch_window
Restart session after a scenario with switch window
is finished. This will close all open browser windows
and open a new browser window
2015-11-18 12:19:15 +08:00
Dan Poltawski 6117291e6b Merge branch 'MDL-52140_30' of git://github.com/stronk7/moodle into MOODLE_30_STABLE 2015-11-17 16:27:52 +00:00
Eloy Lafuente (stronk7) 4e342f5397 MDL-52140 upgrade: add 3.0.0 separation line to all upgrade scripts 2015-11-17 17:11:15 +01:00
Dan Poltawski 6b85e6affd MDL-52185 report: remove ugly bullet points 2015-11-17 15:33:03 +00:00
Dan Poltawski e71116f768 MDL-52180 travis: remove weekly release checker
To prevent false positives
2015-11-17 09:40:30 +00:00
Dan Poltawski a0172a28fc MDL-51988 behat: Add coverage of participation report messaging 2015-11-17 10:23:00 +08:00
Dan Poltawski 60020ccaa9 MDL-51988 behat: select a table by <th> tag 2015-11-17 10:22:59 +08:00
Dan Poltawski 3987e19c3f MDL-51988 report_particiption: do not allow messaging when disabled
Previously the option to send messages when presented regardless of the
site setting.
2015-11-17 10:22:58 +08:00
Russell Smith 2068b31847 MDL-50700 gradereport_overview: Call $PAGE->set_course
To ensure correct blocks are displayed.
2015-11-17 10:02:51 +08:00
Dan Poltawski 9a5f3f7672 MDL-46049 chat: update calendar times on repeating chat sessions
Previously were using the invalid chat id and the calendar entry was not
updated.

Thanks to Andy McGill who worked this out.
2015-11-17 08:55:34 +08:00
Dan Poltawski 75aa0f73db MDL-46049 chat: add missing closing parenthesis 2015-11-17 08:55:32 +08:00
Syam Mohan 2efce29560 MDL-52021 enrollib: Removed repeated capability
'moodle/role:assign' replicated in the permission checking
2015-11-16 16:21:35 +00:00
Frederic Massart a4c6ffd85f MDL-52160 navigation: Really remove module settings empty nodes 2015-11-16 19:44:28 +08:00
Dan Poltawski 184169cd76 MDL-47065 notes: prevent empty spaces added as note
(And updated behat test for this test case)
2015-11-16 15:21:00 +08:00
Jun Pataleta 55d5b4168e MDL-47065 behat: improve verificaton of lack of note 2015-11-16 15:15:25 +08:00
Dan Poltawski 6ccff3ba5d MDL-47065 notes: don't add blank notes
Previously the logic was wrong and was proceeding to add a note
when content was missing.

Also add behat coverage for adding notes to participants (this test is
not perfect, but better than zero coverage we had before).
2015-11-16 15:15:24 +08:00
David Monllao 44f23c7ec1 Merge branch 'MDL-41643-master' of git://github.com/jojoob/moodle into MOODLE_30_STABLE 2015-11-16 14:54:30 +08:00
Dan Poltawski 492b025f5f MDL-50687 phpunit: error early if required locale not installed 2015-11-16 14:26:29 +08:00
Frederic Massart 7f98e36008 MDL-50564 atto: Require atto_menu in atto_plugin 2015-11-16 14:20:41 +08:00
David Monllao f53c016704 Merge branch 'MDL-42571' of git://github.com/blaky/moodle into MOODLE_30_STABLE
Conflicts:
	mod/assign/feedback/editpdf/version.php
2015-11-16 13:41:15 +08:00
32456 changed files with 1212876 additions and 3131909 deletions
+3
View File
@@ -0,0 +1,3 @@
--errors=errors,duplicate-properties
--warnings=known-properties,display-property-grouping,empty-rules,important
--exclude-list=vendor/,lib/editor/tinymce/,lib/yuilib/,theme/bootstrapbase/style/
-249
View File
@@ -1,249 +0,0 @@
{
'plugins': [
'@babel',
'promise',
'jsdoc'
],
'extends': [
'eslint:recommended',
'plugin:promise/recommended'
],
'env': {
'browser': true,
// Enable ES6+ features by default.
// See http://eslint.org/docs/user-guide/configuring#specifying-environments
// Note: The YUI override must exactly match this when disabling the ES6+ version because those features are not supported by Shifter.
'es2020': true,
'amd': true
},
'globals': {
'M': true,
'Y': true
},
// Warn about unused eslint-disable statements.
'reportUnusedDisableDirectives': true,
'rules': {
// See http://eslint.org/docs/rules/ for all rules and explanations of all
// rules.
// === Possible Errors ===
'comma-dangle': 'off',
'no-console': 'error',
'no-empty': 'warn',
'no-extra-parens': 'off',
'no-prototype-builtins': 'off',
'no-unreachable': 'warn',
// === Best Practices ===
// (these mostly match our jshint config)
'array-callback-return': 'warn',
'block-scoped-var': 'warn',
'complexity': 'warn',
'consistent-return': 'warn',
'curly': 'error',
'dot-notation': 'warn',
'no-alert': 'warn',
'no-caller': 'error',
'no-div-regex': 'error',
'no-empty-function': 'warn',
'no-eq-null': 'error',
'no-eval': 'error',
'no-extend-native': 'error',
'no-extra-bind': 'warn',
'no-floating-decimal': 'warn',
'no-global-assign': 'warn',
'no-implied-eval': 'error',
'no-invalid-this': 'error',
'no-iterator': 'error',
'no-labels': 'error',
'no-loop-func': 'error',
'no-multi-spaces': 'warn',
'no-multi-str': 'error',
'no-new-func': 'error',
'no-new-wrappers': 'error',
'no-octal-escape': 'error',
'no-proto': 'error',
'no-redeclare': 'warn',
'no-restricted-globals': ['error', { 'name': 'Notification' }],
'no-return-assign': 'error',
'no-script-url': 'error',
'no-self-compare': 'error',
'no-sequences': 'warn',
'no-throw-literal': 'warn',
'no-unmodified-loop-condition': 'error',
'no-unused-expressions': 'error',
'no-useless-call': 'warn',
'no-useless-escape': 'warn',
'wrap-iife': ['error', 'any'],
// === Variables ===
'no-undef-init': 'error',
'no-unused-vars': ['error', { 'caughtErrors': 'none' }],
// === Stylistic Issues ===
'array-bracket-spacing': 'warn',
'block-spacing': 'warn',
'brace-style': ['warn', '1tbs'],
'camelcase': 'warn',
'capitalized-comments': ['warn', 'always', { 'ignoreConsecutiveComments': true }],
'comma-spacing': ['warn', { 'before': false, 'after': true }],
'comma-style': ['warn', 'last'],
'computed-property-spacing': 'error',
'consistent-this': 'off',
'eol-last': 'off',
'func-call-spacing': ['warn', 'never'],
'func-names': 'off',
'func-style': 'off',
// indent currently not doing well with our wrapping style, so disabled.
'indent': ['off', 4, { 'SwitchCase': 1 }],
'key-spacing': ['warn', { 'beforeColon': false, 'afterColon': true, 'mode': minimum }],
'keyword-spacing': 'warn',
'linebreak-style': ['error', 'unix'],
'lines-around-comment': 'off',
'max-len': ['error', 132],
'max-lines': 'off',
'max-depth': 'warn',
'max-nested-callbacks': ['warn', 5],
'max-params': 'off',
'max-statements': 'off',
'max-statements-per-line': ['warn', { max: 2 }],
'new-cap': ['warn', { 'properties': false }],
'new-parens': 'warn',
'newline-per-chained-call': 'off',
'no-array-constructor': 'off',
'no-bitwise': 'error',
'no-continue': 'off',
'no-inline-comments': 'off',
'no-lonely-if': 'off',
'no-mixed-operators': 'off',
'no-multiple-empty-lines': 'warn',
'no-negated-condition': 'off',
'no-nested-ternary': 'warn',
'no-new-object': 'off',
'no-plusplus': 'off',
'no-tabs': 'error',
'no-ternary': 'off',
'no-trailing-spaces': 'error',
'no-underscore-dangle': 'off',
'no-unneeded-ternary': 'off',
'no-whitespace-before-property': 'warn',
'object-curly-newline': 'off',
'object-curly-spacing': 'warn',
'object-property-newline': 'off',
'one-var': 'off',
'one-var-declaration-per-line': ['warn', 'initializations'],
'operator-assignment': 'off',
'operator-linebreak': 'off',
'padded-blocks': 'off',
'quote-props': ['warn', 'as-needed', {'unnecessary': false, 'keywords': true, 'numbers': true}],
'quotes': 'off',
'semi': 'error',
'semi-spacing': ['warn', {'before': false, 'after': true}],
'sort-vars': 'off',
'space-before-blocks': 'warn',
'space-before-function-paren': ['warn', 'never'],
'space-in-parens': 'warn',
'space-infix-ops': 'warn',
'space-unary-ops': 'warn',
'spaced-comment': 'warn',
'unicode-bom': 'error',
'wrap-regex': 'off',
// === Promises (override plugin:promise/recommended) ===
'promise/always-return': 'warn',
'promise/no-return-wrap': 'warn',
'promise/param-names': 'warn',
'promise/catch-or-return': ['warn', {terminationMethod: ['catch', 'fail', 'always'], allowFinally: true}],
'promise/no-native': 'warn',
'promise/avoid-new': 'warn',
// === Deprecations ===
"no-restricted-properties": ['warn', {
'object': 'M',
'property': 'str',
'message': 'Use "core/str" module or M.util.get_string()'
}],
},
overrides: [
{
files: ["**/yui/src/**/*.js"],
'env': {
// Disable ES6+ for YUI files.
'es2020': false,
},
// Disable some rules which we can't safely define for YUI rollups.
rules: {
'no-undef': 'off',
'no-unused-vars': 'off',
'no-unused-expressions': 'off',
// === JSDocs ===
"jsdoc/check-access": 'off',
"jsdoc/check-alignment": 'off',
"jsdoc/check-param-names": 'off',
"jsdoc/check-property-names": 'off',
"jsdoc/empty-tags": 'off',
"jsdoc/implements-on-classes": 'off',
"jsdoc/multiline-blocks": 'off',
"jsdoc/require-jsdoc": 'off',
"jsdoc/require-param": 'off',
"jsdoc/require-param-name": 'off',
"jsdoc/require-param-type": 'off',
"jsdoc/require-property": 'off',
"jsdoc/require-property-name": 'off',
"jsdoc/require-property-type": 'off',
}
},
{
files: ["**/amd/src/*.js", "**/amd/src/**/*.js", "Gruntfile.js", ".grunt/*.js", ".grunt/tasks/*.js", "jsdoc.conf.js"],
// We're using babel transpiling so use their parser
// for linting.
parser: '@babel/eslint-parser',
// Check AMD with some slightly stricter rules.
rules: {
'no-implicit-globals': 'error',
// Disable all of the rules that have babel versions.
'new-cap': 'off',
// Not using this rule for the time being because it isn't
// compatible with jQuery and ES6.
'no-invalid-this': 'off',
'object-curly-spacing': 'off',
'quotes': 'off',
'semi': 'off',
'no-unused-expressions': 'off',
// Enable all of the babel version of these rules.
'@babel/new-cap': ['warn', { 'properties': false }],
// Not using this rule for the time being because it isn't
// compatible with jQuery and ES6.
'@babel/no-invalid-this': 'off',
'@babel/object-curly-spacing': 'warn',
'@babel/semi': 'error',
'@babel/no-unused-expressions': 'error',
// === Promises ===
// We have Promise now that we're using ES6.
'promise/no-native': 'off',
'promise/avoid-new': 'off',
// === JSDocs ===
'jsdoc/check-access': 'error',
'jsdoc/check-alignment': 1, // Recommended.
'jsdoc/check-param-names': 'error',
'jsdoc/check-property-names': 'error',
'jsdoc/empty-tags': 'error',
'jsdoc/implements-on-classes': 'error',
'jsdoc/multiline-blocks': 'error',
'jsdoc/require-jsdoc': 'error',
'jsdoc/require-param': 'error',
'jsdoc/require-param-name': 'error',
'jsdoc/require-param-type': 'error',
'jsdoc/require-property': 'error',
'jsdoc/require-property-name': 'error',
'jsdoc/require-property-type': 'error'
},
parserOptions: {
'sourceType': 'module',
'requireConfigFile': false
}
}
]
}
-24
View File
@@ -1,24 +0,0 @@
{
"indentation": ["on",{
"Feature": 0,
"Background": 2,
"Scenario": 2,
"Examples": 4,
"example": 6,
"Step": 4,
"given": 4,
"and": 4
}],
"no-dupe-feature-names": "on",
"no-dupe-scenario-names": "off",
"no-empty-file": "on",
"no-files-without-scenarios": "on",
"no-multiple-empty-lines": "on",
"no-partially-commented-tag-lines": "on",
"no-trailing-spaces": "on",
"no-unnamed-features": "on",
"no-unnamed-scenarios": "on",
"no-scenario-outlines-without-examples": "on",
"no-examples-in-scenarios": "on",
"new-line-at-eof": ["on", "yes"]
}
-5
View File
@@ -1,5 +0,0 @@
**/yui/build/** -diff
**/amd/build/** -diff
**.js.map -diff
**-min.js -diff
**.min.js -diff
-2
View File
@@ -1,2 +0,0 @@
# Primary donations pages.
custom: ["https://moodle.com/donations/", "https://fund.moodle.org"]
-7
View File
@@ -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.
--
-6
View File
@@ -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
View File
@@ -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

-31
View File
@@ -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.
-71
View File
@@ -1,71 +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/>.
/**
* Template configuraton file for github actions CI/CD.
*
* @package core
* @copyright 2020 onwards Eloy Lafuente (stronk7) {@link https://stronk7.com}
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// This cannot be used out from a github actions workflow, so just exit.
getenv('GITHUB_WORKFLOW') || die; // phpcs:ignore moodle.Files.MoodleInternal.MoodleInternalGlobalState
unset($CFG);
global $CFG;
$CFG = new stdClass();
$CFG->dbtype = getenv('dbtype');
$CFG->dblibrary = 'native';
$CFG->dbhost = '127.0.0.1';
$CFG->dbname = 'test';
$CFG->dbuser = 'test';
$CFG->dbpass = 'test';
$CFG->prefix = 'm_';
$CFG->dboptions = ['dbcollation' => 'utf8mb4_bin'];
$host = 'localhost';
$CFG->wwwroot = "http://{$host}";
$CFG->dataroot = realpath(dirname(__DIR__)) . '/moodledata';
$CFG->admin = 'admin';
$CFG->directorypermissions = 0777;
// Debug options - possible to be controlled by flag in future.
$CFG->debug = (E_ALL); // DEBUG_DEVELOPER.
$CFG->debugdisplay = 1;
$CFG->debugstringids = 1; // Add strings=1 to url to get string ids.
$CFG->perfdebug = 15;
$CFG->debugpageinfo = 1;
$CFG->allowthemechangeonurl = 1;
$CFG->passwordpolicy = 0;
$CFG->cronclionly = 0;
$CFG->pathtophp = getenv('pathtophp');
$CFG->phpunit_dataroot = realpath(dirname(__DIR__)) . '/phpunitdata';
$CFG->phpunit_prefix = 't_';
define('TEST_EXTERNAL_FILES_HTTP_URL', 'http://localhost:8080');
define('TEST_EXTERNAL_FILES_HTTPS_URL', 'http://localhost:8080');
define('TEST_SESSION_REDIS_HOST', 'localhost');
define('TEST_CACHESTORE_REDIS_TESTSERVERS', 'localhost');
// TODO: add others (solr, mongodb, memcached, ldap...).
// Too much for now: define('PHPUNIT_LONGTEST', true); // Only leaves a few tests out and they are run later by CI.
require_once(__DIR__ . '/lib/setup.php');
-143
View File
@@ -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
-127
View File
@@ -1,127 +0,0 @@
name: Core
on:
push:
branches-ignore:
- main
- MOODLE_[0-9]+_STABLE
tags-ignore:
- v[0-9]+.[0-9]+.[0-9]+*
workflow_dispatch:
inputs:
phpunit_extra_options:
description: Additional options to apply to PHPUnit
required: false
default: ''
env:
php: 8.4
jobs:
Grunt:
runs-on: ubuntu-22.04
steps:
- name: Checking out code
uses: actions/checkout@v4
- name: Configuring node & npm
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Installing node stuff
run: npm install
- name: Running grunt
run: npx grunt
- name: Looking for uncommitted changes
# Add all files to the git index and then run diff --cached to see all changes.
# This ensures that we get the status of all files, including new files.
# We ignore npm-shrinkwrap.json to make the tasks immune to npm changes.
run: |
git add .
git reset -- npm-shrinkwrap.json
git diff --cached --exit-code
PHPUnit:
runs-on: ${{ matrix.os }}
services:
exttests:
image: moodlehq/moodle-exttests
ports:
- 8080:80
redis:
image: redis
ports:
- 6379:6379
strategy:
fail-fast: false
matrix:
include:
# MySQL builds always run with the lowest PHP supported version.
- os: ubuntu-22.04
php: 8.2
extensions:
db: mysqli
# PostgreSQL builds always run with the highest PHP supported version.
- os: ubuntu-22.04
php: 8.4
db: pgsql
steps:
- name: Setting up DB mysql
if: ${{ matrix.db == 'mysqli' }}
uses: moodlehq/mysql-action@v1
with:
collation server: utf8mb4_bin
mysql version: 8.4
mysql database: test
mysql user: test
mysql password: test
use tmpfs: true
tmpfs size: '1024M'
extra conf: --skip-log-bin
- name: Setting up DB pgsql
if: ${{ matrix.db == 'pgsql' }}
uses: m4nu56/postgresql-action@v1
with:
postgresql version: 14
postgresql db: test
postgresql user: test
postgresql password: test
- name: Configuring git vars
uses: rlespinasse/github-slug-action@v4
- name: Setting up PHP ${{ matrix.php }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: ${{ matrix.extensions }}
ini-values: max_input_vars=5000
coverage: none
- name: Checking out code from ${{ env.GITHUB_REF_SLUG }}
uses: actions/checkout@v4
- name: Setting up PHPUnit
env:
dbtype: ${{ matrix.db }}
run: |
echo "pathtophp=$(which php)" >> $GITHUB_ENV # Inject installed pathtophp to env. The template config needs it.
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: Running PHPUnit tests
env:
dbtype: ${{ matrix.db }}
phpunit_options: ${{ secrets.phpunit_options }}
run: vendor/bin/phpunit $phpunit_options ${{ inputs.phpunit_extra_options }}
- name: Git status
run: git status
-23
View File
@@ -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 }}
-149
View File
@@ -1,149 +0,0 @@
name: Windows Testing
on:
workflow_dispatch:
inputs:
phpunit_extra_options:
description: Additional options to apply to PHPUnit
required: false
default: ''
env:
php: 8.4
jobs:
Grunt:
runs-on: windows-latest
steps:
- name: Set git to use LF
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- name: Checking out code
uses: actions/checkout@v4
- name: Configuring node & npm
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Installing node stuff
run: npm ci
- name: Running grunt
run: npx grunt
- name: Looking for uncommitted changes
# Add all files to the git index and then run diff --cached to see all changes.
# This ensures that we get the status of all files, including new files.
# We ignore npm-shrinkwrap.json to make the tasks immune to npm changes.
run: |
git add .
git reset -- npm-shrinkwrap.json
git diff --cached --exit-code
PHPUnit:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
php: 8.4
# 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.
# db: mysqli
db: pgsql
extensions: exif, fileinfo, gd, intl, pgsql, mysql, redis, soap, sodium, zip
- os: windows-latest
php: 8.2
db: pgsql
extensions: exif, fileinfo, gd, intl, pgsql, mysql, redis, soap, sodium, zip
steps:
- name: Setting up DB mysql
if: ${{ matrix.db == 'mysqli' }}
uses: shogo82148/actions-setup-mysql@v1
with:
mysql-version: 8.4
user: test
password: test
- name: Creating DB mysql
if: ${{ matrix.db == 'mysqli' }}
run: mysql --host 127.0.0.1 -utest -ptest -e 'CREATE DATABASE IF NOT EXISTS test COLLATE = utf8mb4_bin;';
- name: Setting up DB pgsql
if: ${{ matrix.db == 'pgsql' }}
run: |
# TODO: Remove these conf. modifications when php74 or php80 are lowest.
# Change to old md5 auth, because php73 does not support it.
# #password_encryption = scram-sha-256
(Get-Content "$env:PGDATA\postgresql.conf"). `
replace('#password_encryption = scram-sha-256', 'password_encryption = md5') | `
Set-Content "$env:PGDATA\postgresql.conf"
(Get-Content "$env:PGDATA\pg_hba.conf"). `
replace('scram-sha-256', 'md5') | `
Set-Content "$env:PGDATA\pg_hba.conf"
$pgService = Get-Service -Name postgresql*
Set-Service -InputObject $pgService -Status running -StartupType automatic
Start-Process -FilePath "$env:PGBIN\pg_isready" -Wait -PassThru
& $env:PGBIN\psql --command="CREATE USER test PASSWORD 'test'" --command="\du"
- name: Creating DB pgsql
if: ${{ matrix.db == 'pgsql' }}
run: |
& $env:PGBIN\createdb --owner=test test
$env:PGPASSWORD = 'test'
& $env:PGBIN\psql --username=test --host=localhost --list test
- name: Configuring git vars
uses: rlespinasse/github-slug-action@v4
- name: Setting up PHP ${{ matrix.php }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: ${{ matrix.extensions }}
ini-values: max_input_vars=5000
coverage: none
- name: Set git to use LF
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- name: Checking out code from ${{ env.GITHUB_REF_SLUG }}
uses: actions/checkout@v4
# Needs to be done after php is available, git configured and Moodle checkout has happened.
- name: Setting up moodle-exttests service
run: |
git clone https://github.com/moodlehq/moodle-exttests.git
nssm install php-built-in C:\tools\php\php.exe -S localhost:8080 -t D:\a\moodle\moodle\moodle-exttests
nssm start php-built-in
- name: Setting up redis service
run: |
choco install redis --version 5.0.7 --no-progress
nssm install redis redis-server
nssm start redis
- name: Setting up PHPUnit
env:
dbtype: ${{ matrix.db }}
shell: bash
run: |
echo "pathtophp=$(which php)" >> $GITHUB_ENV # Inject installed pathtophp to env. The template config needs it.
cp .github/workflows/config-template.php config.php
mkdir ../moodledata
php admin/tool/phpunit/cli/init.php --no-composer-self-update
- name: Running PHPUnit tests
env:
dbtype: ${{ matrix.db }}
phpunit_options: ${{ secrets.phpunit_options }}
run: vendor/bin/phpunit $phpunit_options ${{ inputs.phpunit_extra_options }}
+3 -27
View File
@@ -9,26 +9,15 @@
#
# See gitignore(5) man page for more details
#
# Swap files (vim)
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Temporary files including undo
*~
#
/config.php
/lib/editor/tinymce/extra/tools/temp/
*~
*.swp
/tags
/TAGS
/cscope.*
/.patches/
/.idea/
.phpstorm.*
!/.phpstorm.meta.php/
/.phpstorm.meta.php/*
!/.phpstorm.meta.php/di.php
/nbproject/
CVS
.DS_Store
@@ -36,7 +25,6 @@ CVS
/.project
/.buildpath
/.cache
.phpunit.result.cache
phpunit.xml
# Composer support. Do not ignore composer.json, or composer.lock. These should be shipped by us.
composer.phar
@@ -48,15 +36,3 @@ composer.phar
/lib/yuilib/*/*/*-coverage.js
atlassian-ide-plugin.xml
/node_modules/
/.vscode/
moodle-plugin-ci.phar
.eslintignore
.stylelintignore
/jsdoc
/admin/tool/componentlibrary/docs
/admin/tool/componentlibrary/hugo/site/data/my-index.json
.hugo_build.lock
phpcs.xml
jsconfig.json
UPGRADING-CURRENT.md
.phpunit.cache
-169
View File
@@ -1,169 +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/>.
/**
* This is a babel plugin to add the Moodle module names to the AMD modules
* as part of the transpiling process.
*
* In addition it will also add a return statement for the default export if the
* module is using default exports. This is a highly specific Moodle thing because
* we're transpiling to AMD and none of the existing Babel 7 plugins work correctly.
*
* This will fix the issue where an ES6 module using "export default Foo" will be
* transpiled into an AMD module that returns {default: Foo}; Instead it will now
* just simply return Foo.
*
* Note: This means all other named exports in that module are ignored and won't be
* exported.
*
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
"use strict";
/* eslint-env node */
module.exports = ({template, types}) => {
const fs = require('fs');
const path = require('path');
const cwd = process.cwd();
const ComponentList = require(path.join(process.cwd(), '.grunt', 'components.js'));
/**
* Search the list of components that match the given file name
* and return the Moodle component for that file, if found.
*
* Throw an exception if no matching component is found.
*
* @throws {Error}
* @param {string} searchFileName The file name to look for.
* @return {string} Moodle component
*/
function getModuleNameFromFileName(searchFileName) {
searchFileName = fs.realpathSync(searchFileName);
const relativeFileName = searchFileName.replace(`${cwd}${path.sep}`, '').replace(/\\/g, '/');
const [componentPath, file] = relativeFileName.split('/amd/src/');
const fileName = file.replace('.js', '');
// Check subsystems first which require an exact match.
const componentName = ComponentList.getComponentFromPath(componentPath);
if (componentName) {
return `${componentName}/${fileName}`;
}
// This matches the previous PHP behaviour that would throw an exception
// if it couldn't parse an AMD file.
throw new Error(`Unable to find module name for ${searchFileName} (${componentPath}::${file}}`);
}
/**
* This is heavily inspired by the babel-plugin-add-module-exports plugin.
* See: https://github.com/59naga/babel-plugin-add-module-exports
*
* This is used when we detect a module using "export default Foo;" to make
* sure the transpiled code just returns Foo directly rather than an object
* with the default property (i.e. {default: Foo}).
*
* Note: This means that we can't support modules that combine named exports
* with a default export.
*
* @param {String} path
* @param {String} exportObjectName
*/
function addModuleExportsDefaults(path, exportObjectName) {
const rootPath = path.findParent(path => {
return path.key === 'body' || !path.parentPath;
});
// HACK: `path.node.body.push` instead of path.pushContainer(due doesn't work in Plugin.post).
// This is hardcoded to work specifically with AMD.
rootPath.node.body.push(template(`return ${exportObjectName}.default`)());
}
return {
pre() {
this.seenDefine = false;
this.addedReturnForDefaultExport = false;
},
visitor: {
// Plugin ordering is only respected if we visit the "Program" node.
// See: https://babeljs.io/docs/en/plugins.html#plugin-preset-ordering
//
// We require this to run after the other AMD module transformation so
// let's visit the "Program" node.
Program: {
exit(path) {
path.traverse({
CallExpression(path) {
// If we find a "define" function call.
if (!this.seenDefine && path.get('callee').isIdentifier({name: 'define'})) {
// We only want to modify the first instance of define that we find.
this.seenDefine = true;
// Get the Moodle component for the file being processed.
var moduleName = getModuleNameFromFileName(this.file.opts.filename);
// The function signature of `define()` is:
// define = function (name, deps, callback) {...}
// Ensure that if the moduel supplied its own name that it is replaced.
if (path.node.arguments.length > 0) {
// Ensure that there is only one name.
if (path.node.arguments[0].type === 'StringLiteral') {
// eslint-disable-next-line
console.log(`Replacing module name '${path.node.arguments[0].extra.rawValue}' with ${moduleName}`);
path.node.arguments.shift();
}
}
// Add the module name as the first argument to the define function.
path.node.arguments.unshift(types.stringLiteral(moduleName));
// Add a space after the define function in the built file so that previous versions
// of Moodle will not try to add the module name to the file when it's being served
// by PHP. This forces the regex in PHP to not match for this file.
path.node.callee.name = 'define ';
}
// Check for any Object.defineProperty('exports', 'default') calls.
if (!this.addedReturnForDefaultExport && path.get('callee').matchesPattern('Object.defineProperty')) {
const [identifier, prop] = path.get('arguments');
const objectName = identifier.get('name').node;
const propertyName = prop.get('value').node;
if ((objectName === 'exports' || objectName === '_exports') && propertyName === 'default') {
addModuleExportsDefaults(path, objectName);
this.addedReturnForDefaultExport = true;
}
}
},
AssignmentExpression(path) {
// Check for an exports.default assignments.
if (
!this.addedReturnForDefaultExport &&
(
path.get('left').matchesPattern('exports.default') ||
path.get('left').matchesPattern('_exports.default')
)
) {
const objectName = path.get('left.object.name').node;
addModuleExportsDefaults(path, objectName);
this.addedReturnForDefaultExport = true;
}
}
}, this);
}
}
}
};
};
-432
View File
@@ -1,432 +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/>.
/**
* Helper functions for working with Moodle component names, directories, and sources.
*
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
"use strict";
/* eslint-env node */
/** @var {Object} A list of subsystems in Moodle */
const componentData = {};
/**
* Load details of all moodle modules.
*
* @returns {object}
*/
const fetchComponentData = () => {
const fs = require('fs');
const path = require('path');
const glob = require('glob');
const gruntFilePath = process.cwd();
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';
componentData.pathList.push(process.cwd() + path.sep + 'lib');
for (const [component, thisPath] of Object.entries(components.subsystems)) {
if (thisPath) {
// Prefix "core_" to the front of the subsystems.
componentData.subsystems[thisPath] = `core_${component}`;
componentData.pathList.push(process.cwd() + path.sep + thisPath);
}
}
// 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);
});
};
// Go through each of the plugintypes.
Object.entries(components.plugintypes).forEach(([pluginType, pluginTypePath]) => {
// We don't allow any code in this place..?
glob.sync(`${pluginTypePath}/*/version.php`).forEach(versionPath => {
const componentPath = fs.realpathSync(path.dirname(versionPath));
const componentName = path.basename(componentPath);
const frankenstyleName = `${pluginType}_${componentName}`;
componentData.components[`${pluginTypePath}/${componentName}`] = frankenstyleName;
componentData.pathList.push(componentPath);
// Look for any subplugins.
const subPluginConfigurationFile = `${componentPath}/db/subplugins.json`;
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}`
);
});
} 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;
};
/**
* Get the list of component paths.
*
* @param {string} relativeTo
* @returns {array}
*/
const getComponentPaths = (relativeTo = '') => fetchComponentData().pathList.map(componentPath => {
return componentPath.replace(relativeTo, '');
});
/**
* Get the list of paths to build AMD sources.
*
* @returns {Array}
*/
const getAmdSrcGlobList = () => {
const globList = [];
fetchComponentData().pathList.forEach(componentPath => {
globList.push(`${componentPath}/amd/src/*.js`);
globList.push(`${componentPath}/amd/src/**/*.js`);
});
return globList;
};
/**
* Get the list of paths to build YUI sources.
*
* @param {String} relativeTo
* @returns {Array}
*/
const getYuiSrcGlobList = relativeTo => {
const globList = [];
fetchComponentData().pathList.forEach(componentPath => {
const relativeComponentPath = componentPath.replace(relativeTo, '');
globList.push(`${relativeComponentPath}/yui/src/**/*.js`);
});
return globList;
};
/**
* Get the list of paths to thirdpartylibs.xml.
*
* @param {String} relativeTo
* @returns {Array}
*/
const getThirdPartyLibsList = relativeTo => {
const fs = require('fs');
const path = require('path');
return fetchComponentData().pathList
.map(componentPath => path.relative(relativeTo, componentPath) + '/thirdpartylibs.xml')
.map(componentPath => componentPath.replace(/\\/g, '/'))
.filter(path => fs.existsSync(path))
.sort();
};
/**
* Get the list of thirdparty library paths.
*
* @returns {array}
*/
const getThirdPartyPaths = () => {
const DOMParser = require('@xmldom/xmldom').DOMParser;
const fs = require('fs');
const path = require('path');
const xpath = require('xpath');
const thirdpartyfiles = getThirdPartyLibsList(fs.realpathSync('./'));
const libs = ['node_modules/', 'vendor/'];
const addLibToList = lib => {
if (!lib.match('\\*') && fs.statSync(lib).isDirectory()) {
// Ensure trailing slash on dirs.
lib = lib.replace(/\/?$/, '/');
}
// Look for duplicate paths before adding to array.
if (libs.indexOf(lib) === -1) {
libs.push(lib);
}
};
thirdpartyfiles.forEach(function(file) {
const dirname = path.dirname(file);
const xmlContent = fs.readFileSync(file, 'utf8');
const doc = new DOMParser().parseFromString(xmlContent);
const nodes = xpath.select("/libraries/library/location/text()", doc);
nodes.forEach(function(node) {
let lib = path.posix.join(dirname, node.toString());
addLibToList(lib);
});
});
return libs;
};
/**
* Find the name of the component matching the specified path.
*
* @param {String} path
* @returns {String|null} Name of matching component.
*/
const getComponentFromPath = path => {
const componentList = fetchComponentData().components;
if (componentList.hasOwnProperty(path)) {
return componentList[path];
}
return null;
};
/**
* Check whether the supplied path, relative to the Gruntfile.js, is in a known component.
*
* @param {String} checkPath The path to check. This can be with either Windows, or Linux directory separators.
* @returns {String|null}
*/
const getOwningComponentDirectory = checkPath => {
const path = require('path');
// Fetch all components into a reverse sorted array.
// This ensures that components which are within the directory of another component match first.
const pathList = Object.keys(fetchComponentData().components).sort().reverse();
for (const componentPath of pathList) {
// If the componentPath is the directory being checked, it will be empty.
// If the componentPath is a parent of the directory being checked, the relative directory will not start with ..
if (!path.relative(componentPath, checkPath).startsWith('..')) {
return componentPath;
}
}
return null;
};
/**
* Get the latest tag in a remote GitHub repository.
*
* @param {string} url The remote repository.
* @returns {Array}
*/
const getRepositoryTags = async(url) => {
const gtr = require('git-tags-remote');
try {
const tags = await gtr.get(url);
if (tags !== undefined) {
return tags;
}
} catch (error) {
return [];
}
return [];
};
/**
* Get the list of thirdparty libraries that could be upgraded.
*
* @returns {Array}
*/
const getThirdPartyLibsUpgradable = async() => {
const libraries = getThirdPartyLibsData().filter((library) => !!library.repository);
const upgradableLibraries = [];
const versionCompare = (a, b) => {
if (a === b) {
return 0;
}
const aParts = a.split('.');
const bParts = b.split('.');
for (let i = 0; i < Math.min(aParts.length, bParts.length); i++) {
const aPart = parseInt(aParts[i], 10);
const bPart = parseInt(bParts[i], 10);
if (aPart > bPart) {
// 1.1.0 > 1.0.9
return 1;
} else if (aPart < bPart) {
// 1.0.9 < 1.1.0
return -1;
} else {
// Same version.
continue;
}
}
if (aParts.length > bParts.length) {
// 1.0.1 > 1.0
return 1;
}
// 1.0 < 1.0.1
return -1;
};
for (let library of libraries) {
upgradableLibraries.push(
getRepositoryTags(library.repository).then((tagMap) => {
library.version = library.version.replace(/^v/, '');
const currentVersion = library.version.replace(/moodle-/, '');
const currentMajorVersion = library.version.split('.')[0];
const tags = [...tagMap]
.map((tagData) => tagData[0])
.filter((tag) => !tag.match(/(alpha|beta|rc)/))
.map((tag) => tag.replace(/^v/, ''))
.sort((a, b) => versionCompare(b, a));
if (!tags.length) {
library.warning = "Unable to find any comparable tags.";
return library;
}
library.latestVersion = tags[0];
tags.some((tag) => {
if (!tag) {
return false;
}
// See if the version part matches.
const majorVersion = tag.split('.')[0];
if (majorVersion === currentMajorVersion) {
library.latestSameMajorVersion = tag;
return true;
}
return false;
});
if (versionCompare(currentVersion, library.latestVersion) > 0) {
// Moodle somehow has a newer version than the latest version.
library.warning = `Newer version found: ${currentVersion} > ${library.latestVersion} for ${library.name}`;
return library;
}
if (library.version !== library.latestVersion) {
// Delete version and add it again at the end of the array. That way, current and new will stay closer.
delete library.version;
library.version = currentVersion;
return library;
}
return null;
})
);
}
return (await Promise.all(upgradableLibraries)).filter((library) => !!library);
};
/**
* Get the list of thirdparty libraries.
*
* @returns {Array}
*/
const getThirdPartyLibsData = () => {
const DOMParser = require('@xmldom/xmldom').DOMParser;
const fs = require('fs');
const xpath = require('xpath');
const path = require('path');
const libraryList = [];
const libraryFields = [
'location',
'name',
'version',
'repository',
];
const thirdpartyfiles = getThirdPartyLibsList(fs.realpathSync('./'));
thirdpartyfiles.forEach(function(libraryPath) {
const xmlContent = fs.readFileSync(libraryPath, 'utf8');
const doc = new DOMParser().parseFromString(xmlContent);
const libraries = xpath.select("/libraries/library", doc);
for (const library of libraries) {
const libraryData = [];
for (const field of libraryFields) {
libraryData[field] = xpath.select(`${field}/text()`, library)?.toString();
}
libraryData.location = path.join(path.dirname(libraryPath), libraryData.location);
libraryList.push(libraryData);
}
});
return libraryList.sort((a, b) => a.location.localeCompare(b.location));
};
module.exports = {
fetchComponentData,
getAmdSrcGlobList,
getComponentFromPath,
getComponentPaths,
getOwningComponentDirectory,
getYuiSrcGlobList,
getThirdPartyLibsList,
getThirdPartyPaths,
getThirdPartyLibsUpgradable,
};
-19
View File
@@ -1,19 +0,0 @@
# Moodle JavaScript Documentation
```
.-..-.
_____ | || |
/____/-.---_ .---. .---. .-.| || | .---.
| | _ _ |/ _ \/ _ \/ _ || |/ __ \
* | | | | | || |_| || |_| || |_| || || |___/
|_| |_| |_|\_____/\_____/\_____||_|\_____)
Moodle - the world's open source learning platform
```
## About
This generated documentation includes API documentation for JavaScript written in the AMD and ES2015 module formats within Moodle.
## Related information
See [https://moodledev.io](https://moodledev.io) for other related Developer Documentation.
-131
View File
@@ -1,131 +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/>.
/**
* Helper functions for working with Moodle component names, directories, and sources.
*
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
"use strict";
/* eslint-env node */
// Do not include any plugins as stanard.
const plugins = [];
plugins.push('plugins/markdown');
/**
* Get the source configuration.
*
* @return {Object}
*/
const getSource = () => {
const glob = require('glob');
const path = require('path');
const ComponentList = require(path.resolve('.grunt/components.js'));
const thirdPartyPaths = ComponentList.getThirdPartyPaths();
const source = {
include: [],
includePattern: ".+\\.js$",
};
let includeList = [];
ComponentList.getAmdSrcGlobList().forEach(async pattern => {
includeList.push(...glob.sync(pattern));
});
const cwdLength = process.cwd().length + 1;
includeList.forEach(path => {
if (source.include.indexOf(path) !== -1) {
// Ensure no duplicates.
return;
}
const relPath = path.substring(cwdLength);
if (thirdPartyPaths.indexOf(relPath) !== -1) {
return;
}
source.include.push(path);
});
source.include.push('.grunt/jsdoc/README.md');
return source;
};
const tags = {
// Allow the use of unknown tags.
// We have a lot of legacy uses of these.
allowUnknownTags: true,
// We make use of jsdoc and closure dictionaries as standard.
dictionaries: [
'jsdoc',
'closure',
],
};
// Template configuraiton.
const templates = {
cleverLinks: false,
monospaceLinks: false,
};
module.exports = {
opts: {
destination: "./jsdoc/",
template: "node_modules/docdash",
},
plugins,
recurseDepth: 10,
source: getSource(),
sourceType: 'module',
tags,
templates,
docdash: {
collapse: true,
search: true,
sort: true,
sectionOrder: [
"Namespaces",
"Modules",
"Events",
"Classes",
"Externals",
"Mixins",
"Tutorials",
"Interfaces"
],
"menu": {
"Developer Docs": {
href: "https://moodledev.io",
target: "_blank",
"class": "menu-item",
id: "devdocs"
},
"MDN Docs": {
href: "https://developer.mozilla.org/en-US/docs/Web/JavaScript",
target: "_blank",
"class": "menu-item",
id: "mdndocs",
},
},
typedefs: true,
},
};
-138
View File
@@ -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);
};
-85
View File
@@ -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);
};
-357
View File
@@ -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);
}
};
-230
View File
@@ -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";
}
-30
View File
@@ -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;
-194
View File
@@ -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}`))
);
};
-80
View 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;
-153
View File
@@ -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',
});
-142
View File
@@ -1,142 +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/>.
/* jshint node: true, browser: false */
/* eslint-env node */
/**
* Component Library build tasks.
*
* @copyright 2021 Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
module.exports = grunt => {
/**
* Get a child path of the component library.
*
* @param {string} path
* @returns {string}
*/
const getCLPath = path => `admin/tool/componentlibrary/${path}`;
/**
* Get a spawn handler.
*
* This is a generic function to write the spawn output, and then to exit if required and mark the async task as
* complete.
*
* @param {Promise} done
* @returns {function}
*/
const getSpawnHandler = done => (error, result, code) => {
grunt.log.write(result);
if (error) {
grunt.log.error(result.stdout);
process.exit(code);
}
done();
};
/**
* Spawn a function against Node with the provided args.
*
* @param {array} args
* @returns {object}
*/
const spawnNodeCall = (args) => grunt.util.spawn({
cmd: 'node',
args,
}, getSpawnHandler(grunt.task.current.async()));
/**
* Build the docs using Hugo.
*
* @returns {Object} Reference to the spawned task
*/
const docsBuild = () => spawnNodeCall([
'node_modules/hugo-bin/cli.js',
'--config', getCLPath('config.yml'),
'--cleanDestinationDir',
]);
/**
* Build the docs index using the hugo-lunr-indexer.
*
* @returns {Object} Reference to the spawned task
*/
const indexBuild = () => spawnNodeCall([
'node_modules/hugo-lunr-indexer/bin/hli.js',
'-i', getCLPath('content/**'),
'-o', getCLPath('hugo/site/data/my-index.json'),
'-l', 'yaml',
'-d', '---',
]);
/**
* Build the hugo CSS.
*
* @returns {Object} Reference to the spawned task
*/
const cssBuild = () => spawnNodeCall([
'node_modules/sass/sass.js',
'--style', 'expanded',
'--source-map',
'--embed-sources',
'--precision', 6,
'--load-path', process.cwd(),
getCLPath('hugo/scss/docs.scss'),
getCLPath('hugo/dist/css/docs.css'),
]);
// Register the various component library tasks.
grunt.registerTask('componentlibrary:docsBuild', 'Build the component library', docsBuild);
grunt.registerTask('componentlibrary:cssBuild', 'Build the component library', cssBuild);
grunt.registerTask('componentlibrary:indexBuild', 'Build the component library', indexBuild);
grunt.registerTask('componentlibrary', 'Build the component library', [
'componentlibrary:docsBuild',
'componentlibrary:cssBuild',
'componentlibrary:indexBuild',
]);
grunt.config.merge({
watch: {
componentLibraryDocs: {
files: [
getCLPath('content/**/*.md'),
getCLPath('hugo'),
],
tasks: ['componentlibrary:docsBuild', 'componentlibrary:indexBuild'],
},
componentLibraryCSS: {
files: [
getCLPath('hugo/scss/**/*.scss'),
'hugo',
],
tasks: ['componentlibrary:cssBuild'],
},
},
});
// Add the 'componentlibrary' task as a startup task.
grunt.moodleEnv.startupTasks.push('componentlibrary');
return {
docsBuild,
cssBuild,
indexBuild,
};
};
-64
View File
@@ -1,64 +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/>.
/* jshint node: true, browser: false */
/* eslint-env node */
/**
* @copyright 2021 Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
module.exports = grunt => {
const files = grunt.moodleEnv.files;
// Project configuration.
grunt.config.merge({
eslint: {
// Even though warnings dont stop the build we don't display warnings by default because
// at this moment we've got too many core warnings.
// To display warnings call: grunt eslint --show-lint-warnings
// To fail on warnings call: grunt eslint --max-lint-warnings=0
// Also --max-lint-warnings=-1 can be used to display warnings but not fail.
options: {
quiet: (!grunt.option('show-lint-warnings')) && (typeof grunt.option('max-lint-warnings') === 'undefined'),
maxWarnings: ((typeof grunt.option('max-lint-warnings') !== 'undefined') ? grunt.option('max-lint-warnings') : -1)
},
// Check AMD src files.
amd: {src: files ? files : grunt.moodleEnv.amdSrc},
// Check YUI module source files.
yui: {src: files ? files : grunt.moodleEnv.yuiSrc},
},
});
grunt.loadNpmTasks('grunt-eslint');
// On watch, we dynamically modify config to build only affected files. This
// method is slightly complicated to deal with multiple changed files at once (copied
// from the grunt-contrib-watch readme).
let changedFiles = Object.create(null);
const onChange = grunt.util._.debounce(function() {
const files = Object.keys(changedFiles);
grunt.config('eslint.amd.src', files);
grunt.config('eslint.yui.src', files);
changedFiles = Object.create(null);
}, 200);
grunt.event.on('watch', (action, filepath) => {
changedFiles[filepath] = action;
onChange();
});
};
-92
View File
@@ -1,92 +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/>.
/* jshint node: true, browser: false */
/* eslint-env node */
/**
* @copyright 2021 Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
module.exports = grunt => {
/**
* Get the list of feature files to pass to the gherkin linter.
*
* @returns {Array}
*/
const getGherkinLintTargets = () => {
if (grunt.moodleEnv.files) {
// Specific files were requested. Only check these.
return grunt.moodleEnv.files;
}
if (grunt.moodleEnv.inComponent) {
return [`${grunt.moodleEnv.runDir}/tests/behat/*.feature`];
}
return ['**/tests/behat/*.feature'];
};
const handler = function() {
const done = this.async();
const options = grunt.config('gherkinlint.options');
// Grab the gherkin-lint linter and required scaffolding.
const linter = require('gherkin-lint/dist/linter.js');
const featureFinder = require('gherkin-lint/dist/feature-finder.js');
const configParser = require('gherkin-lint/dist/config-parser.js');
const formatter = require('gherkin-lint/dist/formatters/stylish.js');
// Run the linter.
return linter.lint(
featureFinder.getFeatureFiles(grunt.file.expand(options.files)),
configParser.getConfiguration(configParser.defaultConfigFileName)
)
.then(results => {
// Print the results out uncondtionally.
formatter.printResults(results);
return results;
})
.then(results => {
// Report on the results.
// The done function takes a bool whereby a falsey statement causes the task to fail.
return results.every(result => result.errors.length === 0);
})
.then(done); // eslint-disable-line promise/no-callback-in-promise
};
grunt.registerTask('gherkinlint', 'Run gherkinlint against the current directory', handler);
grunt.config.set('gherkinlint', {
options: {
files: getGherkinLintTargets(),
}
});
grunt.config.merge({
watch: {
gherkinlint: {
files: [grunt.moodleEnv.inComponent ? 'tests/behat/*.feature' : '**/tests/behat/*.feature'],
tasks: ['gherkinlint'],
},
},
});
// Add the 'gherkinlint' task as a startup task.
grunt.moodleEnv.startupTasks.push('gherkinlint');
return handler;
};
-100
View File
@@ -1,100 +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/>.
/* jshint node: true, browser: false */
/* eslint-env node */
/**
* @copyright 2021 Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
module.exports = grunt => {
/**
* Generate the PHPCS configuration.
*
* @param {Object} thirdPartyPaths
*/
const phpcsIgnore = (thirdPartyPaths) => {
const {toXML} = require('jstoxml');
const config = {
_name: 'ruleset',
_attrs: {
name: "MoodleCore",
},
_content: [
{
rule: {
_attrs: {
ref: './phpcs.xml.dist',
},
},
},
],
};
thirdPartyPaths.forEach(library => {
config._content.push({
'exclude-pattern': library,
});
});
grunt.file.write('phpcs.xml', toXML(config, {
header: true,
indent: ' ',
}) + "\n");
};
/**
* Generate ignore files (utilising thirdpartylibs.xml data)
*/
const handler = function() {
const path = require('path');
const ComponentList = require(path.join(process.cwd(), '.grunt', 'components.js'));
// An array of paths to third party directories.
const thirdPartyPaths = ComponentList.getThirdPartyPaths();
// Generate .eslintignore.
const eslintIgnores = [
'# Generated by "grunt ignorefiles"',
// Do not ignore the .grunt directory.
'!/.grunt',
// Ignore all yui/src meta directories and build directories.
'*/**/yui/src/*/meta/',
'*/**/build/',
].concat(thirdPartyPaths);
grunt.file.write('.eslintignore', eslintIgnores.join('\n') + '\n');
// Generate .stylelintignore.
const stylelintIgnores = [
'# Generated by "grunt ignorefiles"',
'**/yui/build/*',
'theme/boost/style/moodle.css',
'theme/classic/style/moodle.css',
'jsdoc/styles/*.css',
'admin/tool/componentlibrary/hugo/dist/css/docs.css',
].concat(thirdPartyPaths);
grunt.file.write('.stylelintignore', stylelintIgnores.join('\n') + '\n');
phpcsIgnore(thirdPartyPaths);
};
grunt.registerTask('ignorefiles', 'Generate ignore files for linters', handler);
return handler;
};
-215
View File
@@ -1,215 +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/>.
/* jshint node: true, browser: false */
/* eslint-env node */
/**
* @copyright 2021 Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Function to generate the destination for the minification 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
*
* @param {String} destPath the current destination
* @param {String} srcPath the matched src path
* @return {String} The rewritten destination path.
*/
const babelRename = function(destPath, srcPath) {
destPath = srcPath.replace(`amd/src`, `amd/build`);
destPath = destPath.replace(/\.js$/, '.min.js');
return destPath;
};
module.exports = grunt => {
// Load the Ignorefiles tasks.
require('./ignorefiles')(grunt);
// Load the Shifter tasks.
require('./shifter')(grunt);
// Load ESLint.
require('./eslint')(grunt);
// Load jsconfig.
require('./jsconfig')(grunt);
// Load JSDoc.
require('./jsdoc')(grunt);
const path = require('path');
// Register JS tasks.
grunt.registerTask('yui', ['eslint:yui', 'shifter']);
grunt.registerTask('amd', ['ignorefiles', 'eslint:amd', 'rollup']);
grunt.registerTask('js', ['amd', 'yui']);
// Register NPM tasks.
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-rollup');
const babelTransform = require('@babel/core').transform;
const babel = (options = {}) => {
return {
name: 'babel',
transform: (code, id) => {
grunt.log.debug(`Transforming ${id}`);
options.filename = id;
const transformed = babelTransform(code, options);
return {
code: transformed.code,
map: transformed.map
};
}
};
};
// Note: We have to use a rate limit plugin here because rollup runs all tasks asynchronously and in parallel.
// When we kick off a full run, if we kick off a rollup of every file this will fork-bomb the machine.
// To work around this we use a concurrent Promise queue based on the number of available processors.
const rateLimit = () => {
const queue = [];
let queueRunner;
const startQueue = () => {
if (queueRunner) {
return;
}
queueRunner = setTimeout(() => {
const limit = Math.max(1, require('os').cpus().length / 2);
grunt.log.debug(`Starting rollup with queue size of ${limit}`);
runQueue(limit);
}, 100);
};
// 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();
});
};
return {
name: 'ratelimit',
// The options hook is run in parallel.
// We can return an unresolved Promise which is queued for later resolution.
options: async(options) => {
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(', ')}`);
runQueue();
},
};
};
const terser = require('rollup-plugin-terser').terser;
grunt.config.merge({
rollup: {
options: {
format: 'esm',
dir: 'output',
sourcemap: true,
treeshake: false,
context: 'window',
plugins: [
rateLimit(),
babel({
sourceMaps: true,
comments: false,
compact: false,
plugins: [
'transform-es2015-modules-amd-lazy',
'system-import-transformer',
// This plugin modifies the Babel transpiling for "export default"
// so that if it's used then only the exported value is returned
// by the generated AMD module.
//
// It also adds the Moodle plugin name to the AMD module definition
// so that it can be imported as expected in other modules.
path.resolve('.grunt/babel-plugin-add-module-to-define.js')
],
presets: [
['@babel/preset-env', {
modules: false,
useBuiltIns: false
}]
]
}),
terser({
// Do not mangle variables.
// Makes debugging easier.
mangle: false,
}),
],
},
dist: {
files: [{
expand: true,
src: grunt.moodleEnv.files ? grunt.moodleEnv.files : grunt.moodleEnv.amdSrc,
rename: babelRename
}],
},
},
});
grunt.config.merge({
watch: {
amd: {
files: grunt.moodleEnv.inComponent
? ['amd/src/*.js', 'amd/src/**/*.js']
: ['**/amd/src/**/*.js'],
tasks: ['amd']
},
},
});
// Add the 'js' task as a startup task.
grunt.moodleEnv.startupTasks.push('js');
// On watch, we dynamically modify config to build only affected files. This
// method is slightly complicated to deal with multiple changed files at once (copied
// from the grunt-contrib-watch readme).
let changedFiles = Object.create(null);
const onChange = grunt.util._.debounce(function() {
const files = Object.keys(changedFiles);
grunt.config('rollup.dist.files', [{expand: true, src: files, rename: babelRename}]);
changedFiles = Object.create(null);
}, 200);
grunt.event.on('watch', function(action, filepath) {
changedFiles[filepath] = action;
onChange();
});
return {
babelRename,
};
};
-53
View File
@@ -1,53 +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/>.
/* jshint node: true, browser: false */
/* eslint-env node */
/**
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const configuration = {
compilerOptions: {
baseUrl: ".",
paths: {
},
target: "es2015",
allowSyntheticDefaultImports: false,
},
exclude: [
"node_modules",
],
include: [],
};
module.exports = (grunt) => {
const handler = () => {
const jsconfigData = Object.assign({}, configuration);
const path = require('path');
const {fetchComponentData} = require(path.join(process.cwd(), '.grunt', 'components.js'));
const componentData = fetchComponentData().components;
for (const [thisPath, component] of Object.entries(componentData)) {
jsconfigData.compilerOptions.paths[`${component}/*`] = [`${thisPath}/amd/src/*`];
jsconfigData.include.push(`${thisPath}/amd/src/**/*`);
}
grunt.file.write('jsconfig.json', JSON.stringify(jsconfigData, null, " ") + "\n");
};
grunt.registerTask('jsconfig', 'Generate a jsconfig configuration compatible with the LSP', handler);
};
-52
View File
@@ -1,52 +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/>.
/* jshint node: true, browser: false */
/* eslint-env node */
/**
* @copyright 2021 Andrew Nicols
* @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();
});
});
};
-40
View File
@@ -1,40 +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/>.
/* jshint node: true, browser: false */
/* eslint-env node */
/**
* @copyright 2021 Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
module.exports = grunt => {
grunt.loadNpmTasks('grunt-sass');
grunt.config.merge({
sass: {
dist: {
files: {
"theme/boost/style/moodle.css": "theme/boost/scss/preset/default.scss",
"theme/classic/style/moodle.css": "theme/classic/scss/classicgrunt.scss"
}
},
options: {
implementation: require('sass'),
includePaths: ["theme/boost/scss/", "theme/classic/scss/"]
}
},
});
};
-155
View File
@@ -1,155 +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/>.
/* jshint node: true, browser: false */
/* eslint-env node */
/**
* @copyright 2021 Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/* eslint-env node */
module.exports = grunt => {
/**
* Shifter task. Is configured with a path to a specific file or a directory,
* in the case of a specific file it will work out the right module to be built.
*
* Note that this task runs the invidiaul shifter jobs async (becase it spawns
* so be careful to to call done().
*/
const handler = function() {
const done = this.async();
const options = grunt.config('shifter.options');
const async = require('async');
const path = require('path');
// Run the shifter processes one at a time to avoid confusing output.
async.eachSeries(options.paths, function(src, filedone) {
var args = [];
args.push(path.normalize(process.cwd() + '/node_modules/shifter/bin/shifter'));
// Always ignore the node_modules directory.
args.push('--excludes', 'node_modules');
// Determine the most appropriate options to run with based upon the current location.
if (grunt.file.isMatch('**/yui/**/*.js', src)) {
// When passed a JS file, build our containing module (this happen with
// watch).
grunt.log.debug('Shifter passed a specific JS file');
src = path.dirname(path.dirname(src));
options.recursive = false;
} else if (grunt.file.isMatch('**/yui/src', src)) {
// When in a src directory --walk all modules.
grunt.log.debug('In a src directory');
args.push('--walk');
options.recursive = false;
} else if (grunt.file.isMatch('**/yui/src/*', src)) {
// When in module, only build our module.
grunt.log.debug('In a module directory');
options.recursive = false;
} else if (grunt.file.isMatch('**/yui/src/*/js', src)) {
// When in module src, only build our module.
grunt.log.debug('In a source directory');
src = path.dirname(src);
options.recursive = false;
}
if (grunt.option('watch')) {
grunt.fail.fatal('The --watch option has been removed, please use `grunt watch` instead');
}
// Add the stderr option if appropriate
if (grunt.option('verbose')) {
args.push('--lint-stderr');
}
if (grunt.option('no-color')) {
args.push('--color=false');
}
var execShifter = function() {
grunt.log.ok("Running shifter on " + src);
grunt.util.spawn({
cmd: "node",
args: args,
opts: {cwd: src, stdio: 'inherit', env: process.env}
}, function(error, result, code) {
if (code) {
grunt.fail.fatal('Shifter failed with code: ' + code);
} else {
grunt.log.ok('Shifter build complete.');
filedone();
}
});
};
// Actually run shifter.
if (!options.recursive) {
execShifter();
} else {
// Check that there are yui modules otherwise shifter ends with exit code 1.
if (grunt.file.expand({cwd: src}, '**/yui/src/**/*.js').length > 0) {
args.push('--recursive');
execShifter();
} else {
grunt.log.ok('No YUI modules to build.');
filedone();
}
}
}, done);
};
// Register the shifter task.
grunt.registerTask('shifter', 'Run Shifter against the current directory', handler);
// Configure it.
grunt.config.set('shifter', {
options: {
recursive: true,
// Shifter takes a relative path.
paths: grunt.moodleEnv.files ? grunt.moodleEnv.files : [grunt.moodleEnv.runDir]
}
});
grunt.config.merge({
watch: {
yui: {
files: grunt.moodleEnv.inComponent
? ['yui/src/*.json', 'yui/src/**/*.js']
: ['**/yui/src/**/*.js'],
tasks: ['yui']
},
},
});
// On watch, we dynamically modify config to build only affected files. This
// method is slightly complicated to deal with multiple changed files at once (copied
// from the grunt-contrib-watch readme).
let changedFiles = Object.create(null);
const onChange = grunt.util._.debounce(function() {
const files = Object.keys(changedFiles);
grunt.config('shifter.options.paths', files);
changedFiles = Object.create(null);
}, 200);
grunt.event.on('watch', (action, filepath) => {
changedFiles[filepath] = action;
onChange();
});
return handler;
};
-46
View File
@@ -1,46 +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/>.
/* jshint node: true, browser: false */
/* eslint-env node */
/**
* @copyright 2021 Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
module.exports = grunt => {
/**
* Generate ignore files (utilising thirdpartylibs.xml data)
*/
const handler = function() {
const path = require('path');
// Are we in a YUI directory?
if (path.basename(path.resolve(grunt.moodleEnv.cwd, '../../')) == 'yui') {
grunt.task.run('yui');
// Are we in an AMD directory?
} else if (grunt.moodleEnv.inAMD) {
grunt.task.run('amd');
} else {
// Run all of the requested startup tasks.
grunt.moodleEnv.startupTasks.forEach(taskName => grunt.task.run(taskName));
}
};
// Register the startup task.
grunt.registerTask('startup', 'Run the correct tasks for the current directory', handler);
return handler;
};
-35
View File
@@ -1,35 +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/>.
/* jshint node: true, browser: false */
/* eslint-env node */
/**
* @copyright 2021 Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
module.exports = grunt => {
// Load the ignorefiles tasks.
require('./ignorefiles')(grunt);
// Load the Style Lint tasks.
require('./stylelint')(grunt);
// Load the SASS tasks.
require('./sass')(grunt);
// Add the 'css' task as a startup task.
grunt.moodleEnv.startupTasks.push('css');
};
-187
View File
@@ -1,187 +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/>.
/* jshint node: true, browser: false */
/* eslint-env node */
/**
* @copyright 2021 Andrew Nicols
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
module.exports = grunt => {
const getCssConfigForFiles = files => {
return {
stylelint: {
css: {
// Use a fully-qualified path.
src: files,
options: {
quietDeprecationWarnings: true,
configOverrides: {
rules: {
// These rules have to be disabled in .stylelintrc for scss compat.
"at-rule-no-unknown": true,
}
}
}
},
},
};
};
const getScssConfigForFiles = files => {
return {
stylelint: {
scss: {
options: {
quietDeprecationWarnings: true,
customSyntax: 'postcss-scss',
},
src: files,
},
},
};
};
/**
* Register any stylelint tasks.
*
* @param {Object} grunt
* @param {Array} files
* @param {String} fullRunDir
*/
const registerStyleLintTasks = () => {
const glob = require('glob');
// The stylelinters do not handle the case where a configuration was provided but no files were included.
// Keep track of whether any files were found.
let hasCss = false;
let hasScss = false;
// The stylelint processors do not take a path argument. They always check all provided values.
// As a result we must check through each glob and determine if any files match the current directory.
const scssFiles = [];
const cssFiles = [];
const requestedFiles = grunt.moodleEnv.files;
if (requestedFiles) {
// Grunt was called with a files argument.
// Check whether each of the requested files matches either the CSS or SCSS source file list.
requestedFiles.forEach(changedFilePath => {
let matchesGlob;
// Check whether this watched path matches any watched SCSS file.
matchesGlob = grunt.moodleEnv.scssSrc.some(watchedPathGlob => {
return glob.sync(watchedPathGlob).indexOf(changedFilePath) !== -1;
});
if (matchesGlob) {
scssFiles.push(changedFilePath);
hasScss = true;
}
// Check whether this watched path matches any watched CSS file.
matchesGlob = grunt.moodleEnv.cssSrc.some(watchedPathGlob => {
return glob.sync(watchedPathGlob).indexOf(changedFilePath) !== -1;
});
if (matchesGlob) {
cssFiles.push(changedFilePath);
hasCss = true;
}
});
} else {
// Grunt was called without a list of files.
// The start directory (runDir) may be a child dir of the project.
// Check each scssSrc file to see if it's in the start directory.
// This means that we can lint just mod/*/styles.css if started in the mod directory.
grunt.moodleEnv.scssSrc.forEach(path => {
if (path.startsWith(grunt.moodleEnv.runDir)) {
scssFiles.push(path);
hasScss = true;
}
});
grunt.moodleEnv.cssSrc.forEach(path => {
if (path.startsWith(grunt.moodleEnv.runDir)) {
cssFiles.push(path);
hasCss = true;
}
});
}
// Register the tasks.
const scssTasks = ['sass'];
if (hasScss) {
grunt.config.merge(getScssConfigForFiles(scssFiles));
scssTasks.unshift('stylelint:scss');
}
scssTasks.unshift('ignorefiles');
const cssTasks = ['ignorefiles'];
if (hasCss) {
grunt.config.merge(getCssConfigForFiles(cssFiles));
cssTasks.push('stylelint:css');
}
// The tasks must be registered, even if empty to ensure a consistent command list.
// They jsut won't run anything.
grunt.registerTask('scss', scssTasks);
grunt.registerTask('rawcss', cssTasks);
};
// Register CSS tasks.
grunt.loadNpmTasks('grunt-stylelint');
// Register the style lint tasks.
registerStyleLintTasks();
grunt.registerTask('css', ['scss', 'rawcss']);
const getCoreThemeMatches = () => {
const scssMatch = 'scss/**/*.scss';
if (grunt.moodleEnv.inTheme) {
return [scssMatch];
}
if (grunt.moodleEnv.runDir.startsWith('theme')) {
return [`*/${scssMatch}`];
}
return [`theme/*/${scssMatch}`];
};
// Add the watch configuration for rawcss, and scss.
grunt.config.merge({
watch: {
rawcss: {
files: [
'**/*.css',
],
excludes: [
'**/moodle.css',
'**/editor.css',
'jsdoc/styles/*.css',
],
tasks: ['rawcss']
},
scss: {
files: getCoreThemeMatches(),
tasks: ['scss']
},
},
});
};
-42
View File
@@ -1,42 +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/>.
/* jshint node: true, browser: false */
/* eslint-env node */
/**
* @copyright 2023 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
module.exports = grunt => {
/**
* Generate upgradable third-party libraries (utilising thirdpartylibs.xml data)
*/
grunt.registerTask('upgradablelibs', 'Generate upgradable third-party libraries', async function() {
const done = this.async();
const path = require('path');
const ComponentList = require(path.join(process.cwd(), '.grunt', 'components.js'));
// An array of third party libraries that have a newer version in their repositories.
const thirdPartyLibs = await ComponentList.getThirdPartyLibsUpgradable({progress: true});
for (let library of thirdPartyLibs) {
grunt.log.ok(JSON.stringify(Object.assign({}, library), null, 4));
}
done();
});
};
-272
View File
@@ -1,272 +0,0 @@
/**
* This is a wrapper task to handle the grunt watch command. It attempts to use
* Watchman to monitor for file changes, if it's installed, because it's much faster.
*
* If Watchman isn't installed then it falls back to the grunt-contrib-watch file
* watcher for backwards compatibility.
*/
/* eslint-env node */
module.exports = grunt => {
/**
* This is a wrapper task to handle the grunt watch command. It attempts to use
* Watchman to monitor for file changes, if it's installed, because it's much faster.
*
* If Watchman isn't installed then it falls back to the grunt-contrib-watch file
* watcher for backwards compatibility.
*/
const watchHandler = function() {
const async = require('async');
const watchTaskDone = this.async();
let watchInitialised = false;
let watchTaskQueue = {};
let processingQueue = false;
const watchman = require('fb-watchman');
const watchmanClient = new watchman.Client();
// Grab the tasks and files that have been queued up and execute them.
var processWatchTaskQueue = function() {
if (!Object.keys(watchTaskQueue).length || processingQueue) {
// If there is nothing in the queue or we're already processing then wait.
return;
}
processingQueue = true;
// Grab all tasks currently in the queue.
var queueToProcess = watchTaskQueue;
// Reset the queue.
watchTaskQueue = {};
async.forEachSeries(
Object.keys(queueToProcess),
function(task, next) {
var files = queueToProcess[task];
var filesOption = '--files=' + files.join(',');
grunt.log.ok('Running task ' + task + ' for files ' + filesOption);
// Spawn the task in a child process so that it doesn't kill this one
// if it failed.
grunt.util.spawn(
{
// Spawn with the grunt bin.
grunt: true,
// Run from current working dir and inherit stdio from process.
opts: {
cwd: grunt.moodleEnv.fullRunDir,
stdio: 'inherit'
},
args: [task, filesOption]
},
function(err, res, code) {
if (code !== 0) {
// The grunt task failed.
grunt.log.error(err);
}
// Move on to the next task.
next();
}
);
},
function() {
// No longer processing.
processingQueue = false;
// Once all of the tasks are done then recurse just in case more tasks
// were queued while we were processing.
processWatchTaskQueue();
}
);
};
const originalWatchConfig = grunt.config.get(['watch']);
const watchConfig = Object.keys(originalWatchConfig).reduce(function(carry, key) {
if (key == 'options') {
return carry;
}
const value = originalWatchConfig[key];
const taskNames = value.tasks;
const files = value.files;
let excludes = [];
if (value.excludes) {
excludes = value.excludes;
}
taskNames.forEach(function(taskName) {
carry[taskName] = {
files,
excludes,
};
});
return carry;
}, {});
watchmanClient.on('error', function(error) {
// We have to add an error handler here and parse the error string because the
// example way from the docs to check if Watchman is installed doesn't actually work!!
// See: https://github.com/facebook/watchman/issues/509
if (error.message.match('Watchman was not found')) {
// If watchman isn't installed then we should fallback to the other watch task.
grunt.log.ok('It is recommended that you install Watchman for better performance using the "watch" command.');
// Fallback to the old grunt-contrib-watch task.
grunt.renameTask('watch-grunt', 'watch');
grunt.task.run(['watch']);
// This task is finished.
watchTaskDone(0);
} else {
grunt.log.error(error);
// Fatal error.
watchTaskDone(1);
}
});
watchmanClient.on('subscription', function(resp) {
if (resp.subscription !== 'grunt-watch') {
return;
}
resp.files.forEach(function(file) {
grunt.log.ok('File changed: ' + file.name);
var fullPath = grunt.moodleEnv.fullRunDir + '/' + file.name;
Object.keys(watchConfig).forEach(function(task) {
const fileGlobs = watchConfig[task].files;
var match = fileGlobs.some(function(fileGlob) {
return grunt.file.isMatch(`**/${fileGlob}`, fullPath);
});
if (match) {
// If we are watching a subdirectory then the file.name will be relative
// to that directory. However the grunt tasks expect the file paths to be
// relative to the Gruntfile.js location so let's normalise them before
// adding them to the queue.
var relativePath = fullPath.replace(grunt.moodleEnv.gruntFilePath + '/', '');
if (task in watchTaskQueue) {
if (!watchTaskQueue[task].includes(relativePath)) {
watchTaskQueue[task] = watchTaskQueue[task].concat(relativePath);
}
} else {
watchTaskQueue[task] = [relativePath];
}
}
});
});
processWatchTaskQueue();
});
process.on('SIGINT', function() {
// Let the user know that they may need to manually stop the Watchman daemon if they
// no longer want it running.
if (watchInitialised) {
grunt.log.ok('The Watchman daemon may still be running and may need to be stopped manually.');
}
process.exit();
});
// Initiate the watch on the current directory.
watchmanClient.command(['watch-project', grunt.moodleEnv.fullRunDir], function(watchError, watchResponse) {
if (watchError) {
grunt.log.error('Error initiating watch:', watchError);
watchTaskDone(1);
return;
}
if ('warning' in watchResponse) {
grunt.log.error('warning: ', watchResponse.warning);
}
var watch = watchResponse.watch;
var relativePath = watchResponse.relative_path;
watchInitialised = true;
watchmanClient.command(['clock', watch], function(clockError, clockResponse) {
if (clockError) {
grunt.log.error('Failed to query clock:', clockError);
watchTaskDone(1);
return;
}
// Generate the expression query used by watchman.
// Documentation is limited, but see https://facebook.github.io/watchman/docs/expr/allof.html for examples.
// We generate an expression to match any value in the files list of all of our tasks, but excluding
// all value in the excludes list of that task.
//
// [anyof, [
// [allof, [
// [anyof, [
// ['match', validPath, 'wholename'],
// ['match', validPath, 'wholename'],
// ],
// [not,
// [anyof, [
// ['match', invalidPath, 'wholename'],
// ['match', invalidPath, 'wholename'],
// ],
// ],
// ],
var matchWholeName = fileGlob => ['match', fileGlob, 'wholename'];
var matches = Object.keys(watchConfig).map(function(task) {
const matchAll = [];
matchAll.push(['anyof'].concat(watchConfig[task].files.map(matchWholeName)));
if (watchConfig[task].excludes.length) {
matchAll.push(['not', ['anyof'].concat(watchConfig[task].excludes.map(matchWholeName))]);
}
return ['allof'].concat(matchAll);
});
matches = ['anyof'].concat(matches);
var sub = {
expression: matches,
// Which fields we're interested in.
fields: ["name", "size", "type"],
// Add our time constraint.
since: clockResponse.clock
};
if (relativePath) {
/* eslint-disable camelcase */
sub.relative_root = relativePath;
}
watchmanClient.command(['subscribe', watch, 'grunt-watch', sub], function(subscribeError) {
if (subscribeError) {
// Probably an error in the subscription criteria.
grunt.log.error('failed to subscribe: ', subscribeError);
watchTaskDone(1);
return;
}
grunt.log.ok('Listening for changes to files in ' + grunt.moodleEnv.fullRunDir);
});
});
});
};
// Rename the grunt-contrib-watch "watch" task because we're going to wrap it.
grunt.renameTask('watch', 'watch-grunt');
// Register the new watch handler.
grunt.registerTask('watch', 'Run tasks on file changes', watchHandler);
grunt.config.merge({
watch: {
options: {
nospawn: true // We need not to spawn so config can be changed dynamically.
},
},
});
return watchHandler;
};
-103
View File
@@ -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();
-2
View File
@@ -1,2 +0,0 @@
**/amd/**
/*.js
-3
View File
@@ -1,6 +1,3 @@
// NOTE: We use eslint now. This file is used only by shifter. We keep the configuration
// here because shifter uses jshint after modules have been concating. Eslint can't
// currently do this.
{
"asi": false,
"bitwise": true,
-1
View File
@@ -1 +0,0 @@
lts/jod
-40
View File
@@ -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([
'' => '@',
]));
-107
View File
@@ -1,107 +0,0 @@
{
"customSyntax": "postcss-scss",
"plugins": [
"stylelint-csstree-validator"
],
"rules": {
"csstree/validator": {
"syntaxExtensions": [
"sass"
]
},
"at-rule-empty-line-before": [ "always",
{"except": [ "blockless-after-blockless"], ignore: ["after-comment", "inside-block"]}
],
"at-rule-name-case": "lower",
"at-rule-name-space-after": "always-single-line",
"at-rule-no-unknown": null, # Enabled for non-scss in grunt.
"at-rule-semicolon-newline-after": "always",
"at-rule-semicolon-space-before": "never",
"block-closing-brace-newline-after": "always",
"block-closing-brace-newline-before": "always",
"block-closing-brace-space-before": "always-single-line",
"block-no-empty": true,
"block-opening-brace-newline-after": "always",
"block-opening-brace-space-after": "always-single-line",
"block-opening-brace-space-before": "always",
"color-hex-case": ["lower", { "severity": "warning" }],
"color-hex-length": ["short", { "severity": "warning" }],
"color-no-invalid-hex": true,
"declaration-bang-space-after": "never",
"declaration-bang-space-before": "always",
"declaration-block-no-duplicate-properties": true,
"declaration-block-no-shorthand-property-overrides": true,
"declaration-block-semicolon-newline-after": "always-multi-line",
"declaration-block-semicolon-space-after": "always-single-line",
"declaration-block-semicolon-space-before": "never",
"declaration-block-single-line-max-declarations": 1,
"declaration-block-trailing-semicolon": "always",
"declaration-colon-newline-after": "always-multi-line",
"declaration-colon-space-after": "always-single-line",
"declaration-colon-space-before": "never",
"declaration-no-important": true,
"font-family-no-duplicate-names": true,
"function-calc-no-unspaced-operator": true,
"function-comma-newline-after": "always-multi-line",
"function-comma-space-after": "always-single-line",
"function-comma-space-before": "never",
"function-linear-gradient-no-nonstandard-direction": true,
"function-max-empty-lines": 0,
"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-whitespace-after": "always",
"indentation": 4,
"keyframe-declaration-no-important": true,
"length-zero-no-unit": [true, { "severity": "warning" }],
"max-empty-lines": 2,
"max-line-length": [132, { "severity": "warning" }],
"media-feature-colon-space-after": "always",
"media-feature-colon-space-before": "never",
"media-feature-parentheses-space-inside": "never",
"media-feature-range-operator-space-after": "always",
"media-feature-range-operator-space-before": "always",
"media-query-list-comma-newline-after": "always-multi-line",
"media-query-list-comma-space-after": "always-single-line",
"media-query-list-comma-space-before": "never",
"no-empty-source": true,
"no-eol-whitespace": true,
"no-extra-semicolons": [true, { "severity": "warning" }],
"no-invalid-double-slash-comments": true,
"no-unknown-animations": true,
"property-case": "lower",
"property-no-unknown": true,
"selector-attribute-brackets-space-inside": "never",
"selector-attribute-operator-space-after": "never",
"selector-attribute-operator-space-before": "never",
"selector-combinator-space-after": "always",
"selector-combinator-space-before": "always",
"selector-list-comma-newline-after": "always",
"selector-list-comma-space-before": "never",
"selector-max-empty-lines": 0,
"selector-pseudo-class-case": "lower",
"selector-pseudo-class-no-unknown": true,
"selector-pseudo-class-parentheses-space-inside": "never",
"selector-pseudo-element-case": "lower",
"selector-pseudo-element-no-unknown": true,
"selector-type-case": "lower",
"selector-type-no-unknown": true,
"string-no-newline": true,
"time-min-milliseconds": 100,
"unit-disallowed-list": ["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": {
}
}
]
}
+172
View File
@@ -0,0 +1,172 @@
# PLEASE NOTE: Travis is not currently utilised by the Moodle core integration
# process (which uses our internal CI system) this file is here for the benefit
# of community developers git clones - see MDL-51458.
sudo: false
# We currently disable Travis notifications entirely until https://github.com/travis-ci/travis-ci/issues/4976
# is fixed.
notifications:
email: false
language: php
php:
# We only run the highest and lowest supported versions to reduce the load on travis-ci.org.
- 7.0
# - 5.6
# - 5.5
- 5.4
env:
# Although we want to run these jobs and see failures as quickly as possible, we also want to get the slowest job to
# start first so that the total run time is not too high.
#
# We only run MySQL on PHP 5.6, so run that first.
# CI Tests should be second-highest in priority as these only take <= 60 seconds to run under normal circumstances.
# Postgres is significantly is pretty reasonable in its run-time.
# Run unit tests on MySQL
- DB=mysqli PHPUNIT=true INSTALL=false CITEST=false
# Run CI Tests without running PHPUnit.
- DB=none PHPUNIT=false INSTALL=false CITEST=true
# Run unit tests on Postgres
- DB=pgsql PHPUNIT=true INSTALL=false CITEST=false
matrix:
# Enable fast finish.
# This will fail the build if a single job fails (except those in allow_failures).
# It will not stop the jobs from running.
fast_finish: true
exclude:
# MySQL - it's just too slow.
# Exclude it on all versions except for 7.0
# - env: DB=mysqli PHPUNIT=true INSTALL=false CITEST=false
# php: 5.6
#
# - env: DB=mysqli PHPUNIT=true INSTALL=false CITEST=false
# php: 5.5
- env: DB=mysqli PHPUNIT=true INSTALL=false CITEST=false
php: 5.4
cache:
directories:
- $HOME/.composer/cache
install:
# Set the encrypted GITHUB_TOKEN if it's available to raise the API limit.
- if [ -n "$GITHUB_APITOKEN" ]; then composer config github-oauth.github.com $GITHUB_APITOKEN; fi
# Install composer dependencies.
# We need --no-interaction in case we hit API limits for composer. This causes it to fall back to a standard clone.
# Typically it should be able to use the Composer cache if any other job has already completed before we started here.
- travis_retry composer install --prefer-dist --no-interaction
before_script:
- >
if [ "$INSTALL" = 'true' -o "$PHPUNIT" = 'true' ];
then
# Copy generic configuration in place.
cp config-dist.php config.php ;
# Create the moodledata directory.
mkdir -p "$HOME"/roots/base
# The database name and password.
sed -i \
-e "s%= 'moodle'%= 'travis_ci_test'%" \
-e "s%= 'password'%= ''%" \
config.php ;
# The wwwroot and dataroot.
sed -i \
-e "s%http://example.com/moodle%http://localhost%" \
-e "s%/home/example/moodledata%/home/travis/roots/base%" \
config.php ;
if [ "$DB" = 'pgsql' ];
then
# Postgres-specific setup.
sed -i \
-e "s%= 'username'%= 'postgres'%" \
config.php ;
psql -c 'CREATE DATABASE travis_ci_test;' -U postgres;
fi
if [ "$DB" = 'mysqli' ];
then
# MySQL-specific setup.
sed -i \
-e "s%= 'pgsql'%= 'mysqli'%" \
-e "s%= 'username'%= 'travis'%" \
config.php;
mysql -u root -e 'SET GLOBAL innodb_file_format=barracuda;' ;
mysql -u root -e 'SET GLOBAL innodb_file_per_table=ON;' ;
mysql -e 'CREATE DATABASE travis_ci_test DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_bin;' ;
fi
if [ "$PHPUNIT" = 'true' ];
then
# Create a directory for the phpunit dataroot.
mkdir -p "$HOME"/roots/phpunit
# The phpunit dataroot and prefix..
sed -i \
-e "/require_once/i \\\$CFG->phpunit_dataroot = '\/home\/travis\/roots\/phpunit';" \
-e "/require_once/i \\\$CFG->phpunit_prefix = 'p_';" \
config.php ;
# Initialise PHPUnit for Moodle.
php admin/tool/phpunit/cli/init.php
fi
fi
script:
########################################################################
# PHPUnit
########################################################################
- >
if [ "$PHPUNIT" = 'true' ];
then
vendor/bin/phpunit;
fi
########################################################################
# CI Tests
########################################################################
- >
if [ "$CITEST" = 'true' ];
then
# Note - this is deliberately placed in the script section as we
# should not add any code until after phpunit has run.
# The following repositories are required.
# The local_ci repository does the actual checking.
git clone https://github.com/moodlehq/moodle-local_ci.git local/ci
# We need the official upstream for comparison
git remote add upstream https://github.com/moodle/moodle.git;
git fetch upstream MOODLE_30_STABLE;
export GIT_PREVIOUS_COMMIT="`git merge-base FETCH_HEAD $TRAVIS_COMMIT`";
export GIT_COMMIT="$TRAVIS_COMMIT";
export UPSTREAM_FETCH_HEAD=`git rev-parse FETCH_HEAD`
# Variables required by our linter.
export gitcmd=`which git`;
export gitdir="$TRAVIS_BUILD_DIR";
export phpcmd=`which php`;
fi
# Actually run the CI Tests - do this outside of the main test to make output clearer.
- >
if [ "$CITEST" = 'true' ];
then
bash local/ci/php_lint/php_lint.sh;
fi
@@ -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,7 +0,0 @@
issueNumber: MDL-72050
notes:
core_files:
- message: >-
A new function `file_clear_draft_area()` has been added to delete the
files in a draft area.
type: improved
@@ -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,18 +0,0 @@
issueNumber: MDL-75971
notes:
core:
- message: >-
Behat now supports email content verification using Mailpit.
You can check the contents of an email using the step `Then the email to "user@example.com" with subject containing "subject" should contain "content".`
To use this feature:
1. Ensure that Mailpit is running
2. Define the following constants in your `config.php`:
- `TEST_EMAILCATCHER_MAIL_SERVER` - The Mailpit server address (e.g. `0.0.0.0:1025`)
- `TEST_EMAILCATCHER_API_SERVER` - The Mailpit API server (qe.g. `http://localhost:8025`)
3. Ensure that the email catcher is set up using the step `Given an email catcher server is configured`.
type: improved
@@ -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

Some files were not shown because too many files have changed in this diff Show More