--- /dev/null
+<?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 | E_STRICT); // 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');
--- /dev/null
+name: Core
+
+on: [push]
+
+env:
+ php: 7.4
+
+jobs:
+ Grunt:
+ runs-on: ubuntu-18.04
+
+ steps:
+ - name: Checking out code
+ uses: actions/checkout@v2
+
+ - name: Configuring node & npm
+ shell: bash -l {0}
+ run: nvm install
+
+ - 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:
+ - os: ubuntu-18.04
+ php: 7.2
+ db: mysqli
+ - os: ubuntu-18.04
+ php: 7.4
+ db: pgsql
+
+ steps:
+ - name: Setting up DB mysql
+ if: ${{ matrix.db == 'mysqli' }}
+ uses: johanmeiring/mysql-action@tmpfs-patch
+ with:
+ collation server: utf8mb4_bin
+ mysql version: 5.7
+ mysql database: test
+ mysql user: test
+ mysql password: test
+ use tmpfs: true
+
+ - name: Setting up DB pgsql
+ if: ${{ matrix.db == 'pgsql' }}
+ uses: m4nu56/postgresql-action@v1
+ with:
+ postgresql version: 9.6
+ postgresql db: test
+ postgresql user: test
+ postgresql password: test
+
+ - name: Configuring git vars
+ uses: rlespinasse/github-slug-action@v3.x
+
+ - name: Setting up PHP ${{ matrix.php }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+
+ - name: Checking out code from ${{ env.GITHUB_REF_SLUG }}
+ uses: actions/checkout@v2
+
+ - 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 }}
+ run: vendor/bin/phpunit -v
- mysql
- docker
-php:
- # We only run the highest and lowest supported versions to reduce the load on travis-ci.org.
- - 7.4
- - 7.2
-
addons:
postgresql: "9.6"
-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 7.2, 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 CI Tests without running PHPUnit.
- - DB=none TASK=CITEST
-
- # Run unit tests on Postgres
- - DB=pgsql TASK=PHPUNIT
-
- # Perform an upgrade test too.
- - DB=pgsql TASK=UPGRADE
-
jobs:
# Enable fast finish.
# This will fail the build if a single job fails (except those in allow_failures).
fast_finish: true
include:
- # Run mysql only on highest - it's just too slow
- - php: 7.4
+ # First all the lowest php ones (7.2)
+ - php: 7.2
+ env: DB=none TASK=CITEST
+ - php: 7.2
+ env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon'
+
+ - if: env(MOODLE_DATABASE) = "pgsql" OR env(MOODLE_DATABASE) = "all" OR env(MOODLE_DATABASE) IS NOT present
+ php: 7.2
+ env: DB=pgsql TASK=PHPUNIT
+
+ - if: env(MOODLE_DATABASE) = "mysqli" OR env(MOODLE_DATABASE) = "all"
+ php: 7.2
+ env: DB=mysqli TASK=PHPUNIT
+
+ # Then, conditionally, all the highest php ones (7.4)
+ - if: env(MOODLE_PHP) = "all"
+ php: 7.4
+ env: DB=none TASK=CITEST
+ - if: env(MOODLE_PHP) = "all"
+ php: 7.4
+ env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon'
+
+ - if: env(MOODLE_PHP) = "all" AND (env(MOODLE_DATABASE) = "pgsql" OR env(MOODLE_DATABASE) = "all" OR env(MOODLE_DATABASE) IS NOT present)
+ php: 7.4
+ env: DB=pgsql TASK=PHPUNIT
+
+ - if: env(MOODLE_PHP) = "all" AND (env(MOODLE_DATABASE) = "mysqli" OR env(MOODLE_DATABASE) = "all")
+ php: 7.4
env: DB=mysqli TASK=PHPUNIT
- # Run grunt/npm install on highest version too ('node' is an alias for the latest node.js version.)
- - php: 7.4
- env: DB=none TASK=GRUNT NVM_VERSION='lts/carbon'
cache:
directories:
before_script:
- phpenv config-rm xdebug.ini
- >
- if [ "$TASK" = 'PHPUNIT' -o "$TASK" = 'UPGRADE' ];
+ if [ "$TASK" = 'PHPUNIT' ];
then
# Copy generic configuration in place.
cp config-dist.php config.php ;
export phpcmd=`which php`;
fi
- ########################################################################
- # Upgrade test
- ########################################################################
- - >
- if [ "$TASK" = 'UPGRADE' ];
- then
- # We need the official upstream.
- git remote add upstream https://github.com/moodle/moodle.git;
-
- # Checkout 30 STABLE branch (the first version compatible with PHP 7.x)
- git fetch upstream MOODLE_30_STABLE;
- git checkout MOODLE_30_STABLE;
-
- # Perform the upgrade
- php admin/cli/install_database.php --agree-license --adminpass=Password --adminemail=admin@example.com --fullname="Upgrade test" --shortname=Upgrade;
-
- # Return to the previous commit
- git checkout -;
-
- # Perform the upgrade
- php admin/cli/upgrade.php --non-interactive --allow-unstable ;
-
- # The local_ci repository can be used to check upgrade savepoints.
- git clone https://github.com/moodlehq/moodle-local_ci.git local/ci ;
- fi
-
script:
- >
if [ "$TASK" = 'PHPUNIT' ];
git diff --cached --exit-code ;
fi
- ########################################################################
- # Upgrade test
- ########################################################################
- - >
- if [ "$TASK" = 'UPGRADE' ];
- then
- cp local/ci/check_upgrade_savepoints/check_upgrade_savepoints.php ./check_upgrade_savepoints.php
- result=`php check_upgrade_savepoints.php`;
- # Check if there are problems
- count=`echo "$result" | grep -P "ERROR|WARN" | wc -l` ;
- if (($count > 0));
- then
- echo "$result"
- exit 1 ;
- fi
- fi
-
after_script:
- >
if [ "$TASK" = 'PHPUNIT' ];
--- /dev/null
+<?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/>.
+
+/**
+ * Generates a secure key for the current server (presuming it does not already exist).
+ *
+ * @package core_admin
+ * @copyright 2020 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+use \core\encryption;
+
+define('CLI_SCRIPT', true);
+
+require(__DIR__ . '/../../config.php');
+require_once($CFG->libdir . '/clilib.php');
+
+// Get cli options.
+[$options, $unrecognized] = cli_get_params(
+ ['help' => false, 'method' => null],
+ ['h' => 'help']);
+
+if ($unrecognized) {
+ $unrecognized = implode("\n ", $unrecognized);
+ cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
+}
+
+if ($options['help']) {
+ echo "Generate secure key
+
+This script manually creates a secure key within the secret data root folder (configured in
+config.php as \$CFG->secretdataroot). You must run it using an account with access to write
+to that folder.
+
+In normal use Moodle automatically creates the key; this script is intended when setting up
+a new Moodle system, for cases where the secure folder is not on shared storage and the key
+may be manually installed on multiple servers.
+
+Options:
+-h, --help Print out this help
+--method <method> Generate key for specified encryption method instead of default.
+ * sodium
+ * openssl-aes-256-ctr
+
+Example:
+php admin/cli/generate_key.php
+";
+ exit;
+}
+
+$method = $options['method'];
+
+if (encryption::key_exists($method)) {
+ echo 'Key already exists: ' . encryption::get_key_file($method) . "\n";
+ exit;
+}
+
+// Creates key with default permissions (no chmod).
+echo "Generating key...\n";
+encryption::create_key($method, false);
+
+echo "\nKey created: " . encryption::get_key_file($method) . "\n\n";
+echo "If the key folder is not shared storage, then key files should be copied to all servers.\n";
$settings->add(new admin_setting_configduration('analytics/modeltimelimit', new lang_string('modeltimelimit', 'analytics'),
new lang_string('modeltimelimitinfo', 'analytics'), 20 * MINSECS));
+ $options = array(
+ 0 => new lang_string('neverdelete', 'analytics'),
+ 1000 => new lang_string('numdays', '', 1000),
+ 365 => new lang_string('numdays', '', 365),
+ 180 => new lang_string('numdays', '', 180),
+ 150 => new lang_string('numdays', '', 150),
+ 120 => new lang_string('numdays', '', 120),
+ 90 => new lang_string('numdays', '', 90),
+ 60 => new lang_string('numdays', '', 60),
+ 35 => new lang_string('numdays', '', 35));
+ $settings->add(new admin_setting_configselect('analytics/calclifetime',
+ new lang_string('calclifetime', 'analytics'),
+ new lang_string('configlcalclifetime', 'analytics'), 35, $options));
+
+
}
}
--- /dev/null
+{{!
+ 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 core_admin/admin_setting_encryptedpassword
+
+ Admin encrypted password template.
+
+ Context variables required for this template:
+ * name - form element name
+ * set - whether it is set or empty
+ * id - element id
+
+ Example context (json):
+ {
+ "name": "test",
+ "id": "test0",
+ "set": true
+ }
+}}
+<div class="core_admin_encryptedpassword" data-encryptedpasswordid="{{ id }}"
+ {{#novalue}}data-novalue="y"{{/novalue}}>
+ {{#set}}
+ <span>{{# str }} encryptedpassword_set, admin {{/ str }}</span>
+ {{/set}}
+ {{^set}}
+ <a href="#" title="{{# str }} encryptedpassword_edit, admin {{/ str }}">
+ <span>{{# str }} novalueclicktoset, form {{/ str }}</span>
+ {{# pix }} t/passwordunmask-edit, core, {{# str }} passwordunmaskedithint, form {{/ str }}{{/ pix }}
+ </a>
+ {{/set}}
+ <input style="display: none" type="password" name="{{name}}" disabled>
+ {{!
+ Using buttons instead of links here allows them to be connected to the label, so the button
+ works if you click the label.
+ }}
+ {{#set}}
+ <button type="button" id="{{id}}" title="{{# str }} encryptedpassword_edit, admin {{/ str }}" class="btn btn-link" data-editbutton>
+ {{# pix }} t/passwordunmask-edit, core, {{/ pix }}
+ </button>
+ {{/set}}
+ <button type="button" style="display: none" title="{{# str }} cancel {{/ str }}" class="btn btn-link" data-cancelbutton>
+ <i class="icon fa fa-times"></i>
+ </button>
+</div>
+
+{{#js}}
+require(['core_form/encryptedpassword'], function(encryptedpassword) {
+ new encryptedpassword.EncryptedPassword("{{ id }}");
+});
+{{/js}}
}
/**
- * Sets the specified site settings. A table with | config | value | (optional)plugin | is expected.
+ * Sets the specified site settings. A table with | config | value | (optional)plugin | (optional)encrypted | is expected.
*
* @Given /^the following config values are set as admin:$/
* @param TableNode $table
foreach ($data as $config => $value) {
// Default plugin value is null.
$plugin = null;
+ $encrypted = false;
if (is_array($value)) {
$plugin = $value[1];
+ if (array_key_exists(2, $value)) {
+ $encrypted = $value[2] === 'encrypted';
+ }
$value = $value[0];
}
+
+ if ($encrypted) {
+ $value = \core\encryption::encrypt($value);
+ }
+
set_config($config, $value, $plugin);
}
}
Background:
Given the following "users" exist:
- | username | firstname | lastname | email |
- | teacher1 | Teacher | 1 | teacher1@example.com |
+ | username | firstname | lastname |
+ | teacher1 | Teacher | 1 |
+ | tutor | Teaching | Assistant |
+ | student | Student | One |
And the following "courses" exist:
- | fullname | shortname | category |
- | Course 1 | C1 | 0 |
+ | fullname | shortname |
+ | Course 1 | C1 |
And the following "course enrolments" exist:
- | user | course | role |
- | teacher1 | C1 | editingteacher |
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | tutor | C1 | teacher |
+ | student | C1 | student |
Scenario: Default system capabilities modification
Given I log in as "admin"
Then "mod/forum:deleteanypost" capability has "Prohibit" permission
And "mod/forum:editanypost" capability has "Prevent" permission
And "mod/forum:addquestion" capability has "Allow" permission
+
+ @javascript
+ Scenario: Edit permissions escapes role names correctly
+ When I am on the "C1" "Course" page logged in as "admin"
+ And I navigate to "Edit settings" in current page administration
+ And I set the following fields to these values:
+ | Your word for 'Teacher' | Teacher >= editing |
+ | Your word for 'Non-editing teacher' | Teacher < "editing" |
+ | Your word for 'Student' | Studier & 'learner' |
+ And I press "Save and display"
+ And I navigate to course participants
+ Then I should see "Teacher >= editing (Teacher)" in the "Teacher 1" "table_row"
+ And I should see "Teacher < \"editing\" (Non-editing teacher)" in the "Teaching Assistant" "table_row"
+ And I should see "Studier & 'learner' (Student)" in the "Student One" "table_row"
+ And I navigate to "Users > Permissions" in current page administration
+ And I should see "Teacher >= editing" in the "mod/forum:replypost" "table_row"
+ And I should see "Teacher < \"editing\"" in the "mod/forum:replypost" "table_row"
+ And I should see "Studier & 'learner'" in the "mod/forum:replypost" "table_row"
+ And I follow "Prohibit"
+ And "Teacher >= editing" "button" in the "Prohibit role" "dialogue" should be visible
+ And "Teacher < \"editing\"" "button" in the "Prohibit role" "dialogue" should be visible
+ And "Studier & 'learner'" "button" in the "Prohibit role" "dialogue" should be visible
--- /dev/null
+@tool_behat
+Feature: Verify that the inplace editable field works as expected
+ In order to use behat step definitions
+ As a test write
+ I need to ensure that the inplace editable works in forms
+
+ Background:
+ Given the following "course" exists:
+ | fullname | Course 1 |
+ | shortname | C1 |
+ And the following "activities" exist:
+ | activity | course | name | idnumber |
+ | forum | C1 | My first forum | forum1 |
+ | assign | C1 | My first assignment | assign1 |
+ | quiz | C1 | My first quiz | quiz1 |
+ And I log in as "admin"
+ And I am on "Course 1" course homepage with editing mode on
+
+ @javascript
+ Scenario: Using an inplace editable updates the name of an activity
+ When I set the field "Edit title" in the "My first assignment" "activity" to "Coursework submission"
+ Then I should see "Coursework submission"
+ And I should not see "My first assignment"
+ But I should see "My first forum"
+ And I should see "My first quiz"
+ And I set the field "Edit title" in the "Coursework submission" "activity" to "My first assignment"
+ And I should not see "Coursework submission"
+ But I should see "My first assignment"
+ And I should see "My first forum"
+ And I should see "My first quiz"
And I press the shift tab key
And the focused element is "Username" "field"
- @javascript
- Scenario: Using the arrow keys allows me to navigate through menus
- Given the following "users" exist:
- | username | email | firstname | lastname |
- | saffronr | saffron.rutledge@example.com | Saffron | Rutledge |
- And I log in as "saffronr"
- And I click on "Saffron Rutledge" "link" in the ".usermenu" "css_element"
- When I press the up key
- Then the focused element is "Log out" "link"
+# TODO: Uncomment the following when MDL-66979 is integrated.
+# @javascript
+# Scenario: Using the arrow keys allows me to navigate through menus
+# Given the following "users" exist:
+# | username | email | firstname | lastname |
+# | saffronr | saffron.rutledge@example.com | Saffron | Rutledge |
+# And I log in as "saffronr"
+# And I click on "Saffron Rutledge" "link" in the ".usermenu" "css_element"
+# When I press the up key
+# Then the focused element is "Log out" "link"
@javascript
Scenario: The escape key can be used to close a dialogue
This files describes API changes in /admin/*.
+=== 3.11 ===
+
+* New admin setting admin_setting_encryptedpassword allows passwords in admin settings to be
+ encrypted (with the new \core\encryption API) so that even the admin cannot read them.
+
=== 3.9 ===
* The following functions, previously used (exclusively) by upgrade steps are not available anymore because of the upgrade cleanup performed for this version. See MDL-65809 for more info:
}
/**
- * Returns the enabled time splitting methods.
- *
- * @deprecated since Moodle 3.7
- * @todo MDL-65086 This will be deleted in Moodle 3.11
- * @see \core_analytics\manager::get_time_splitting_methods_for_evaluation
- * @return \core_analytics\local\time_splitting\base[]
+ * @deprecated since Moodle 3.7 use get_time_splitting_methods_for_evaluation instead
*/
public static function get_enabled_time_splitting_methods() {
- debugging('This function has been deprecated. You can use self::get_time_splitting_methods_for_evaluation if ' .
+ throw new coding_exception(__FUNCTION__ . '() has been removed. You can use self::get_time_splitting_methods_for_evaluation if ' .
'you want to get the default time splitting methods for evaluation, or you can use self::get_all_time_splittings if ' .
'you want to get all the time splitting methods available on this site.');
- return self::get_time_splitting_methods_for_evaluation();
}
/**
*/
public static function add_builtin_models() {
- debugging('core_analytics\manager::add_builtin_models() has been deprecated. Core models are now automatically '.
- 'updated according to their declaration in the lib/db/analytics.php file.', DEBUG_DEVELOPER);
+ throw new \coding_exception('core_analytics\manager::add_builtin_models() has been removed. Core models ' .
+ 'are now automatically updated according to their declaration in the lib/db/analytics.php file.');
}
/**
$param + $idsparams);
}
}
+
+ // Clean up calculations table.
+ $calclifetime = get_config('analytics', 'calclifetime');
+ if (!empty($calclifetime)) {
+ $lifetime = time() - ($calclifetime * DAYSECS); // Value in days.
+ $DB->delete_records_select('analytics_indicator_calc', 'timecreated < ?', [$lifetime]);
+ }
}
/**
This files describes API changes in analytics sub system,
information provided here is intended especially for developers.
+=== 3.11 ===
+* Final deprecation get_enabled_time_splitting_methods. Method has been removed. Use
+ get_time_splitting_methods_for_evaluation instead.
+* Final deprecation add_builtin_models. Method has been removed. The functionality
+ has been replaced with automatic update of models provided by the core moodle component.
+ There is no need to call this method explicitly any more. Instead, adding new models can be achieved
+ by updating the lib/db/analytics.php file and bumping the core version.
+
=== 3.8 ===
* "Time-splitting method" have been replaced by "Analysis interval" for the language strings that are
$issuerid = required_param('id', PARAM_INT);
$wantsurl = new moodle_url(optional_param('wantsurl', '', PARAM_URL));
+$PAGE->set_context(context_system::instance());
+$PAGE->set_url(new moodle_url('/auth/oauth2/login.php', ['id' => $issuerid]));
+
require_sesskey();
if (!\auth_oauth2\api::is_enabled()) {
$newcm = duplicate_module($course, get_fast_modinfo($course)->get_cm($quiz->cmid));
- $sql = "SELECT qa.answer
+ $sql = "SELECT qa.id, qa.answer
FROM {quiz} q
LEFT JOIN {quiz_slots} qs ON qs.quizid = q.id
LEFT JOIN {question_answers} qa ON qa.question = qs.questionid
WHERE q.id = :quizid";
$params = array('quizid' => $newcm->instance);
- $answers = $DB->get_fieldset_sql($sql, $params);
- $this->assertEquals($CFG->wwwroot . '/course/view.php?id=' . $course->id, $answers[0]);
- $this->assertEquals($CFG->wwwroot . '/mod/quiz/view.php?id=' . $quiz->cmid, $answers[1]);
- $this->assertEquals($CFG->wwwroot . '/grade/report/index.php?id=' . $quiz->cmid, $answers[2]);
- $this->assertEquals($CFG->wwwroot . '/mod/quiz/index.php?id=' . $quiz->cmid, $answers[3]);
+ $answers = $DB->get_records_sql_menu($sql, $params);
+
+ $this->assertEquals($CFG->wwwroot . '/course/view.php?id=' . $course->id, $answers[$firstanswer->id]);
+ $this->assertEquals($CFG->wwwroot . '/mod/quiz/view.php?id=' . $quiz->cmid, $answers[$secondanswer->id]);
+ $this->assertEquals($CFG->wwwroot . '/grade/report/index.php?id=' . $quiz->cmid, $answers[$thirdanswer->id]);
+ $this->assertEquals($CFG->wwwroot . '/mod/quiz/index.php?id=' . $quiz->cmid, $answers[$fourthanswer->id]);
}
}
--- /dev/null
+<?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/>.
+
+/**
+ * This is the external method used for fetching the addable blocks in a given page.
+ *
+ * @package core_block
+ * @since Moodle 3.11
+ * @copyright 2020 Mihail Geshoski <mihail@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_block\external;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/externallib.php');
+
+use external_api;
+use external_function_parameters;
+use external_multiple_structure;
+use external_single_structure;
+use external_value;
+
+/**
+ * This is the external method used for fetching the addable blocks in a given page.
+ *
+ * @copyright 2020 Mihail Geshoski <mihail@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class fetch_addable_blocks extends external_api {
+
+ /**
+ * Describes the parameters for execute.
+ *
+ * @return external_function_parameters
+ */
+ public static function execute_parameters(): external_function_parameters {
+ return new external_function_parameters(
+ [
+ 'pagecontextid' => new external_value(PARAM_INT, 'The context ID of the page.'),
+ 'pagetype' => new external_value(PARAM_ALPHAEXT, 'The type of the page.'),
+ 'pagelayout' => new external_value(PARAM_ALPHA, 'The layout of the page.')
+ ]
+ );
+ }
+
+ /**
+ * Fetch the addable blocks in a given page.
+ *
+ * @param int $pagecontextid The context ID of the page
+ * @param string $pagetype The type of the page
+ * @param string $pagelayout The layout of the page
+ * @return array The blocks list
+ */
+ public static function execute(int $pagecontextid, string $pagetype, string $pagelayout): array {
+ global $PAGE;
+
+ $params = self::validate_parameters(self::execute_parameters(),
+ [
+ 'pagecontextid' => $pagecontextid,
+ 'pagetype' => $pagetype,
+ 'pagelayout' => $pagelayout
+ ]
+ );
+
+ $context = \context::instance_by_id($params['pagecontextid']);
+ // Validate the context. This will also set the context in $PAGE.
+ self::validate_context($context);
+
+ // We need to manually set the page layout and page type.
+ $PAGE->set_pagelayout($params['pagelayout']);
+ $PAGE->set_pagetype($params['pagetype']);
+ // Firstly, we need to load all currently existing page blocks to later determine which blocks are addable.
+ $PAGE->blocks->load_blocks(false);
+ $PAGE->blocks->create_all_block_instances();
+
+ $addableblocks = $PAGE->blocks->get_addable_blocks();
+
+ return array_map(function($block) {
+ return [
+ 'name' => $block->name,
+ 'title' => get_string('pluginname', "block_{$block->name}")
+ ];
+ }, $addableblocks);
+ }
+
+ /**
+ * Describes the execute return value.
+ *
+ * @return external_multiple_structure
+ */
+ public static function execute_returns(): external_multiple_structure {
+ return new external_multiple_structure(
+ new external_single_structure(
+ [
+ 'name' => new external_value(PARAM_PLUGIN, 'The name of the block.'),
+ 'title' => new external_value(PARAM_RAW, 'The title of the block.'),
+ ]
+ ),
+ 'List of addable blocks in a given page.'
+ );
+ }
+}
// Filter out all pagination options which are too large for the amount of courses user is enrolled in.
var totalCourseCount = parseInt(root.find(Selectors.courseView.region).attr('data-totalcoursecount'), 10);
- if (totalCourseCount) {
- itemsPerPage = itemsPerPage.filter(function(pagingOption) {
- return pagingOption.value < totalCourseCount;
- });
- }
+ itemsPerPage = itemsPerPage.filter(function(pagingOption) {
+ return pagingOption.value < totalCourseCount || pagingOption.value === 0;
+ });
var filters = getFilterValues(root);
var config = $.extend({}, DEFAULT_PAGED_CONTENT_CONFIG);
pageCourses = $.merge(loadedPages[currentPage].courses, courses.slice(0, nextPageStart));
}
} else {
- nextPageStart = pageData.limit;
+ // When the page limit is zero, there is only one page of courses, no start for next page.
+ nextPageStart = pageData.limit || false;
pageCourses = (pageData.limit > 0) ? courses.slice(0, pageData.limit) : courses;
}
courses: pageCourses
};
- // Set up the next page
- var remainingCourses = nextPageStart ? courses.slice(nextPageStart, courses.length) : [];
+ // Set up the next page (if there is more than one page).
+ var remainingCourses = nextPageStart !== false ? courses.slice(nextPageStart, courses.length) : [];
if (remainingCourses.length) {
loadedPages[currentPage + 1] = {
courses: remainingCourses
// Check and remember the given view.
$this->view = $view ? $view : BLOCK_MYOVERVIEW_VIEW_CARD;
- // Check and remember the given page size.
- if ($paging == BLOCK_MYOVERVIEW_PAGING_ALL) {
+ // Check and remember the given page size, `null` indicates no page size set
+ // while a `0` indicates a paging size of `All`.
+ if (!is_null($paging) && $paging == BLOCK_MYOVERVIEW_PAGING_ALL) {
$this->paging = BLOCK_MYOVERVIEW_PAGING_ALL;
} else {
$this->paging = $paging ? $paging : BLOCK_MYOVERVIEW_PAGING_12;
| student1 | Student | X | student1@example.com | S1 |
And the following "courses" exist:
| fullname | shortname | category |
- | Course 1 | C1 | 0 |
- | Course 2 | C2 | 0 |
- | Course 3 | C3 | 0 |
- | Course 4 | C4 | 0 |
- | Course 5 | C5 | 0 |
- | Course 6 | C6 | 0 |
- | Course 7 | C7 | 0 |
- | Course 8 | C8 | 0 |
- | Course 9 | C9 | 0 |
+ | Course 1 | C01 | 0 |
+ | Course 2 | C02 | 0 |
+ | Course 3 | C03 | 0 |
+ | Course 4 | C04 | 0 |
+ | Course 5 | C05 | 0 |
+ | Course 6 | C06 | 0 |
+ | Course 7 | C07 | 0 |
+ | Course 8 | C08 | 0 |
+ | Course 9 | C09 | 0 |
| Course 10 | C10 | 0 |
| Course 11 | C11 | 0 |
| Course 12 | C12 | 0 |
| Course 13 | C13 | 0 |
And the following "course enrolments" exist:
| user | course | role |
- | student1 | C1 | student |
- | student1 | C2 | student |
- | student1 | C3 | student |
- | student1 | C4 | student |
- | student1 | C5 | student |
- | student1 | C6 | student |
- | student1 | C7 | student |
- | student1 | C8 | student |
- | student1 | C9 | student |
+ | student1 | C01 | student |
+ | student1 | C02 | student |
+ | student1 | C03 | student |
+ | student1 | C04 | student |
+ | student1 | C05 | student |
+ | student1 | C06 | student |
+ | student1 | C07 | student |
+ | student1 | C08 | student |
+ | student1 | C09 | student |
| student1 | C10 | student |
| student1 | C11 | student |
| student1 | C12 | student |
Scenario: Toggle the page limit between page reloads
Given I log in as "student1"
- When I click on "[data-toggle='dropdown']" "css_element" in the "Course overview" "block"
- And I click on "All" "link"
+ When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block"
+ And I click on "All" "link" in the ".dropdown-menu.show" "css_element"
Then I should see "Course 13"
And I reload the page
Then I should see "Course 13"
Scenario: Toggle the page limit between grouping changes
Given I log in as "student1"
- When I click on "[data-toggle='dropdown']" "css_element" in the "Course overview" "block"
- And I click on "All" "link"
+ When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block"
+ And I click on "All" "link" in the ".dropdown-menu.show" "css_element"
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
And I click on "In progress" "link" in the "Course overview" "block"
Then I should see "Course 13"
--- /dev/null
+@block @block_myoverview @javascript
+Feature: My overview block pagination
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | student1 | Student | X | student1@example.com | S1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 01 | C1 | 0 |
+ | Course 02 | C2 | 0 |
+ | Course 03 | C3 | 0 |
+ | Course 04 | C4 | 0 |
+ | Course 05 | C5 | 0 |
+ | Course 06 | C6 | 0 |
+ | Course 07 | C7 | 0 |
+ | Course 08 | C8 | 0 |
+ | Course 09 | C9 | 0 |
+ | Course 10 | C10 | 0 |
+ | Course 11 | C11 | 0 |
+ | Course 12 | C12 | 0 |
+ | Course 13 | C13 | 0 |
+ | Course 14 | C14 | 0 |
+ | Course 15 | C15 | 0 |
+ | Course 16 | C16 | 0 |
+ | Course 17 | C17 | 0 |
+ | Course 18 | C18 | 0 |
+ | Course 19 | C19 | 0 |
+ | Course 20 | C20 | 0 |
+ | Course 21 | C21 | 0 |
+ | Course 22 | C22 | 0 |
+ | Course 23 | C23 | 0 |
+ | Course 24 | C24 | 0 |
+ | Course 25 | C25 | 0 |
+
+ Scenario: The pagination controls should be hidden if I am not enrolled in any courses
+ When I log in as "student1"
+ Then I should see "No courses" in the "Course overview" "block"
+ And I should not see "Show" in the "Course overview" "block"
+ And ".block_myoverview .dropdown-menu.show" "css_element" should not be visible
+ And ".block_myoverview [data-control='next']" "css_element" should not be visible
+ And ".block_myoverview [data-control='previous']" "css_element" should not be visible
+ And I log out
+
+ Scenario: The pagination controls should be hidden if I am enrolled in 12 courses or less
+ Given the following "course enrolments" exist:
+ | user | course | role |
+ | student1 | C1 | student |
+ | student1 | C2 | student |
+ | student1 | C3 | student |
+ | student1 | C4 | student |
+ | student1 | C5 | student |
+ | student1 | C6 | student |
+ | student1 | C7 | student |
+ | student1 | C8 | student |
+ | student1 | C9 | student |
+ | student1 | C10 | student |
+ | student1 | C11 | student |
+ | student1 | C12 | student |
+ When I log in as "student1"
+ Then I should not see "Show" in the "Course overview" "block"
+ And ".block_myoverview .dropdown-menu.show" "css_element" should not be visible
+ And ".block_myoverview [data-control='next']" "css_element" should not be visible
+ And ".block_myoverview [data-control='previous']" "css_element" should not be visible
+ And I log out
+
+ Scenario: The default pagination should be 12 courses
+ Given the following "course enrolments" exist:
+ | user | course | role |
+ | student1 | C1 | student |
+ | student1 | C2 | student |
+ | student1 | C3 | student |
+ | student1 | C4 | student |
+ | student1 | C5 | student |
+ | student1 | C6 | student |
+ | student1 | C7 | student |
+ | student1 | C8 | student |
+ | student1 | C9 | student |
+ | student1 | C10 | student |
+ | student1 | C11 | student |
+ | student1 | C12 | student |
+ | student1 | C13 | student |
+ When I log in as "student1"
+ Then I should see "12" in the "[data-action='limit-toggle']" "css_element"
+ And I log out
+
+ Scenario: I should only see pagination limit options less than total number of enrolled courses
+ Given the following "course enrolments" exist:
+ | user | course | role |
+ | student1 | C1 | student |
+ | student1 | C2 | student |
+ | student1 | C3 | student |
+ | student1 | C4 | student |
+ | student1 | C5 | student |
+ | student1 | C6 | student |
+ | student1 | C7 | student |
+ | student1 | C8 | student |
+ | student1 | C9 | student |
+ | student1 | C10 | student |
+ | student1 | C11 | student |
+ | student1 | C12 | student |
+ | student1 | C13 | student |
+ And I log in as "student1"
+ When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block"
+ Then I should see "All" in the ".dropdown-menu.show" "css_element"
+ And I should see "12" in the ".dropdown-menu.show" "css_element"
+ And ".block_myoverview [data-control='next']" "css_element" should be visible
+ And ".block_myoverview [data-control='previous']" "css_element" should be visible
+ But I should not see "24" in the ".block_myoverview .dropdown-menu.show" "css_element"
+ And I log out
+
+ Scenario: Previous page button should be disabled when on the first page of courses
+ Given the following "course enrolments" exist:
+ | user | course | role |
+ | student1 | C1 | student |
+ | student1 | C2 | student |
+ | student1 | C3 | student |
+ | student1 | C4 | student |
+ | student1 | C5 | student |
+ | student1 | C6 | student |
+ | student1 | C7 | student |
+ | student1 | C8 | student |
+ | student1 | C9 | student |
+ | student1 | C10 | student |
+ | student1 | C11 | student |
+ | student1 | C12 | student |
+ | student1 | C13 | student |
+ When I log in as "student1"
+ Then the "class" attribute of ".block_myoverview [data-control='previous']" "css_element" should contain "disabled"
+ And I log out
+
+ Scenario: Next page button should be disabled when on the last page of courses
+ Given the following "course enrolments" exist:
+ | user | course | role |
+ | student1 | C1 | student |
+ | student1 | C2 | student |
+ | student1 | C3 | student |
+ | student1 | C4 | student |
+ | student1 | C5 | student |
+ | student1 | C6 | student |
+ | student1 | C7 | student |
+ | student1 | C8 | student |
+ | student1 | C9 | student |
+ | student1 | C10 | student |
+ | student1 | C11 | student |
+ | student1 | C12 | student |
+ | student1 | C13 | student |
+ When I log in as "student1"
+ And I click on "[data-control='next']" "css_element" in the "Course overview" "block"
+ And I wait until ".block_myoverview [data-control='next']" "css_element" exists
+ Then the "class" attribute of ".block_myoverview [data-control='next']" "css_element" should contain "disabled"
+ And I log out
+
+ Scenario: Next and previous page buttons should both be enabled when not on last or first page of courses
+ Given the following "course enrolments" exist:
+ | user | course | role |
+ | student1 | C1 | student |
+ | student1 | C2 | student |
+ | student1 | C3 | student |
+ | student1 | C4 | student |
+ | student1 | C5 | student |
+ | student1 | C6 | student |
+ | student1 | C7 | student |
+ | student1 | C8 | student |
+ | student1 | C9 | student |
+ | student1 | C10 | student |
+ | student1 | C11 | student |
+ | student1 | C12 | student |
+ | student1 | C13 | student |
+ | student1 | C14 | student |
+ | student1 | C15 | student |
+ | student1 | C16 | student |
+ | student1 | C17 | student |
+ | student1 | C18 | student |
+ | student1 | C19 | student |
+ | student1 | C20 | student |
+ | student1 | C21 | student |
+ | student1 | C22 | student |
+ | student1 | C23 | student |
+ | student1 | C24 | student |
+ | student1 | C25 | student |
+ When I log in as "student1"
+ And I click on "[data-control='next']" "css_element" in the "Course overview" "block"
+ And I wait until ".block_myoverview [data-control='next']" "css_element" exists
+ Then the "class" attribute of ".block_myoverview [data-control='next']" "css_element" should not contain "disabled"
+ And the "class" attribute of ".block_myoverview [data-control='previous']" "css_element" should not contain "disabled"
+ And I should see "Course 13" in the "Course overview" "block"
+ And I should see "Course 24" in the "Course overview" "block"
+ But I should not see "Course 12" in the "Course overview" "block"
+ And I should not see "Course 25" in the "Course overview" "block"
+ And I log out
}
}
+ // Whether or not section name should be displayed.
+ $showsectionname = !empty($config->showsectionname) ? true : false;
+
// Prepare an array of sections to create links for.
$sections = array();
$canviewhidden = has_capability('moodle/course:update', $context);
$sections[$i]->highlight = true;
$sectiontojumpto = $section->section;
}
+ if ($showsectionname) {
+ $sections[$i]->name = $courseformat->get_section_name($i);
+ }
}
}
if (!empty($sections)) {
// Render the sections.
$renderer = $this->page->get_renderer('block_section_links');
- $this->content->text = $renderer->render_section_links($this->page->course, $sections, $sectiontojumpto);
+ $this->content->text = $renderer->render_section_links($this->page->course, $sections,
+ $sectiontojumpto, $showsectionname);
}
return $this->content;
$mform->addHelpButton('config_incby'.$i, 'incby'.$i, 'block_section_links');
}
+ $mform->addElement('selectyesno', 'config_showsectionname', get_string('showsectionname', 'block_section_links'));
+ $mform->setDefault('config_showsectionname', !empty($config->showsectionname) ? 1 : 0);
+ $mform->addHelpButton('config_showsectionname', 'showsectionname', 'block_section_links');
}
}
\ No newline at end of file
$string['numsections2_help'] = 'Once the number of sections in the course reaches this number then the Alternative increment by value is used.';
$string['pluginname'] = 'Section links';
$string['section_links:addinstance'] = 'Add a new section links block';
+$string['showsectionname'] = 'Display section name';
+$string['showsectionname_help'] = 'Display section name in addition to section number';
$string['topics'] = 'Topics';
$string['weeks'] = 'Weeks';
$string['privacy:metadata'] = 'The Section links block only shows data stored in other locations.';
* @param stdClass $course The course we are rendering for.
* @param array $sections An array of section objects to render.
* @param bool|int The section to provide a jump to link for.
+ * @param bool $showsectionname Whether or not section name should be displayed.
* @return string The HTML to display.
*/
- public function render_section_links(stdClass $course, array $sections, $jumptosection = false) {
- $html = html_writer::start_tag('ol', array('class' => 'inline-list'));
+ public function render_section_links(stdClass $course, array $sections, $jumptosection = false, $showsectionname = false) {
+ $olparams = $showsectionname ? ['class' => 'unlist'] : ['class' => 'inline-list'];
+ $html = html_writer::start_tag('ol', $olparams);
foreach ($sections as $section) {
$attributes = array();
if (!$section->visible) {
}
$html .= html_writer::start_tag('li');
$sectiontext = $section->section;
+ if ($showsectionname) {
+ $sectiontext .= ': ' . $section->name;
+ }
if ($section->highlight) {
$sectiontext = html_writer::tag('strong', $sectiontext);
}
get_string('incby'.$i.'_help', 'block_section_links'),
$selected[$i][1], $increments));
}
+
+ $settings->add(new admin_setting_configcheckbox('block_section_links/showsectionname',
+ get_string('showsectionname', 'block_section_links'),
+ get_string('showsectionname_help', 'block_section_links'),
+ 0));
}
\ No newline at end of file
--- /dev/null
+@block @block_section_links
+Feature: The Section links block can be configured to display section name in addition to section number
+
+ Background:
+ Given the following "courses" exist:
+ | fullname | shortname | category | numsections | coursedisplay |
+ | Course 1 | C1 | 0 | 10 | 1 |
+ And the following "activities" exist:
+ | activity | name | course | idnumber | section |
+ | assign | First assignment | C1 | assign1 | 7 |
+ And the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ | student1 | Student | 1 | student1@example.com |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ And I log in as "admin"
+ And I set the following administration settings values:
+ | showsectionname | 1 |
+ And I am on "Course 1" course homepage with editing mode on
+ And I add the "Section links" block
+ And I log out
+
+ Scenario: Student can see section name under the Section links block
+ Given I log in as "student1"
+ When I am on "Course 1" course homepage
+ Then I should see "7: Topic 7" in the "Section links" "block"
+ And I follow "7: Topic 7"
+ And I should see "First assignment"
+
+ Scenario: Teacher can configure existing Section links block to display section number or section name
+ Given I log in as "teacher1"
+ And I am on "Course 1" course homepage with editing mode on
+ When I configure the "Section links" block
+ And I set the following fields to these values:
+ | Display section name | No |
+ And I click on "Save changes" "button"
+ Then I should not see "7: Topic 7" in the "Section links" "block"
+ And I should see "7" in the "Section links" "block"
+ And I follow "7"
+ And I should see "First assignment"
--- /dev/null
+This file describes API changes in the section_links block code.
+
+=== 3.11 ===
+
+* New optional parameter $showsectionname has been added to render_section_links(). Setting this to true will display
+ section name in addition to section number.
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2021052500; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version = 2021052501; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2021052500; // Requires this Moodle version
$plugin->component = 'block_section_links'; // Full name of the plugin (used for diagnostics)
$xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]//li[contains(., $activityname)]";
$this->execute('behat_action_menu::i_open_the_action_menu_in', [$xpath, 'xpath_element']);
}
+
+ /**
+ * Return the list of partial named selectors.
+ *
+ * @return array
+ */
+ public static function get_partial_named_selectors(): array {
+ return [
+ new behat_component_named_selector('Activity', [
+ "//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]//li[contains(., %locator%)]"
+ ]),
+ ];
+ }
}
@javascript
Scenario: Edit name of acitivity in-place in site main menu block
- Given I log in as "admin"
+ Given the following "activity" exists:
+ | activity | forum |
+ | course | Acceptance test site |
+ | name | My forum name |
+ | idnumber | forum |
+ And I log in as "admin"
And I am on site homepage
And I navigate to "Turn editing on" in current page administration
And I add the "Main menu" block
- When I add a "Forum" to section "0" and I fill the form with:
- | Forum name | My forum name |
- And I click on "Edit title" "link" in the "My forum name" activity in site main menu block
- And I set the field "New name for activity My forum name" to "New forum name"
- And I press the enter key
+ When I set the field "Edit title" in the "My forum name" "block_site_main_menu > Activity" to "New forum name"
Then I should not see "My forum name"
And I should see "New forum name"
And I follow "New forum name"
$xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_social_activities ')]//li[contains(., $activityname)]";
$this->execute('behat_action_menu::i_open_the_action_menu_in', [$xpath, 'xpath_element']);
}
+
+ /**
+ * Return the list of partial named selectors.
+ *
+ * @return array
+ */
+ public static function get_partial_named_selectors(): array {
+ return [
+ new behat_component_named_selector('Activity', [
+ "//*[contains(concat(' ',normalize-space(@class),' '),' block_social_activities ')]//li[contains(., %locator%)]",
+ ]),
+ ];
+ }
}
And I click on "Add a new Forum" "link" in the "Add an activity or resource" "dialogue"
And I set the field "Forum name" to "My forum name"
And I press "Save and return to course"
- And I click on "Edit title" "link" in the "My forum name" activity in social activities block
- And I set the field "New name for activity My forum name" to "New forum name"
- And I press the enter key
+ When I set the field "Edit title" in the "My forum name" "block_social_activities > Activity" to "New forum name"
Then I should not see "My forum name" in the "Social activities" "block"
And I should see "New forum name"
And I follow "New forum name"
And I should not see "My forum name" in the "Social activities" "block"
And I click on "My forum name" "link" in the "Recent activity" "block"
And I should see "My forum name" in the ".breadcrumb" "css_element"
- And I log out
<a href="{{url}}"
title={{#quote}}{{{name}}}{{/quote}}
aria-label='{{#str}} ariaeventlistitem, block_timeline, { "name": {{#quote}}{{{name}}}{{/quote}}, "course": {{#quote}}{{{course.fullnamedisplay}}}{{/quote}}, "date": "{{#userdate}} {{timesort}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}" } {{/str}}'
- ><h6 class="event-name text-truncate mb-0">{{#quote}}{{{name}}}{{/quote}}</h6></a>
+ ><h6 class="event-name text-truncate mb-0">{{{name}}}</h6></a>
{{#course.fullnamedisplay}}
- <small class="text-muted text-truncate mb-0">{{#quote}}{{{course.fullnamedisplay}}}{{/quote}}</small>
+ <small class="text-muted text-truncate mb-0">{{{course.fullnamedisplay}}}</small>
{{/course.fullnamedisplay}}
{{#action.actionable}}
<h6 class="mb-0 pt-2">
@javascript
Scenario: Edit cohort name in-place
When I follow "Cohorts"
- And I click on "Edit cohort name" "link" in the "Test cohort name" "table_row"
- And I set the field "New name for cohort Test cohort name" to "Students cohort"
- And I press the enter key
+ And I set the field "Edit cohort name" to "Students cohort"
Then I should not see "Test cohort name"
And I should see "Students cohort"
And I follow "Cohorts"
// Use the igbinary serializer instead of the php default one. Note that phpredis must be compiled with
// igbinary support to make the setting to work. Also, if you change the serializer you have to flush the database!
// $CFG->session_redis_serializer_use_igbinary = false; // Optional, default is PHP builtin serializer.
+// $CFG->session_redis_compressor = 'none'; // Optional, possible values are:
+// // 'gzip' - PHP GZip compression
+// // 'zstd' - PHP Zstandard compression
//
// Please be aware that when selecting Memcached for sessions that it is advised to use a dedicated
// memcache server. The memcached extension does not provide isolated environments for individual uses.
//
// $CFG->maxcoursesincategory = 10000;
//
+// Admin setting encryption
+//
+// $CFG->secretdataroot = '/var/www/my_secret_folder';
+//
+// Location to store encryption keys. By default this is $CFG->dataroot/secret; set this if
+// you want to use a different location for increased security (e.g. if too many people have access
+// to the main dataroot, or if you want to avoid using shared storage). Your web server user needs
+// read access to this location, and write access unless you manually create the keys.
+//
+// $CFG->nokeygeneration = false;
+//
+// If you change this to true then the server will give an error if keys don't exist, instead of
+// automatically generating them. This is only needed if you want to ensure that keys are consistent
+// across a cluster when not using shared storage. If you stop the server generating keys, you will
+// need to manually generate them by running 'php admin/cli/generate_key.php'.
+
//=========================================================================
// 7. SETTINGS FOR DEVELOPMENT SERVERS - not intended for production use!!!
//=========================================================================
const firstChooserOption = sectionChooserOptions.querySelector(selectors.regions.chooserOption.container);
toggleFocusableChooserOption(firstChooserOption, true);
- initTabsKeyboardNavigation(body);
initChooserOptionsKeyboardNavigation(body, mappedModules, sectionChooserOptions, modal);
return body;
.catch();
};
-/**
- * Initialise the keyboard navigation controls for the tab list items.
- *
- * @method initTabsKeyboardNavigation
- * @param {HTMLElement} body Our modal that we are working with
- */
-const initTabsKeyboardNavigation = (body) => {
- // Set up the tab handlers.
- const favTabNav = body.querySelector(selectors.regions.favouriteTabNav);
- const recommendedTabNav = body.querySelector(selectors.regions.recommendedTabNav);
- const defaultTabNav = body.querySelector(selectors.regions.defaultTabNav);
- const activityTabNav = body.querySelector(selectors.regions.activityTabNav);
- const resourceTabNav = body.querySelector(selectors.regions.resourceTabNav);
- const tabNavArray = [favTabNav, recommendedTabNav, defaultTabNav, activityTabNav, resourceTabNav];
- tabNavArray.forEach((element) => {
- return element.addEventListener('keydown', (e) => {
- // The first visible navigation tab link.
- const firstLink = e.target.parentElement.querySelector(selectors.elements.visibletabs);
- // The last navigation tab link. It would always be the default activities tab link.
- const lastLink = e.target.parentElement.lastElementChild;
-
- if (e.keyCode === arrowRight) {
- const nextLink = e.target.nextElementSibling;
- if (nextLink === null) {
- e.target.tabIndex = -1;
- firstLink.tabIndex = 0;
- firstLink.focus();
- } else if (nextLink.classList.contains('d-none')) {
- e.target.tabIndex = -1;
- lastLink.tabIndex = 0;
- lastLink.focus();
- } else {
- e.target.tabIndex = -1;
- nextLink.tabIndex = 0;
- nextLink.focus();
- }
- }
- if (e.keyCode === arrowLeft) {
- const previousLink = e.target.previousElementSibling;
- if (previousLink === null) {
- e.target.tabIndex = -1;
- lastLink.tabIndex = 0;
- lastLink.focus();
- } else if (previousLink.classList.contains('d-none')) {
- e.target.tabIndex = -1;
- firstLink.tabIndex = 0;
- firstLink.focus();
- } else {
- e.target.tabIndex = -1;
- previousLink.tabIndex = 0;
- previousLink.focus();
- }
- }
- if (e.keyCode === home) {
- e.target.tabIndex = -1;
- firstLink.tabIndex = 0;
- firstLink.focus();
- }
- if (e.keyCode === end) {
- e.target.tabIndex = -1;
- lastLink.tabIndex = 0;
- lastLink.focus();
- }
- if (e.keyCode === space) {
- e.preventDefault();
- e.target.click();
- }
- });
- });
-};
-
/**
* Initialise the keyboard navigation controls for the chooser options.
*
help: getDataSelector('region', 'help'),
modules: getDataSelector('region', 'modules'),
favouriteTabNav: getDataSelector('region', 'favourite-tab-nav'),
- recommendedTabNav: getDataSelector('region', 'recommended-tab-nav'),
defaultTabNav: getDataSelector('region', 'default-tab-nav'),
activityTabNav: getDataSelector('region', 'activity-tab-nav'),
- resourceTabNav: getDataSelector('region', 'resources-tab-nav'),
favouriteTab: getDataSelector('region', 'favourites'),
recommendedTab: getDataSelector('region', 'recommended'),
defaultTab: getDataSelector('region', 'default'),
@javascript
Scenario: Inline edit section name in topics format
- When I click on "Edit topic name" "link" in the "li#section-1" "css_element"
- And I set the field "New name for topic Topic 1" to "Midterm evaluation"
- And I press the enter key
+ When I set the field "Edit topic name" in the "li#section-1" "css_element" to "Midterm evaluation"
Then I should not see "Topic 1" in the "region-main" "region"
And "New name for topic" "field" should not exist
And I should see "Midterm evaluation" in the "li#section-1" "css_element"
@javascript
Scenario: Inline edit section name in weeks format
- When I click on "Edit week name" "link" in the "li#section-1" "css_element"
- And I set the field "New name for week 1 May - 7 May" to "Midterm evaluation"
- And I press the enter key
+ When I set the field "Edit week name" in the "li#section-1" "css_element" to "Midterm evaluation"
Then I should not see "1 May - 7 May" in the "region-main" "region"
And "New name for week" "field" should not exist
And I should see "Midterm evaluation" in the "li#section-1" "css_element"
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
+ And the following "activity" exists:
+ | course | C1 |
+ | activity | forum |
+ | name | Test forum name |
+ | description | Test forum description |
+ | idnumber | forum1 |
When I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
- And I add a "Forum" to section "1" and I fill the form with:
- | Forum name | Test forum name |
- | Description | Test forum description |
# Rename activity
- And I click on "Edit title" "link" in the "//div[contains(@class,'activityinstance') and contains(.,'Test forum name')]" "xpath_element"
- And I set the field "New name for activity Test forum name" to "Good news"
- And I press the enter key
+ And I set the field "Edit title" in the "Test forum name" "activity" to "Good news"
Then I should not see "Test forum name" in the ".course-content" "css_element"
And "New name for activity Test forum name" "field" should not exist
And I should see "Good news"
And I should not see "Test forum name"
# Cancel renaming
And I click on "Edit title" "link" in the "//div[contains(@class,'activityinstance') and contains(.,'Good news')]" "xpath_element"
- And I set the field "New name for activity Good news" to "Terrible news"
+ And I type "Terrible news"
And I press the escape key
And "New name for activity Good news" "field" should not exist
And I should see "Good news"
* @param string $newactivityname
*/
public function i_change_activity_name_to($activityname, $newactivityname) {
-
- if (!$this->running_javascript()) {
- throw new DriverException('Change activity name step is not available with Javascript disabled');
- }
-
- $activity = $this->escape($activityname);
-
- $this->execute('behat_course::i_click_on_in_the_activity',
- array(get_string('edittitle'), "link", $activity)
- );
-
- // Adding chr(10) to save changes.
- $this->execute('behat_forms::i_set_the_field_to',
- array('title', $this->escape($newactivityname) . chr(10))
- );
-
+ $this->execute('behat_forms::i_set_the_field_in_container_to', [
+ get_string('edittitle'),
+ $activityname,
+ 'activity',
+ $newactivityname
+ ]);
}
/**
'component' => new external_value(PARAM_COMPONENT, 'component'),
'area' => new external_value(PARAM_ALPHANUMEXT, 'area'),
'itemid' => new external_value(PARAM_INT, 'itemid'),
- 'usescategories' => new external_value(PARAM_INT, 'view has categories'),
+ 'usescategories' => new external_value(PARAM_BOOL, 'view has categories'),
'categories' => new external_multiple_structure(
new external_single_structure(
array(
Then I should see "Other fields" in the "#customfield_catlist" "css_element"
And I navigate to "Reports > Logs" in site administration
And I press "Get these logs"
- And I log out
Scenario: Edit a category name for custom course fields
Given the following "custom field categories" exist:
| Category for test | core_course | course | 0 |
And I log in as "admin"
And I navigate to "Courses > Course custom fields" in site administration
- And I click on "Edit category name" "link" in the "//div[contains(@class,'categoryinstance') and contains(.,'Category for test')]" "xpath_element"
- And I set the field "New value for Category for test" to "Good fields"
- And I press the enter key
+ And I set the field "Edit category name" in the "//div[contains(@class,'categoryinstance') and contains(.,'Category for test')]" "xpath_element" to "Good fields"
Then I should not see "Category for test" in the "#customfield_catlist" "css_element"
And "New value for Category for test" "field" should not exist
And I should see "Good fields" in the "#customfield_catlist" "css_element"
And I navigate to "Reports > Logs" in site administration
And I press "Get these logs"
- And I log out
Scenario: Delete a category for custom course fields
Given the following "custom field categories" exist:
Then I should not see "Test category" in the "#customfield_catlist" "css_element"
And I navigate to "Reports > Logs" in site administration
And I press "Get these logs"
- And I log out
Scenario: Move field in the course custom fields to another category
Given the following "custom field categories" exist:
And I press "Move \"Field1\""
And I follow "After field Field2"
And "Field1" "text" should appear after "Field2" "text"
- And I log out
Scenario: Reorder course custom field categories
Given the following "custom field categories" exist:
And "Field1" "text" should appear after "Category1" "text"
And "Category2" "text" should appear after "Field1" "text"
And "Category3" "text" should appear after "Category2" "text"
- And I log out
$student = get_archetype_roles('student');
$student = reset($student);
$settings->add(new admin_setting_configselect('enrol_cohort/roleid',
- get_string('defaultrole', 'role'), '', $student->id, $options));
+ get_string('defaultrole', 'role'), '', $student->id ?? null, $options));
$options = array(
ENROL_EXT_REMOVED_UNENROL => get_string('extremovedunenrol', 'enrol'),
+++ /dev/null
-<?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/>.
-
-/**
- * CLI sync for full external database synchronisation.
- *
- * Sample cron entry:
- * # 5 minutes past 4am
- * 5 4 * * * $sudo -u www-data /usr/bin/php /var/www/moodle/enrol/database/cli/sync.php
- *
- * Notes:
- * - it is required to use the web server account when executing PHP CLI scripts
- * - you need to change the "www-data" to match the apache user account
- * - use "su" if "sudo" not available
- *
- * @deprecated since Moodle 3.7 MDL-59986 - please do not use this CLI script any more, use scheduled task instead.
- * @todo MDL-63266 This will be deleted in Moodle 3.11.
- * @package enrol_database
- * @copyright 2010 Petr Skoda {@link http://skodak.org}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-define('CLI_SCRIPT', true);
-
-require(__DIR__.'/../../../config.php');
-require_once("$CFG->libdir/clilib.php");
-
-// Now get cli options.
-list($options, $unrecognized) = cli_get_params(array('verbose'=>false, 'help'=>false), array('v'=>'verbose', 'h'=>'help'));
-
-if ($unrecognized) {
- $unrecognized = implode("\n ", $unrecognized);
- cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
-}
-
-if ($options['help']) {
- $help =
-"Execute enrol sync with external database.
-The enrol_database plugin must be enabled and properly configured.
-
-Options:
--v, --verbose Print verbose progress information
--h, --help Print out this help
-
-Example:
-\$ sudo -u www-data /usr/bin/php enrol/database/cli/sync.php
-
-Sample cron entry:
-# 5 minutes past 4am
-5 4 * * * sudo -u www-data /usr/bin/php /var/www/moodle/enrol/database/cli/sync.php
-";
-
- echo $help;
- die;
-}
-
-cli_problem('[ENROL DATABASE] The sync enrolments cron script has been deprecated. Please use the scheduled task instead.');
-
-// Abort execution of the CLI script if the enrol_database\task\sync_enrolments is enabled.
-$task = \core\task\manager::get_scheduled_task('enrol_database\task\sync_enrolments');
-if (!$task->get_disabled()) {
- cli_error('[ENROL DATABASE] The scheduled task sync_enrolments is enabled, the cron execution has been aborted.');
-}
-
-if (!enrol_is_enabled('database')) {
- cli_error('enrol_database plugin is disabled, synchronisation stopped', 2);
-}
-
-if (empty($options['verbose'])) {
- $trace = new null_progress_trace();
-} else {
- $trace = new text_progress_trace();
-}
-
-/** @var enrol_database_plugin $enrol */
-$enrol = enrol_get_plugin('database');
-$result = 0;
-
-$result = $result | $enrol->sync_courses($trace);
-$result = $result | $enrol->sync_enrolments($trace);
-
-exit($result);
$options = get_default_enrol_roles(context_system::instance());
$student = get_archetype_roles('student');
$student = reset($student);
- $settings->add(new admin_setting_configselect('enrol_database/defaultrole', get_string('defaultrole', 'enrol_database'), get_string('defaultrole_desc', 'enrol_database'), $student->id, $options));
+ $settings->add(new admin_setting_configselect('enrol_database/defaultrole',
+ get_string('defaultrole', 'enrol_database'),
+ get_string('defaultrole_desc', 'enrol_database'),
+ $student->id ?? null,
+ $options));
}
$settings->add(new admin_setting_configcheckbox('enrol_database/ignorehiddencourses', get_string('ignorehiddencourses', 'enrol_database'), get_string('ignorehiddencourses_desc', 'enrol_database'), 0));
This files describes API changes in the enrol_database code.
+=== 3.11 ===
+* Final deprecation enrol/database/cli/sync.php. Refer below for substitute.
+
=== 3.9 ===
* Class enrol_database_admin_setting_category has been removed. This class was only used by the database
enrolment plugin settings and it was replaced by admin_settings_coursecat_select.
*/
public static function get_enrolled_users_parameters() {
return new external_function_parameters(
- array(
+ [
'courseid' => new external_value(PARAM_INT, 'course id'),
'options' => new external_multiple_structure(
new external_single_structure(
- array(
+ [
'name' => new external_value(PARAM_ALPHANUMEXT, 'option name'),
'value' => new external_value(PARAM_RAW, 'option value')
- )
+ ]
), 'Option names:
* withcapability (string) return only users with this capability. This option requires \'moodle/role:review\' on the course context.
* groupid (integer) return only users in this group id. If the course has groups enabled and this param
isn\'t defined, returns all the viewable users.
This option requires \'moodle/site:accessallgroups\' on the course context if the
user doesn\'t belong to the group.
- * onlyactive (integer) return only users with active enrolments and matching time restrictions. This option requires \'moodle/course:enrolreview\' on the course context.
+ * onlyactive (integer) return only users with active enrolments and matching time restrictions.
+ This option requires \'moodle/course:enrolreview\' on the course context.
+ Please note that this option can\'t
+ be used together with onlysuspended (only one can be active).
+ * onlysuspended (integer) return only suspended users. This option requires
+ \'moodle/course:enrolreview\' on the course context. Please note that this option can\'t
+ be used together with onlyactive (only one can be active).
* userfields (\'string, string, ...\') return only the values of these user fields.
* limitfrom (integer) sql limit from.
* limitnumber (integer) maximum number of returned users.
* sortby (string) sort by id, firstname or lastname. For ordering like the site does, use siteorder.
* sortdirection (string) ASC or DESC',
- VALUE_DEFAULT, array()),
- )
+ VALUE_DEFAULT, []),
+ ]
);
}
* }
* @return array An array of users
*/
- public static function get_enrolled_users($courseid, $options = array()) {
+ public static function get_enrolled_users($courseid, $options = []) {
global $CFG, $USER, $DB;
require_once($CFG->dirroot . '/course/lib.php');
$params = self::validate_parameters(
self::get_enrolled_users_parameters(),
- array(
+ [
'courseid'=>$courseid,
'options'=>$options
- )
+ ]
);
$withcapability = '';
$groupid = 0;
$onlyactive = false;
- $userfields = array();
+ $onlysuspended = false;
+ $userfields = [];
$limitfrom = 0;
$limitnumber = 0;
$sortby = 'us.id';
- $sortparams = array();
+ $sortparams = [];
$sortdirection = 'ASC';
foreach ($options as $option) {
switch ($option['name']) {
- case 'withcapability':
- $withcapability = $option['value'];
- break;
- case 'groupid':
- $groupid = (int)$option['value'];
- break;
- case 'onlyactive':
- $onlyactive = !empty($option['value']);
- break;
- case 'userfields':
- $thefields = explode(',', $option['value']);
- foreach ($thefields as $f) {
- $userfields[] = clean_param($f, PARAM_ALPHANUMEXT);
- }
- break;
- case 'limitfrom' :
- $limitfrom = clean_param($option['value'], PARAM_INT);
- break;
- case 'limitnumber' :
- $limitnumber = clean_param($option['value'], PARAM_INT);
- break;
- case 'sortby':
- $sortallowedvalues = array('id', 'firstname', 'lastname', 'siteorder');
- if (!in_array($option['value'], $sortallowedvalues)) {
- throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $option['value'] . '),' .
- 'allowed values are: ' . implode(',', $sortallowedvalues));
- }
- if ($option['value'] == 'siteorder') {
- list($sortby, $sortparams) = users_order_by_sql('us');
- } else {
- $sortby = 'us.' . $option['value'];
- }
- break;
- case 'sortdirection':
- $sortdirection = strtoupper($option['value']);
- $directionallowedvalues = array('ASC', 'DESC');
- if (!in_array($sortdirection, $directionallowedvalues)) {
- throw new invalid_parameter_exception('Invalid value for sortdirection parameter
+ case 'withcapability':
+ $withcapability = $option['value'];
+ break;
+ case 'groupid':
+ $groupid = (int)$option['value'];
+ break;
+ case 'onlyactive':
+ $onlyactive = !empty($option['value']);
+ break;
+ case 'onlysuspended':
+ $onlysuspended = !empty($option['value']);
+ break;
+ case 'userfields':
+ $thefields = explode(',', $option['value']);
+ foreach ($thefields as $f) {
+ $userfields[] = clean_param($f, PARAM_ALPHANUMEXT);
+ }
+ break;
+ case 'limitfrom' :
+ $limitfrom = clean_param($option['value'], PARAM_INT);
+ break;
+ case 'limitnumber' :
+ $limitnumber = clean_param($option['value'], PARAM_INT);
+ break;
+ case 'sortby':
+ $sortallowedvalues = ['id', 'firstname', 'lastname', 'siteorder'];
+ if (!in_array($option['value'], $sortallowedvalues)) {
+ throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' .
+ $option['value'] . '), allowed values are: ' . implode(',', $sortallowedvalues));
+ }
+ if ($option['value'] == 'siteorder') {
+ list($sortby, $sortparams) = users_order_by_sql('us');
+ } else {
+ $sortby = 'us.' . $option['value'];
+ }
+ break;
+ case 'sortdirection':
+ $sortdirection = strtoupper($option['value']);
+ $directionallowedvalues = ['ASC', 'DESC'];
+ if (!in_array($sortdirection, $directionallowedvalues)) {
+ throw new invalid_parameter_exception('Invalid value for sortdirection parameter
(value: ' . $sortdirection . '),' . 'allowed values are: ' . implode(',', $directionallowedvalues));
- }
- break;
+ }
+ break;
}
}
- $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
+ $course = $DB->get_record('course', ['id' => $courseid], '*', MUST_EXIST);
$coursecontext = context_course::instance($courseid, IGNORE_MISSING);
if ($courseid == SITEID) {
$context = context_system::instance();
require_capability('moodle/site:accessallgroups', $coursecontext);
}
// to overwrite this option, you need course:enrolereview permission
- if ($onlyactive) {
+ if ($onlyactive || $onlysuspended) {
require_capability('moodle/course:enrolreview', $coursecontext);
}
- list($enrolledsql, $enrolledparams) = get_enrolled_sql($coursecontext, $withcapability, $groupid, $onlyactive);
+ list($enrolledsql, $enrolledparams) = get_enrolled_sql($coursecontext, $withcapability, $groupid, $onlyactive,
+ $onlysuspended);
$ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
$ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel)";
$enrolledparams['contextlevel'] = CONTEXT_USER;
$enrolledparams = array_merge($enrolledparams, $groupparams);
} else {
// User doesn't belong to any group, so he can't see any user. Return an empty array.
- return array();
+ return [];
}
}
$sql = "SELECT us.*, COALESCE(ul.timeaccess, 0) AS lastcourseaccess
$enrolledparams['courseid'] = $courseid;
$enrolledusers = $DB->get_recordset_sql($sql, $enrolledparams, $limitfrom, $limitnumber);
- $users = array();
+ $users = [];
foreach ($enrolledusers as $user) {
context_helper::preload_from_record($user);
if ($userdetails = user_get_user_details($user, $course, $userfields)) {
public static function get_enrolled_users_returns() {
return new external_multiple_structure(
new external_single_structure(
- array(
+ [
'id' => new external_value(PARAM_INT, 'ID of the user'),
'username' => new external_value(PARAM_RAW, 'Username policy is defined in Moodle security config', VALUE_OPTIONAL),
'firstname' => new external_value(PARAM_NOTAGS, 'The first name(s) of the user', VALUE_OPTIONAL),
'profileimageurl' => new external_value(PARAM_URL, 'User image profile URL - big version', VALUE_OPTIONAL),
'customfields' => new external_multiple_structure(
new external_single_structure(
- array(
+ [
'type' => new external_value(PARAM_ALPHANUMEXT, 'The type of the custom field - text field, checkbox...'),
'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
'shortname' => new external_value(PARAM_RAW, 'The shortname of the custom field - to be able to build the field class in the code'),
- )
+ ]
), 'User custom fields (also known as user profil fields)', VALUE_OPTIONAL),
'groups' => new external_multiple_structure(
new external_single_structure(
- array(
+ [
'id' => new external_value(PARAM_INT, 'group id'),
&