Compare commits
220 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fb57854de5 | |||
| 4fa5e7e6ea | |||
| aedff688e9 | |||
| a7a6d32f3b | |||
| 1d39913329 | |||
| 1cdbf1089b | |||
| b1aacfaab4 | |||
| 4260b9a0bf | |||
| 7e150c3221 | |||
| 4d8935e15e | |||
| 8926183e8e | |||
| 8288ac9e11 | |||
| 6bd427fad3 | |||
| 03608d8fc6 | |||
| 01ae2a52cb | |||
| ba49df6fbb | |||
| 2c1e8c2d2d | |||
| 828b8f51b4 | |||
| 9b5861e5d1 | |||
| b4dddbfd75 | |||
| d7eb914233 | |||
| 9e62c289ce | |||
| 564da75657 | |||
| e7582fab55 | |||
| 47e76e08b6 | |||
| 8126a90efb | |||
| e8b7ff09db | |||
| 71b78c2d21 | |||
| f35845fcad | |||
| a76cc39623 | |||
| f896d4b381 | |||
| 3ca9f3a5e0 | |||
| e77c82dc1d | |||
| 857fa9d371 | |||
| 1abad7745b | |||
| 4449bd5399 | |||
| b7dcc4e960 | |||
| b1d5327464 | |||
| 1f2ecfb129 | |||
| e05f5eb00e | |||
| d348e7a481 | |||
| 67b633fa5b | |||
| e14847283c | |||
| 8bbcf2a84c | |||
| c5b9e1af04 | |||
| d52b85dfb5 | |||
| 971fc7f1a8 | |||
| 465cbf2813 | |||
| 0f51395604 | |||
| b9f78d526d | |||
| 0528058a20 | |||
| 10837f283a | |||
| 920eb9e0eb | |||
| 56d29efbd1 | |||
| 17ccafb6f0 | |||
| 6bfb046d86 | |||
| 1619586794 | |||
| 86079406d9 | |||
| 8a79c5fb4b | |||
| 27c9a39712 | |||
| 5a75954abb | |||
| 58168c958c | |||
| 80e9a89704 | |||
| df91fc31a3 | |||
| 4b26a84586 | |||
| 6ee622ad13 | |||
| f7cf98ea86 | |||
| 8c1fe31370 | |||
| fe92590370 | |||
| 1718b04587 | |||
| 44cb65c5a3 | |||
| dc7dea0cf2 | |||
| 81a7de4721 | |||
| 4027f44c77 | |||
| 5c572f8a6b | |||
| 08cc1f3d32 | |||
| 5a1fb35680 | |||
| 6f18148684 | |||
| 6e6d5fa3cf | |||
| 37170e5714 | |||
| fb680bb33d | |||
| 4c872ab444 | |||
| 7cbd576777 | |||
| 2c8cfb9e13 | |||
| 1bfe81f05d | |||
| 2cf09ff8f3 | |||
| d9284eee0f | |||
| aa1723e748 | |||
| c079d2c3ea | |||
| 39aa019a86 | |||
| 882235917f | |||
| e12ffcdda0 | |||
| b2c2f2a0c5 | |||
| 7a7f029149 | |||
| 6cfefde94b | |||
| be8d3d004f | |||
| d42c5d9311 | |||
| 1c0e0f55bf | |||
| c5aebec657 | |||
| 4e689e468e | |||
| 6b4d33a98f | |||
| 506a4a33d4 | |||
| afe3c2b88f | |||
| 1a8415f1f1 | |||
| caab968624 | |||
| f3748c1006 | |||
| 72f49e4c76 | |||
| 4e8e5ca6ca | |||
| 3cf9457a36 | |||
| 31d96fb54f | |||
| 43874f1138 | |||
| 77e3a9f987 | |||
| e5269918b5 | |||
| 857c90735e | |||
| 1d95966c57 | |||
| f9a2bb1125 | |||
| cba59e58ec | |||
| 6d92262ecb | |||
| f3227bee2e | |||
| b1ba8e672e | |||
| 1ecd11e72e | |||
| 3e2dbf557b | |||
| 50c2251ef2 | |||
| 4dce7cdcd0 | |||
| 1241a17d4b | |||
| d3ec08f146 | |||
| c2bd37c0ec | |||
| 417c399bf6 | |||
| ffb5bfee38 | |||
| 6f84540075 | |||
| c6d921e53a | |||
| 890849b3e1 | |||
| f3b0bb1896 | |||
| a528f9ca2e | |||
| 1768ae93e8 | |||
| aa24806a89 | |||
| 156d7a5845 | |||
| 6bf10930c7 | |||
| adfde92245 | |||
| 641639c0a7 | |||
| f4254a24ad | |||
| cbeefb6854 | |||
| 5567567e5b | |||
| ec6551ce80 | |||
| 9118477079 | |||
| 53f41838e7 | |||
| 1b56df014c | |||
| 2edd3e1155 | |||
| a68bc9319a | |||
| 23a0e2a9a4 | |||
| d88cbc344d | |||
| e4c595aac8 | |||
| d3eaf24089 | |||
| 39012d0b09 | |||
| 3dcfe6520b | |||
| d4c569d95e | |||
| 35ad89a715 | |||
| f557352f22 | |||
| 894f2f161e | |||
| f6c8f4a098 | |||
| 93045eb621 | |||
| 5bff1a62b1 | |||
| e195cb3905 | |||
| 3c3f130d2c | |||
| 99809ba681 | |||
| 9c9b31658d | |||
| 23ad73ee56 | |||
| 9ce4d3a2de | |||
| b17cba4ef7 | |||
| f0c84172fb | |||
| c9913935bf | |||
| 72fb26ef25 | |||
| 8e40b622bd | |||
| f009e8e887 | |||
| e46b6a53ed | |||
| 1646ab8620 | |||
| 4290c0db02 | |||
| 61dc480cfa | |||
| c13c75342f | |||
| e394149084 | |||
| b275637a13 | |||
| 771e06bb2f | |||
| e3a4f45aa7 | |||
| 794b06cf63 | |||
| f833fba135 | |||
| 8fec0dc9e6 | |||
| df9acfa2ec | |||
| 52ef39651d | |||
| c03ad2f40d | |||
| 9e2daa9d6c | |||
| 6eb5c80e75 | |||
| d807b51210 | |||
| 6880fa0c12 | |||
| 31c83f8aa5 | |||
| 1ef32165a0 | |||
| ede9478779 | |||
| c083984fd0 | |||
| 7049a8334c | |||
| 2025178343 | |||
| 0620c07639 | |||
| 39096ac3d1 | |||
| d404adf500 | |||
| dd9b7e8deb | |||
| b357491d00 | |||
| 6139a04b64 | |||
| 9ebe2f8676 | |||
| a2aaba5f5d | |||
| 4112aa8957 | |||
| 39026940b3 | |||
| 0e0bdbdd76 | |||
| 5a40f5524e | |||
| 806b3f2d69 | |||
| 3d812fe6c3 | |||
| 367a2a7678 | |||
| 70ba060e57 | |||
| f5fc38f35a | |||
| dcd55b53a7 | |||
| a45cfb7c7b | |||
| c300545e9e | |||
| 66a22969e1 |
@@ -51,7 +51,7 @@ jobs:
|
||||
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")
|
||||
done < <(find . -name "*_test.php" -not -path "*/fixtures/*")
|
||||
# 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"
|
||||
|
||||
+21
-20
@@ -56,6 +56,14 @@ jobs:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
postgres:
|
||||
image: ${{ matrix.db == 'pgsql' && 'postgres:13' || '' }}
|
||||
env:
|
||||
POSTGRES_DB: test
|
||||
POSTGRES_USER: test
|
||||
POSTGRES_PASSWORD: test
|
||||
ports:
|
||||
- 5432:5432
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -71,27 +79,20 @@ jobs:
|
||||
db: pgsql
|
||||
|
||||
steps:
|
||||
- name: Setting up DB mysql
|
||||
- name: Run MySQL Server
|
||||
if: ${{ matrix.db == 'mysqli' }}
|
||||
uses: moodlehq/mysql-action@v1
|
||||
with:
|
||||
collation server: utf8mb4_bin
|
||||
mysql version: 8.0
|
||||
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: 13
|
||||
postgresql db: test
|
||||
postgresql user: test
|
||||
postgresql password: test
|
||||
run: |
|
||||
docker run --rm \
|
||||
-e MYSQL_DATABASE=test \
|
||||
-e MYSQL_USER=test \
|
||||
-e MYSQL_PASSWORD=test \
|
||||
-e MYSQL_ROOT_PASSWORD=test \
|
||||
-p 3306:3306 \
|
||||
-d \
|
||||
--tmpfs /var/lib/mysql:rw,noexec,nosuid,size=1024M \
|
||||
mysql:8.0 \
|
||||
--skip-log-bin \
|
||||
--collation-server=utf8mb4_bin
|
||||
|
||||
- name: Configuring git vars
|
||||
uses: rlespinasse/github-slug-action@v4
|
||||
|
||||
@@ -18,7 +18,7 @@ import inquirer from 'inquirer';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { createNote } from './note.mjs';
|
||||
import { getInitialValues } from './helpers.mjs';
|
||||
import { getInitialValues, getIssueNumberFromBranchName } from './helpers.mjs';
|
||||
import * as Prompts from './prompts.mjs';
|
||||
import logger from './logger.mjs';
|
||||
|
||||
@@ -29,7 +29,7 @@ export default async (options) => {
|
||||
// Fetch information.
|
||||
const messages = [];
|
||||
const { issueNumber } = await inquirer.prompt([
|
||||
Prompts.getIssuePrompt(),
|
||||
Prompts.getIssuePrompt(getIssueNumberFromBranchName()),
|
||||
], initialValues);
|
||||
|
||||
let selection = {};
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import chalk from 'chalk';
|
||||
import branchName from 'current-git-branch';
|
||||
import { isStandardComponent, isCommunityComponent, rewritePlugintypeAsSubsystem } from './components.mjs';
|
||||
import { isValidNoteName } from './noteTypes.mjs';
|
||||
import logger from './logger.mjs';
|
||||
@@ -59,6 +60,19 @@ export const formatIssueNumber = (input) => {
|
||||
return input;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the issue number, based off current git branch name
|
||||
*
|
||||
* @returns {string|null}
|
||||
*/
|
||||
export const getIssueNumberFromBranchName = () => {
|
||||
const issueNumber = String(branchName()).match(/^[a-zA-Z]*-\d+/);
|
||||
if (issueNumber) {
|
||||
return formatIssueNumber(issueNumber[0]);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate a component.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
issueNumber: MDL-75067
|
||||
notes:
|
||||
tool_behat:
|
||||
- message: >-
|
||||
The `behat_session_trait::ensure_element_[does_not_]exists(...)` methods
|
||||
now accept optional `$container` parameter to define the parent node to
|
||||
look within
|
||||
type: improved
|
||||
@@ -0,0 +1,7 @@
|
||||
issueNumber: MDL-87100
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
When creating upgrade notes, the issue number will be inferred from the
|
||||
current Git branch name by default
|
||||
type: improved
|
||||
@@ -0,0 +1,7 @@
|
||||
issueNumber: MDL-87443
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
There is a new Behat `toast_message` named selector to more easily
|
||||
assert the presence of Toast messages on the page
|
||||
type: improved
|
||||
@@ -0,0 +1,8 @@
|
||||
issueNumber: MDL-87993
|
||||
notes:
|
||||
core:
|
||||
- message: >-
|
||||
The `core/toast` JS module now accepts a `visuallyHidden` configuration
|
||||
parameter to render visually hidden toast messages for screen reader
|
||||
users.
|
||||
type: improved
|
||||
@@ -32,7 +32,7 @@ Moodle also offers hosting through both [MoodleCloud][11], and our [partner netw
|
||||
|
||||
## License
|
||||
|
||||
Moodle is provided freely as open source software, under version 3 of the GNU General Public License. For more information on our license see
|
||||
Moodle is provided freely as open source software, under version 3 of the GNU General Public License. See our [license page][12] for more information.
|
||||
|
||||
[1]: https://moodle.org
|
||||
[2]: https://moodle.com
|
||||
|
||||
@@ -6,6 +6,40 @@ More detailed information on key changes can be found in the [Developer update n
|
||||
|
||||
The format of this change log follows the advice given at [Keep a CHANGELOG](https://keepachangelog.com).
|
||||
|
||||
## 4.5.10+
|
||||
|
||||
### core
|
||||
|
||||
#### Added
|
||||
|
||||
- When creating upgrade notes, the issue number will be inferred from the current Git branch name by default
|
||||
|
||||
For more information see [MDL-87100](https://tracker.moodle.org/browse/MDL-87100)
|
||||
- There is a new Behat `toast_message` named selector to more easily assert the presence of Toast messages on the page
|
||||
|
||||
For more information see [MDL-87443](https://tracker.moodle.org/browse/MDL-87443)
|
||||
- The `core/toast` JS module now accepts a `visuallyHidden` configuration parameter to render visually hidden toast messages for screen reader users.
|
||||
|
||||
For more information see [MDL-87993](https://tracker.moodle.org/browse/MDL-87993)
|
||||
|
||||
### tool_behat
|
||||
|
||||
#### Added
|
||||
|
||||
- The `behat_session_trait::ensure_element_[does_not_]exists(...)` methods now accept optional `$container` parameter to define the parent node to look within
|
||||
|
||||
For more information see [MDL-75067](https://tracker.moodle.org/browse/MDL-75067)
|
||||
|
||||
## 4.5.9
|
||||
|
||||
### core
|
||||
|
||||
#### Changed
|
||||
|
||||
- `\core\output\core_renderer::confirm()`'s `$displayoptions` parameter now also accepts a `headinglevel` option that developers can use to specify the heading level of the confirmation's heading. If not specified, the confirmation heading will be rendered in an `h4` tag.
|
||||
|
||||
For more information see [MDL-87694](https://tracker.moodle.org/browse/MDL-87694)
|
||||
|
||||
## 4.5.8
|
||||
|
||||
### core
|
||||
|
||||
+24
-6
@@ -941,9 +941,27 @@ admin_externalpage_setup('adminnotifications');
|
||||
|
||||
$output = $PAGE->get_renderer('core', 'admin');
|
||||
|
||||
echo $output->admin_notifications_page($maturity, $insecuredataroot, $errorsdisplayed, $cronoverdue, $dbproblems,
|
||||
$maintenancemode, $availableupdates, $availableupdatesfetch, $buggyiconvnomb,
|
||||
$registered, $cachewarnings, $eventshandlers, $themedesignermode, $devlibdir,
|
||||
$mobileconfigured, $overridetossl, $invalidforgottenpasswordurl, $croninfrequent,
|
||||
$showcampaigncontent, $showfeedbackencouragement, $servicesandsupportcontent,
|
||||
$xmlrpcwarning);
|
||||
echo $output->admin_notifications_page(
|
||||
$maturity,
|
||||
$insecuredataroot,
|
||||
$errorsdisplayed,
|
||||
$cronoverdue,
|
||||
$dbproblems,
|
||||
$maintenancemode,
|
||||
$availableupdates,
|
||||
$availableupdatesfetch,
|
||||
$buggyiconvnomb,
|
||||
$registered,
|
||||
$cachewarnings,
|
||||
$eventshandlers,
|
||||
$themedesignermode,
|
||||
$devlibdir,
|
||||
$mobileconfigured,
|
||||
$overridetossl,
|
||||
$invalidforgottenpasswordurl,
|
||||
$croninfrequent,
|
||||
$showcampaigncontent,
|
||||
$showfeedbackencouragement,
|
||||
$servicesandsupportcontent,
|
||||
$xmlrpcwarning
|
||||
);
|
||||
|
||||
+53
-7
@@ -282,13 +282,30 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
*
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function admin_notifications_page($maturity, $insecuredataroot, $errorsdisplayed,
|
||||
$cronoverdue, $dbproblems, $maintenancemode, $availableupdates, $availableupdatesfetch,
|
||||
$buggyiconvnomb, $registered, array $cachewarnings = array(), $eventshandlers = 0,
|
||||
$themedesignermode = false, $devlibdir = false, $mobileconfigured = false,
|
||||
$overridetossl = false, $invalidforgottenpasswordurl = false, $croninfrequent = false,
|
||||
$showcampaigncontent = false, bool $showfeedbackencouragement = false, bool $showservicesandsupport = false,
|
||||
$xmlrpcwarning = '') {
|
||||
public function admin_notifications_page(
|
||||
$maturity,
|
||||
$insecuredataroot,
|
||||
$errorsdisplayed,
|
||||
$cronoverdue,
|
||||
$dbproblems,
|
||||
$maintenancemode,
|
||||
$availableupdates,
|
||||
$availableupdatesfetch,
|
||||
$buggyiconvnomb,
|
||||
$registered,
|
||||
array $cachewarnings = [],
|
||||
$eventshandlers = 0,
|
||||
$themedesignermode = false,
|
||||
$devlibdir = false,
|
||||
$mobileconfigured = false,
|
||||
$overridetossl = false,
|
||||
$invalidforgottenpasswordurl = false,
|
||||
$croninfrequent = false,
|
||||
$showcampaigncontent = false,
|
||||
bool $showfeedbackencouragement = false,
|
||||
bool $showservicesandsupport = false,
|
||||
$xmlrpcwarning = ''
|
||||
) {
|
||||
|
||||
global $CFG;
|
||||
$output = '';
|
||||
@@ -314,6 +331,8 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$output .= $this->mobile_configuration_warning($mobileconfigured);
|
||||
$output .= $this->forgotten_password_url_warning($invalidforgottenpasswordurl);
|
||||
$output .= $this->mnet_deprecation_warning($xmlrpcwarning);
|
||||
$output .= $this->moodlenet_removal_warning();
|
||||
$output .= $this->marketplace_integration_notice();
|
||||
$output .= $this->userfeedback_encouragement($showfeedbackencouragement);
|
||||
$output .= $this->services_and_support_content($showservicesandsupport);
|
||||
$output .= $this->campaign_content($showcampaigncontent);
|
||||
@@ -2267,6 +2286,33 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
return $this->warning($xmlrpcwarning);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a warning about the removal of MoodleNet integration.
|
||||
*
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
protected function moodlenet_removal_warning(): string {
|
||||
$moodlenetenabled = get_config('tool_moodlenet', 'enablemoodlenet');
|
||||
if (!empty($moodlenetenabled)) {
|
||||
$moodlenetwarning = get_string('moodlenetremovalwarning', 'admin');
|
||||
return $this->warning($moodlenetwarning);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a notice about Moodle Marketplace integration.
|
||||
*
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
protected function marketplace_integration_notice(): string {
|
||||
$installer = tool_installaddon_installer::instance();
|
||||
$url = $installer->get_marketplace_url();
|
||||
$notice = get_string('marketplaceavailablenotice', 'admin', $url->out());
|
||||
return $this->warning($notice, 'info');
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the theme selector list.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# tool_behat Upgrade notes
|
||||
|
||||
## 4.5.10+
|
||||
|
||||
### Added
|
||||
|
||||
- The `behat_session_trait::ensure_element_[does_not_]exists(...)` methods now accept optional `$container` parameter to define the parent node to look within
|
||||
|
||||
For more information see [MDL-75067](https://tracker.moodle.org/browse/MDL-75067)
|
||||
|
||||
## 4.5.2
|
||||
|
||||
### Added
|
||||
|
||||
@@ -224,6 +224,11 @@ if (empty($parallelrun)) {
|
||||
$cmds['singlerun'] = $runtestscommand;
|
||||
|
||||
echo "Running single behat site:" . PHP_EOL;
|
||||
// The inner PHP process is not marked as having an interactive terminal even if it's passed
|
||||
// through from this one which does, so we need to pass it through as an environment variable.
|
||||
if (function_exists('posix_isatty') && posix_isatty(STDOUT)) {
|
||||
putenv('MOODLE_BEHAT_RUNNING_IN_TTY=1');
|
||||
}
|
||||
passthru("php $runtestscommand", $status);
|
||||
$exitcodes['singlerun'] = $status;
|
||||
chdir($cwd);
|
||||
@@ -525,4 +530,3 @@ function get_status_lines_from_run_op(Symfony\Component\Process\Process $process
|
||||
|
||||
return $statusstr;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
@tool @tool_dataprivacy
|
||||
Feature: Authorized users can request others personal data
|
||||
In order to export or access another users data
|
||||
As a designated role
|
||||
I need the correct permissions
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| user1 | User1 | One | user1@example.com |
|
||||
| user2 | User2 | Two | user2@example.com |
|
||||
| officer1 | Officer1 | One | officer1@example.com |
|
||||
# Create Privacy Officer Role.
|
||||
And the following "role" exists:
|
||||
| shortname | privacyofficer |
|
||||
| name | Privacy Officer |
|
||||
| context_system | 1 |
|
||||
| tool/dataprivacy:managedataregistry | allow |
|
||||
| tool/dataprivacy:managedatarequests | allow |
|
||||
| tool/dataprivacy:makedatarequestsforchildren | allow |
|
||||
| moodle/site:configview | allow |
|
||||
| moodle/category:viewhiddencategories | allow |
|
||||
| moodle/course:viewhiddencourses | allow |
|
||||
| moodle/course:viewhiddenactivities | allow |
|
||||
| moodle/course:view | allow |
|
||||
# Create Parent Role.
|
||||
And the following "role" exists:
|
||||
| shortname | parentrole |
|
||||
| name | Parent Role |
|
||||
| context_user | 1 |
|
||||
| moodle/user:viewdetails | allow |
|
||||
| moodle/user:viewalldetails | allow |
|
||||
| moodle/user:readuserblogs | allow |
|
||||
| moodle/user:readuserposts | allow |
|
||||
| moodle/user:viewuseractivitiesreport | allow |
|
||||
| moodle/user:editprofile | allow |
|
||||
| tool/policy:acceptbehalf | allow |
|
||||
| tool/dataprivacy:makedatarequestsforchildren | allow |
|
||||
# Add permission to allow parent to make requests on behalf of child user.
|
||||
And the following config values are set as admin:
|
||||
| contactdataprotectionofficer | 1 | tool_dataprivacy |
|
||||
And I log in as "admin"
|
||||
|
||||
@javascript
|
||||
Scenario: Privacy officer can request for other user's personal data
|
||||
Given I navigate to "Users > Permissions > Assign system roles" in site administration
|
||||
# Assign Privacy Officer role to officer1.
|
||||
And I follow "Privacy Officer"
|
||||
And I set the field "addselect_searchtext" to "Officer1"
|
||||
And I set the field "addselect" to "Officer1 One (officer1@example.com)"
|
||||
And I press "Add"
|
||||
# Navigate to home in order to navigate properly to Privacy settings.
|
||||
And I am on site homepage
|
||||
# Select Privacy officer in the Orivacy officer role mapping setting.
|
||||
And I navigate to "Users > Privacy and policies > Privacy settings" in site administration
|
||||
And I click on "Privacy Officer" "checkbox"
|
||||
And I press "Save changes"
|
||||
And I log in as "officer1"
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
# Create a new request as the designated privacy officer.
|
||||
When I follow "New request"
|
||||
And I set the field "User" to "User1 One"
|
||||
And I set the field "Comment" to "User One data"
|
||||
And I press "Save changes"
|
||||
# Confirm that the new data request is successfully created for selected user with status "Awaiting approval".
|
||||
Then the following should exist in the "generaltable" table:
|
||||
| Type | User | Requested by | Status | Message |
|
||||
| Export | User1 One | Officer1 One | Awaiting approval | User One data |
|
||||
|
||||
@javascript
|
||||
Scenario: Parent user can request data on behalf of child user
|
||||
Given I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
And I follow "User1 One"
|
||||
And I click on "Preferences" "link" in the ".profile_tree" "css_element"
|
||||
# Assign user2 as parent for user1.
|
||||
And I follow "Assign roles relative to this user"
|
||||
And I follow "Parent"
|
||||
And I set the field "Potential users" to "User2 Two (user2@example.com)"
|
||||
And I click on "Add" "button" in the "#page-content" "css_element"
|
||||
And I log in as "user2"
|
||||
And I follow "Profile" in the user menu
|
||||
And I follow "Data requests"
|
||||
# As parent, create a data request for a child user.
|
||||
And I follow "New request"
|
||||
And I click on "User" "field"
|
||||
When I type "User1 One"
|
||||
# Confirm that only the parent's child users can be searched and selected.
|
||||
Then I should see "User1 One"
|
||||
And I type "User2 Two"
|
||||
And I should see "No suggestions"
|
||||
And I type "Officer1 One"
|
||||
And I should see "No suggestions"
|
||||
And I set the field "Search" to "User1"
|
||||
And I set the field "Comment" to "This is a comment"
|
||||
And I press "Save changes"
|
||||
# Confirm that data request was successfully made by parent on behalf of child user.
|
||||
And I should see "Your request has been submitted to the privacy officer"
|
||||
And the following should exist in the "generaltable" table:
|
||||
| Type | Requested by | Status | Message |
|
||||
| Export all of my personal data (User1 One) | User2 Two | Awaiting approval | This is a comment |
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
define("tool_installaddon/footer",["exports","jquery","core_course/local/activitychooser/selectors"],(function(_exports,_jquery,_selectors){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
|
||||
/**
|
||||
* Activity chooser footer handlers for tool_installaddon.
|
||||
*
|
||||
* @module tool_installaddon/footer
|
||||
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.footerClickListener=void 0,_jquery=_interopRequireDefault(_jquery),_selectors=_interopRequireDefault(_selectors);_exports.footerClickListener=(e,footerData,modal)=>{const closeOption=e.target.closest(_selectors.default.actions.closeOption);if(!closeOption)return;const moduleName=closeOption.dataset.modname;if(!moduleName)return;const carousel=(0,_jquery.default)(modal.getBody()[0].querySelector(_selectors.default.regions.carousel));carousel.carousel("prev"),modal.setFooter(footerData.customfootertemplate),carousel.one("slid.bs.carousel",(()=>{const caller=modal.getBody()[0].querySelector(_selectors.default.regions.modules).querySelector(_selectors.default.regions.getModuleSelector(moduleName));caller&&caller.focus()}))}}));
|
||||
|
||||
//# sourceMappingURL=footer.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"footer.min.js","sources":["../src/footer.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Activity chooser footer handlers for tool_installaddon.\n *\n * @module tool_installaddon/footer\n * @copyright 2026 Safat Shahin <safat.shahin@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport selectors from 'core_course/local/activitychooser/selectors';\n\n/**\n * Handle clicks in the chooser footer when installaddon is the active footer plugin.\n *\n * @param {Event} e The event being triggered\n * @param {Object} footerData The footer data generated for the chooser\n * @param {Object} modal The chooser modal\n */\nexport const footerClickListener = (e, footerData, modal) => {\n const closeOption = e.target.closest(selectors.actions.closeOption);\n\n if (!closeOption) {\n return;\n }\n\n const moduleName = closeOption.dataset.modname;\n\n if (!moduleName) {\n return;\n }\n\n const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));\n\n // Trigger the transition between 'pages'.\n carousel.carousel('prev');\n modal.setFooter(footerData.customfootertemplate);\n carousel.one('slid.bs.carousel', () => {\n const allModules = modal.getBody()[0].querySelector(selectors.regions.modules);\n const caller = allModules.querySelector(selectors.regions.getModuleSelector(moduleName));\n if (caller) {\n caller.focus();\n }\n });\n};\n"],"names":["e","footerData","modal","closeOption","target","closest","selectors","actions","moduleName","dataset","modname","carousel","getBody","querySelector","regions","setFooter","customfootertemplate","one","caller","modules","getModuleSelector","focus"],"mappings":";;;;;;;oNAiCmC,CAACA,EAAGC,WAAYC,eACzCC,YAAcH,EAAEI,OAAOC,QAAQC,mBAAUC,QAAQJ,iBAElDA,yBAICK,WAAaL,YAAYM,QAAQC,YAElCF,wBAICG,UAAW,mBAAET,MAAMU,UAAU,GAAGC,cAAcP,mBAAUQ,QAAQH,WAGtEA,SAASA,SAAS,QAClBT,MAAMa,UAAUd,WAAWe,sBAC3BL,SAASM,IAAI,oBAAoB,WAEvBC,OADahB,MAAMU,UAAU,GAAGC,cAAcP,mBAAUQ,QAAQK,SAC5CN,cAAcP,mBAAUQ,QAAQM,kBAAkBZ,aACxEU,QACAA,OAAOG"}
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Activity chooser footer handlers for tool_installaddon.
|
||||
*
|
||||
* @module tool_installaddon/footer
|
||||
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import selectors from 'core_course/local/activitychooser/selectors';
|
||||
|
||||
/**
|
||||
* Handle clicks in the chooser footer when installaddon is the active footer plugin.
|
||||
*
|
||||
* @param {Event} e The event being triggered
|
||||
* @param {Object} footerData The footer data generated for the chooser
|
||||
* @param {Object} modal The chooser modal
|
||||
*/
|
||||
export const footerClickListener = (e, footerData, modal) => {
|
||||
const closeOption = e.target.closest(selectors.actions.closeOption);
|
||||
|
||||
if (!closeOption) {
|
||||
return;
|
||||
}
|
||||
|
||||
const moduleName = closeOption.dataset.modname;
|
||||
|
||||
if (!moduleName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));
|
||||
|
||||
// Trigger the transition between 'pages'.
|
||||
carousel.carousel('prev');
|
||||
modal.setFooter(footerData.customfootertemplate);
|
||||
carousel.one('slid.bs.carousel', () => {
|
||||
const allModules = modal.getBody()[0].querySelector(selectors.regions.modules);
|
||||
const caller = allModules.querySelector(selectors.regions.getModuleSelector(moduleName));
|
||||
if (caller) {
|
||||
caller.focus();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -70,6 +70,25 @@ class tool_installaddon_installer {
|
||||
$url = 'https://moodle.org/plugins/get.php';
|
||||
}
|
||||
|
||||
return $this->get_external_service_url($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns URL to Moodle Marketplace.
|
||||
*
|
||||
* @return moodle_url
|
||||
*/
|
||||
public function get_marketplace_url(): moodle_url {
|
||||
return $this->get_external_service_url('https://marketplace.moodle.com/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns URL to external service with optional encoded site information.
|
||||
*
|
||||
* @param string $url The external service base URL.
|
||||
* @return moodle_url
|
||||
*/
|
||||
protected function get_external_service_url(string $url): moodle_url {
|
||||
if (!$this->should_send_site_info()) {
|
||||
return new moodle_url($url);
|
||||
}
|
||||
|
||||
+9
-6
@@ -16,20 +16,23 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace tool_moodlenet\task;
|
||||
namespace tool_installaddon\task;
|
||||
|
||||
/**
|
||||
* Ad-hoc task to perform post install tasks.
|
||||
* We use this to set the active activity chooser footer plugin to tool_moodlenet.
|
||||
* We use this to set the active activity chooser footer plugin to tool_installaddon.
|
||||
* We couldn't do this directly in install.php, because there is an admin_apply_default_settings() call after all plugins are
|
||||
* installed and that would reset whatever value we had set earlier to 'hidden'.
|
||||
*
|
||||
* @package tool_moodlenet
|
||||
* @copyright 2022 Shamim Rezaie <shamim@moodle.com>
|
||||
* @package tool_installaddon
|
||||
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class post_install extends \core\task\adhoc_task {
|
||||
public function execute() {
|
||||
set_config('activitychooseractivefooter', 'tool_moodlenet');
|
||||
/**
|
||||
* Sets the active activity chooser footer plugin after install defaults are applied.
|
||||
*/
|
||||
public function execute(): void {
|
||||
set_config('activitychooseractivefooter', 'tool_installaddon');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Install script for tool_installaddon.
|
||||
*
|
||||
* @package tool_installaddon
|
||||
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Perform the post-install procedures.
|
||||
*/
|
||||
function xmldb_tool_installaddon_install(): void {
|
||||
// Use an ad-hoc task to set the active activity chooser footer plugin to tool_installaddon.
|
||||
// We couldn't do this in admin/settings/courses.php for 2 reasons:
|
||||
// - First, because it would be a breach of component communications principles to do so there.
|
||||
// - Second, because we can't call get_plugins_with_function() during install and upgrade (or it will return []).
|
||||
// We couldn't do this directly here either, because there is an admin_apply_default_settings() call after all plugins are
|
||||
// installed and that would reset whatever value we set here to 'hidden'.
|
||||
$postinstall = new tool_installaddon\task\post_install();
|
||||
core\task\manager::queue_adhoc_task($postinstall);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Upgrade script for tool_installaddon.
|
||||
*
|
||||
* @package tool_installaddon
|
||||
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Upgrade the plugin.
|
||||
*
|
||||
* @param int $oldversion
|
||||
* @return bool always true
|
||||
*/
|
||||
function xmldb_tool_installaddon_upgrade(int $oldversion): bool {
|
||||
if ($oldversion < 2024100701) {
|
||||
// Set the activity chooser active footer to include marketplace regardless of the previous setting.
|
||||
// We are deliberately setting this to increase awareness of marketplace.
|
||||
set_config('activitychooseractivefooter', 'tool_installaddon');
|
||||
|
||||
upgrade_plugin_savepoint(true, 2024100701, 'tool', 'installaddon');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['acknowledgement'] = 'Acknowledgement';
|
||||
$string['acknowledgementtext'] = 'I understand that it is my responsibility to have full backups of this site prior to installing additional plugins. I accept and understand that plugins (especially but not only those originating in unofficial sources) may contain security holes, can make the site unavailable, or cause private data leaks or loss.';
|
||||
$string['activitychooserfootertext'] = 'Browse more activities on';
|
||||
$string['featuredisabled'] = 'The plugin installer is disabled on this site.';
|
||||
$string['installaddon'] = 'Install plugin!';
|
||||
$string['installaddons'] = 'Install plugins';
|
||||
@@ -44,6 +45,8 @@ $string['installfromzipsubmit'] = 'Install plugin from the ZIP file';
|
||||
$string['installfromziptype'] = 'Plugin type';
|
||||
$string['installfromziptype_help'] = 'For plugins that correctly declare their component name, the installer is able to detect the plugin type automatically. If the auto-detection fails, choose the correct type of plugin manually. Warning: The installation procedure can fail badly if an incorrect plugin type is specified.';
|
||||
$string['installfromziptype_link'] = 'Development:Plugins';
|
||||
$string['marketplaceadminlinktext'] = 'Browse new plugins';
|
||||
$string['marketplacelink'] = 'Moodle Marketplace';
|
||||
$string['permcheck'] = 'Make sure the plugin type root location is writable by the web server process.';
|
||||
$string['permcheckerror'] = 'Error while checking for write permission';
|
||||
$string['permcheckprogress'] = 'Checking for write permission ...';
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Public API for tool_installaddon.
|
||||
*
|
||||
* @package tool_installaddon
|
||||
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use core_course\local\entity\activity_chooser_footer;
|
||||
|
||||
/**
|
||||
* Build activity chooser footer content for Marketplace.
|
||||
*
|
||||
* @param int $courseid The selected course id.
|
||||
* @param int $sectionid The selected section id.
|
||||
* @return activity_chooser_footer
|
||||
*/
|
||||
function tool_installaddon_custom_chooser_footer(int $courseid, int $sectionid): activity_chooser_footer {
|
||||
global $OUTPUT;
|
||||
|
||||
$installer = tool_installaddon_installer::instance();
|
||||
$marketplaceurl = $installer->get_marketplace_url();
|
||||
|
||||
$renderedfooter = $OUTPUT->render_from_template('tool_installaddon/chooser_footer', [
|
||||
'url' => $marketplaceurl->out(false),
|
||||
]);
|
||||
|
||||
return new activity_chooser_footer(
|
||||
'tool_installaddon/footer',
|
||||
$renderedfooter
|
||||
);
|
||||
}
|
||||
@@ -26,6 +26,12 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
if ($hassiteconfig and empty($CFG->disableupdateautodeploy)) {
|
||||
$installer = tool_installaddon_installer::instance();
|
||||
$ADMIN->add('modules', new admin_externalpage(
|
||||
'tool_installaddon_marketplace',
|
||||
get_string('marketplaceadminlinktext', 'tool_installaddon'),
|
||||
$installer->get_marketplace_url()->out(false)
|
||||
), 'modsettings');
|
||||
|
||||
$ADMIN->add('modules', new admin_externalpage('tool_installaddon_index',
|
||||
get_string('installaddons', 'tool_installaddon'),
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{{!
|
||||
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 tool_installaddon/chooser_footer
|
||||
|
||||
Activity chooser footer link to Marketplace.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"url": "https://marketplace.moodle.com/?site=hash"
|
||||
}
|
||||
}}
|
||||
<div class="w-100 d-flex px-2">
|
||||
<span class="my-auto me-1">{{#str}} activitychooserfootertext, tool_installaddon {{/str}}</span>
|
||||
{{> tool_installaddon/marketplace_link }}
|
||||
</div>
|
||||
@@ -0,0 +1,36 @@
|
||||
{{!
|
||||
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 tool_installaddon/marketplace_link
|
||||
|
||||
Reusable link to Moodle Marketplace.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"url": "https://marketplace.moodle.com/?site=hash"
|
||||
}
|
||||
}}
|
||||
<a class="d-inline my-auto" href="{{url}}" target="_blank" rel="noopener noreferrer">
|
||||
{{#str}} marketplacelink, tool_installaddon {{/str}}
|
||||
<i
|
||||
class="fa fa-external-link"
|
||||
role="img"
|
||||
aria-label="{{#str}} opensinnewwindow, core {{/str}}"
|
||||
title="{{#str}} opensinnewwindow, core {{/str}}"
|
||||
>
|
||||
</i>
|
||||
</a>
|
||||
@@ -0,0 +1,37 @@
|
||||
@tool @tool_installaddon @javascript
|
||||
Feature: Marketplace activity chooser footer
|
||||
In order to browse plugins from the activity chooser
|
||||
As a teacher
|
||||
I need to see the Marketplace footer link when installaddon is the active footer plugin
|
||||
|
||||
Background:
|
||||
Given the following config values are set as admin:
|
||||
| activitychooseractivefooter | tool_installaddon |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category | format |
|
||||
| Course 1 | C1 | 0 | topics |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
|
||||
Scenario: Activity chooser footer includes Marketplace link
|
||||
Given I log in as "teacher1"
|
||||
When I am on "Course 1" course homepage with editing mode on
|
||||
And I open the activity chooser
|
||||
Then "Browse more activities on" "text" should exist in the "Add an activity or resource" "dialogue"
|
||||
And "Marketplace" "link" should exist in the "Add an activity or resource" "dialogue"
|
||||
|
||||
Scenario: Back button works from activity help when the Marketplace footer is active
|
||||
Given I log in as "teacher1"
|
||||
When I am on "Course 1" course homepage with editing mode on
|
||||
And I open the activity chooser
|
||||
And I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue"
|
||||
Then "help" "core_course > Activity chooser screen" should be visible
|
||||
When I click on "Back" "button" in the "Add an activity or resource" "dialogue"
|
||||
Then "modules" "core_course > Activity chooser screen" should be visible
|
||||
And "help" "core_course > Activity chooser screen" should not be visible
|
||||
And "Marketplace" "link" should exist in the "Add an activity or resource" "dialogue"
|
||||
And "Back" "button" should not exist in the "Add an activity or resource" "dialogue"
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Provides a mock testable_tool_installaddon_installer_without_site_info class.
|
||||
*
|
||||
* @package tool_installaddon
|
||||
* @subpackage fixtures
|
||||
* @category test
|
||||
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__ . '/testable_installer.php');
|
||||
|
||||
/**
|
||||
* Testable subclass with site-info sharing disabled.
|
||||
*
|
||||
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class testable_tool_installaddon_installer_without_site_info extends testable_tool_installaddon_installer {
|
||||
/**
|
||||
* Disable site info sharing.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_send_site_info() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,14 @@
|
||||
namespace tool_installaddon;
|
||||
|
||||
use testable_tool_installaddon_installer;
|
||||
use testable_tool_installaddon_installer_without_site_info;
|
||||
use tool_installaddon_installer;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once(__DIR__.'/fixtures/testable_installer.php');
|
||||
require_once(__DIR__ . '/fixtures/testable_installer.php');
|
||||
require_once(__DIR__ . '/fixtures/testable_installer_without_site_info.php');
|
||||
|
||||
/**
|
||||
* Unit tests for the {@link tool_installaddon_installer} class
|
||||
@@ -32,16 +34,14 @@ require_once(__DIR__.'/fixtures/testable_installer.php');
|
||||
* @category test
|
||||
* @copyright 2013 David Mudrak <david@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \tool_installaddon_installer
|
||||
*/
|
||||
final class installer_test extends \advanced_testcase {
|
||||
|
||||
public function test_get_addons_repository_url(): void {
|
||||
$installer = testable_tool_installaddon_installer::instance();
|
||||
$url = $installer->get_addons_repository_url();
|
||||
$query = parse_url($url, PHP_URL_QUERY);
|
||||
$this->assertEquals(1, preg_match('~^site=(.+)$~', $query, $matches));
|
||||
$site = rawurldecode($matches[1]);
|
||||
$site = json_decode(base64_decode($site), true);
|
||||
$site = $this->decode_site_info_from_url($url);
|
||||
$this->assertIsArray($site);
|
||||
$this->assertEquals(3, count($site));
|
||||
$this->assertSame('Nasty site', $site['fullname']);
|
||||
@@ -49,6 +49,34 @@ final class installer_test extends \advanced_testcase {
|
||||
$this->assertSame("2.5'; DROP TABLE mdl_user; --", $site['majorversion']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that marketplace URL includes expected host, scheme and site payload.
|
||||
*/
|
||||
public function test_get_marketplace_url(): void {
|
||||
$installer = testable_tool_installaddon_installer::instance();
|
||||
$addonsurl = $installer->get_addons_repository_url();
|
||||
$marketplaceurl = $installer->get_marketplace_url();
|
||||
|
||||
$this->assertSame('marketplace.moodle.com', parse_url($marketplaceurl, PHP_URL_HOST));
|
||||
$this->assertSame('https', parse_url($marketplaceurl, PHP_URL_SCHEME));
|
||||
$this->assertSame(
|
||||
$this->decode_site_info_from_url($addonsurl),
|
||||
$this->decode_site_info_from_url($marketplaceurl),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that marketplace URL has no query string when site info is unavailable.
|
||||
*/
|
||||
public function test_get_marketplace_url_without_site_info(): void {
|
||||
$installer = testable_tool_installaddon_installer_without_site_info::instance();
|
||||
$marketplaceurl = $installer->get_marketplace_url();
|
||||
|
||||
$this->assertSame('marketplace.moodle.com', parse_url($marketplaceurl, PHP_URL_HOST));
|
||||
$this->assertSame('https', parse_url($marketplaceurl, PHP_URL_SCHEME));
|
||||
$this->assertEmpty(parse_url($marketplaceurl, PHP_URL_QUERY));
|
||||
}
|
||||
|
||||
public function test_decode_remote_request(): void {
|
||||
$installer = testable_tool_installaddon_installer::instance();
|
||||
|
||||
@@ -132,7 +160,7 @@ $plugin->version = 2014121300;
|
||||
$versionphp = file_get_contents($fixtures.'/github/moodle-repository_mahara-master/version.php');
|
||||
$this->assertEquals('repository_mahara', $installer->testable_detect_plugin_component_from_versionphp($versionphp));
|
||||
|
||||
$versionphp = file_get_contents($fixtures.'/nocomponent/baz/version.php');
|
||||
$versionphp = file_get_contents($fixtures . '/nocomponent/baz/version.php');
|
||||
$this->assertFalse($installer->testable_detect_plugin_component_from_versionphp($versionphp));
|
||||
}
|
||||
|
||||
@@ -143,15 +171,28 @@ $plugin->version = 2014121300;
|
||||
$storage1 = $installer->make_installfromzip_storage();
|
||||
$this->assertTrue(is_dir($storage1));
|
||||
$this->assertTrue(is_writable($storage1));
|
||||
file_put_contents($storage1.'/hello.txt', 'Find me if you can!');
|
||||
file_put_contents($storage1 . '/hello.txt', 'Find me if you can!');
|
||||
|
||||
// Check we get unique directory on each call.
|
||||
$storage2 = $installer->make_installfromzip_storage();
|
||||
$this->assertTrue(is_dir($storage2));
|
||||
$this->assertTrue(is_writable($storage2));
|
||||
$this->assertFalse(file_exists($storage2.'/hello.txt'));
|
||||
$this->assertFalse(file_exists($storage2 . '/hello.txt'));
|
||||
|
||||
// Check both are in the same parent directory.
|
||||
$this->assertEquals(dirname($storage1), dirname($storage2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the encoded 'site' URL query parameter.
|
||||
*
|
||||
* @param moodle_url $url
|
||||
* @return array
|
||||
*/
|
||||
private function decode_site_info_from_url(\moodle_url $url): array {
|
||||
$query = parse_url($url, PHP_URL_QUERY);
|
||||
$this->assertEquals(1, preg_match('~^site=(.+)$~', $query, $matches));
|
||||
$site = rawurldecode($matches[1]);
|
||||
return json_decode(base64_decode($site), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Unit tests for tool_installaddon lib.
|
||||
*
|
||||
* @package tool_installaddon
|
||||
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_installaddon;
|
||||
|
||||
use core_course\local\entity\activity_chooser_footer;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/admin/tool/installaddon/lib.php');
|
||||
|
||||
/**
|
||||
* Test installaddon lib functions.
|
||||
*/
|
||||
final class lib_test extends \advanced_testcase {
|
||||
/**
|
||||
* Tests chooser footer generation for marketplace link content.
|
||||
*
|
||||
* @covers ::tool_installaddon_custom_chooser_footer
|
||||
*/
|
||||
public function test_tool_installaddon_custom_chooser_footer(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$footer = \tool_installaddon_custom_chooser_footer(1, 1);
|
||||
|
||||
$this->assertInstanceOf(activity_chooser_footer::class, $footer);
|
||||
$this->assertSame('tool_installaddon/footer', $footer->get_footer_js_file());
|
||||
$this->assertStringContainsString('https://marketplace.moodle.com/', $footer->get_footer_template());
|
||||
$this->assertStringContainsString('site=', $footer->get_footer_template());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace tool_installaddon\task;
|
||||
|
||||
/**
|
||||
* Unit tests for the post install task.
|
||||
*
|
||||
* @package tool_installaddon
|
||||
* @category test
|
||||
* @copyright 2026 Safat Shahin <safat.shahin@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \tool_installaddon\task\post_install
|
||||
*/
|
||||
final class post_install_test extends \advanced_testcase {
|
||||
/**
|
||||
* Tests that the task sets the activity chooser footer plugin to tool_installaddon.
|
||||
*/
|
||||
public function test_execute_sets_activitychooseractivefooter_config(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
set_config('activitychooseractivefooter', 'hidden');
|
||||
|
||||
$task = new post_install();
|
||||
$task->execute();
|
||||
|
||||
$this->assertSame(
|
||||
'tool_installaddon',
|
||||
get_config('core', 'activitychooseractivefooter'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,6 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->component = 'tool_installaddon';
|
||||
$plugin->version = 2024100700;
|
||||
$plugin->version = 2024100701;
|
||||
$plugin->requires = 2024100100;
|
||||
$plugin->maturity = MATURITY_STABLE;
|
||||
|
||||
@@ -28,12 +28,5 @@ declare(strict_types=1);
|
||||
* Perform the post-install procedures.
|
||||
*/
|
||||
function xmldb_tool_moodlenet_install() {
|
||||
// Use an ad-hoc task to set the active activity chooser footer plugin to tool_moodlenet.
|
||||
// We couldn't do this in admin/settings/courses.php for 2 reasons:
|
||||
// - First, because it would be a breach of component communications principles to do so there.
|
||||
// - Second, because we can't call get_plugins_with_function() during install and upgrade (or it will return []).
|
||||
// We couldn't do this directly here either, because there is an admin_apply_default_settings() call after all plugins are
|
||||
// installed and that would reset whatever value we set here to 'hidden'.
|
||||
$postinstall = new tool_moodlenet\task\post_install();
|
||||
core\task\manager::queue_adhoc_task($postinstall);
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ $string['defaultmoodlenet_desc'] = 'The URL of the MoodleNet instance available
|
||||
$string['defaultmoodlenetname'] = "MoodleNet instance name";
|
||||
$string['defaultmoodlenetnamevalue'] = 'MoodleNet Central';
|
||||
$string['defaultmoodlenetname_desc'] = 'The name of the MoodleNet instance available via the activity chooser.';
|
||||
$string['removalwarning_feature'] = 'If you need to continue using MoodleNet, contact your site administrator about setting up a self-hosted MoodleNet instance.';
|
||||
$string['removalwarning_service'] = 'After the date, you will no longer be able to browse or add content from MoodleNet Central.';
|
||||
$string['removalwarning_title'] = 'The MoodleNet service will be shut down on 20 April 2026.';
|
||||
$string['enablemoodlenet'] = 'Enable MoodleNet integration (inbound)';
|
||||
$string['enablemoodlenet_desc'] = 'If enabled, a user with the capability to create and manage activities can browse MoodleNet via the activity chooser and import MoodleNet resources into their course. In addition, a user with the capability to restore backups can select a backup file on MoodleNet and restore it into Moodle.';
|
||||
$string['errorduringdownload'] = 'An error occurred while downloading the file: {$a}';
|
||||
|
||||
@@ -27,8 +27,16 @@
|
||||
<div class="content text-start mb-5 px-5 py-4" data-region="chooser-option-summary-content-container">
|
||||
<div data-region="moodle-net">
|
||||
<div class="overlay-icon-container z-index-1 d-none" data-region="spinner"></div>
|
||||
<img class="w-25 mb-4" aria-hidden="true" src="{{{img}}}">
|
||||
<img class="w-25 mb-4" alt="" aria-hidden="true" src="{{{img}}}">
|
||||
<p>{{#str}} instancedescription, tool_moodlenet {{/str}}</p>
|
||||
|
||||
{{! Removal warning - always visible when MoodleNet integration is enabled }}
|
||||
<div class="alert alert-warning w-75 mx-auto mb-3" role="alert">
|
||||
<strong>{{#str}}removalwarning_title, tool_moodlenet{{/str}}</strong>
|
||||
<p class="mb-1">{{#str}}removalwarning_service, tool_moodlenet{{/str}}</p>
|
||||
<p class="mb-0">{{#str}}removalwarning_feature, tool_moodlenet{{/str}}</p>
|
||||
</div>
|
||||
|
||||
<p class="w-75 mx-auto mb-1 mt-5">{{#str}} connectandbrowse, tool_moodlenet {{/str}}</p>
|
||||
<a class="btn btn-secondary d-block w-75 mx-auto mb-4"
|
||||
data-action="browse"
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
@tool @tool_uploaduser @_file_upload
|
||||
Feature: Admin can suspend user course enrolment via CSV upload
|
||||
In order to manage enrolments in bulk
|
||||
As an administrator
|
||||
I need to be able to enrol and suspend users using CSV upload
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
| Course 2 | C2 |
|
||||
| Course 3 | C3 |
|
||||
|
||||
@javascript
|
||||
Scenario: Admin uploads enrol and suspend CSVs and verifies enrolment status
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Users > Accounts > Upload users" in site administration
|
||||
When I upload "lib/tests/fixtures/QA_user_enrol.txt" file to "File" filemanager
|
||||
And I press "Upload users"
|
||||
And I press "Upload users"
|
||||
And I press "Continue"
|
||||
And I upload "lib/tests/fixtures/QA_user_suspend.txt" file to "File" filemanager
|
||||
And I press "Upload users"
|
||||
And I set the field "Upload type" to "Update existing users only"
|
||||
And I press "Upload users"
|
||||
And I press "Continue"
|
||||
And I am on the "Course 1" "enrolled users" page
|
||||
Then the following should exist in the "participants" table:
|
||||
| First name | Status |
|
||||
| Learner One | Active |
|
||||
| Learner Two | Active |
|
||||
And I am on the "Course 2" "enrolled users" page
|
||||
And the following should exist in the "participants" table:
|
||||
| First name | Status |
|
||||
| Learner One | Active |
|
||||
| Learner Two | Suspended |
|
||||
And I am on the "Course 3" "enrolled users" page
|
||||
And the following should exist in the "participants" table:
|
||||
| First name | Status |
|
||||
| Learner One | Suspended |
|
||||
| Learner Two | Active |
|
||||
@@ -197,9 +197,7 @@ class api {
|
||||
|
||||
$data->link = $confirmationurl->out(false);
|
||||
$message = get_string('confirmlinkedloginemail', 'auth_oauth2', $data);
|
||||
|
||||
$data->link = $confirmationurl->out();
|
||||
$messagehtml = text_to_html(get_string('confirmlinkedloginemail', 'auth_oauth2', $data), false, false, true);
|
||||
$messagehtml = text_to_html(get_string('confirmlinkedloginemail', 'auth_oauth2', $data), false, false);
|
||||
|
||||
$user->mailformat = 1; // Always send HTML version as well.
|
||||
|
||||
@@ -339,9 +337,7 @@ class api {
|
||||
|
||||
$data->link = $confirmationurl->out(false);
|
||||
$message = get_string('confirmaccountemail', 'auth_oauth2', $data);
|
||||
|
||||
$data->link = $confirmationurl->out();
|
||||
$messagehtml = text_to_html(get_string('confirmaccountemail', 'auth_oauth2', $data), false, false, true);
|
||||
$messagehtml = text_to_html(get_string('confirmaccountemail', 'auth_oauth2', $data), false, false);
|
||||
|
||||
$user->mailformat = 1; // Always send HTML version as well.
|
||||
|
||||
|
||||
@@ -27,19 +27,14 @@ $string['auth_oauth2description'] = 'OAuth 2 standards based authentication';
|
||||
$string['auth_oauth2settings'] = 'OAuth 2 authentication settings.';
|
||||
$string['confirmaccountemail'] = 'Hi {$a->firstname},
|
||||
|
||||
A new account has been requested at \'{$a->sitename}\'
|
||||
using your email address.
|
||||
A new account has been requested at \'{$a->sitename}\' using your email address.
|
||||
|
||||
To confirm your new account, please go to this web address:
|
||||
To confirm your new account, please click the link below:
|
||||
|
||||
{$a->link}
|
||||
<a href="{$a->link}">Confirm your account</a>
|
||||
|
||||
In most mail programs, this should appear as a blue link
|
||||
which you can just click on. If that doesn\'t work,
|
||||
then cut and paste the address into the address
|
||||
line at the top of your web browser window.
|
||||
|
||||
If you need help, please contact the site administrator,
|
||||
If you need help, please contact the site administrator.
|
||||
{$a->admin}
|
||||
|
||||
If you did not do this, someone else could be trying to compromise your account.
|
||||
@@ -53,16 +48,12 @@ A request has been made to link the {$a->issuername} login
|
||||
{$a->linkedemail} to your account at \'{$a->sitename}\'
|
||||
using your email address.
|
||||
|
||||
To confirm this request and link these logins, please go to this web address:
|
||||
To confirm this request and link these logins, please click the link below:
|
||||
|
||||
{$a->link}
|
||||
<a href="{$a->link}">Link your accounts</a>
|
||||
|
||||
In most mail programs, this should appear as a blue link
|
||||
which you can just click on. If that doesn\'t work,
|
||||
then cut and paste the address into the address
|
||||
line at the top of your web browser window.
|
||||
|
||||
If you need help, please contact the site administrator,
|
||||
If you need help, please contact the site administrator.
|
||||
{$a->admin}
|
||||
|
||||
If you did not do this, someone else could be trying to compromise your account.
|
||||
|
||||
@@ -35,10 +35,12 @@ Feature: Test if the login form provides the correct feedback
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Appearance > Logos" in site administration
|
||||
And I upload "course/tests/fixtures/image.jpg" file to "Logo" filemanager
|
||||
And I upload "course/tests/fixtures/image.jpg" file to "Favicon" filemanager
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
And I follow "Log in"
|
||||
Then "//img[@id='logoimage']" "xpath_element" should exist
|
||||
Then "//img[@id='logoimage' and contains(@src, '/image.jpg')]" "xpath_element" should exist
|
||||
And "//link[@rel='shortcut icon' and contains(@href, '/image.jpg')]" "xpath_element" should exist
|
||||
|
||||
Scenario: Add a custom welcome message
|
||||
Given the following config values are set as admin:
|
||||
|
||||
@@ -5285,6 +5285,15 @@ class restore_create_categories_and_questions extends restore_structure_step {
|
||||
$this->set_mapping('question_bank_entry', $this->latestqbe->oldid, $this->latestqbe->newid);
|
||||
}
|
||||
|
||||
if (
|
||||
($data->qtype === 'random')
|
||||
&& ($this->latestversion->status == \core_question\local\bank\question_version_status::QUESTION_STATUS_HIDDEN)
|
||||
) {
|
||||
// Ensure that this newly created question is considered by
|
||||
// \qtype_random\task\remove_unused_questions.
|
||||
$this->latestversion->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_DRAFT;
|
||||
}
|
||||
|
||||
// Now store the question.
|
||||
$newitemid = $DB->insert_record('question', $data);
|
||||
$this->set_mapping('question', $oldid, $newitemid);
|
||||
|
||||
@@ -107,17 +107,12 @@ final class async_backup_test extends \advanced_testcase {
|
||||
$asynctask = new \core\task\asynchronous_backup_task();
|
||||
$asynctask->set_custom_data(['backupid' => $backupid]);
|
||||
$asynctask->set_userid($USER->id);
|
||||
\core\task\manager::queue_adhoc_task($asynctask);
|
||||
|
||||
// We are expecting trace output during this test.
|
||||
$this->expectOutputRegex("/$backupid/");
|
||||
|
||||
// Execute adhoc task.
|
||||
$now = time();
|
||||
$task = \core\task\manager::get_next_adhoc_task($now);
|
||||
$this->assertInstanceOf('\\core\\task\\asynchronous_backup_task', $task);
|
||||
$task->execute();
|
||||
\core\task\manager::adhoc_task_complete($task);
|
||||
$asynctask->execute();
|
||||
|
||||
$postbackuprec = $DB->get_record('backup_controllers', ['backupid' => $backupid]);
|
||||
|
||||
@@ -151,13 +146,9 @@ final class async_backup_test extends \advanced_testcase {
|
||||
// Create the adhoc task.
|
||||
$asynctask = new \core\task\asynchronous_backup_task();
|
||||
$asynctask->set_custom_data(['backupid' => $backupid]);
|
||||
\core\task\manager::queue_adhoc_task($asynctask);
|
||||
|
||||
// Execute adhoc task.
|
||||
$now = time();
|
||||
$task = \core\task\manager::get_next_adhoc_task($now);
|
||||
$task->execute();
|
||||
\core\task\manager::adhoc_task_complete($task);
|
||||
$asynctask->execute();
|
||||
|
||||
$postbackuprec = $DB->get_record('backup_controllers', ['backupid' => $backupid]);
|
||||
|
||||
@@ -241,19 +232,11 @@ final class async_backup_test extends \advanced_testcase {
|
||||
// Now queue an adhoc task and check it handles and completes gracefully.
|
||||
$asynctask = new \core\task\asynchronous_backup_task();
|
||||
$asynctask->set_custom_data(array('backupid' => $backupid));
|
||||
\core\task\manager::queue_adhoc_task($asynctask);
|
||||
|
||||
// We are expecting a specific message output during this test.
|
||||
$this->expectOutputRegex('/invalid controller/');
|
||||
|
||||
// Execute adhoc task.
|
||||
$now = time();
|
||||
$task = \core\task\manager::get_next_adhoc_task($now);
|
||||
$this->assertInstanceOf('\\core\\task\\asynchronous_backup_task', $task);
|
||||
$task->execute();
|
||||
\core\task\manager::adhoc_task_complete($task);
|
||||
|
||||
// Check the task record is removed.
|
||||
$this->assertEquals(0, $DB->count_records('task_adhoc'));
|
||||
$asynctask->execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,20 +223,12 @@ final class async_restore_test extends \advanced_testcase {
|
||||
// Create the adhoc task.
|
||||
$asynctask = new \core\task\asynchronous_restore_task();
|
||||
$asynctask->set_custom_data(['backupid' => $restoreid]);
|
||||
\core\task\manager::queue_adhoc_task($asynctask);
|
||||
|
||||
// We are expecting a specific message output during this test.
|
||||
$this->expectOutputRegex('/invalid controller/');
|
||||
|
||||
// Execute adhoc task.
|
||||
$now = time();
|
||||
$task = \core\task\manager::get_next_adhoc_task($now);
|
||||
$this->assertInstanceOf('\\core\\task\\asynchronous_restore_task', $task);
|
||||
$task->execute();
|
||||
\core\task\manager::adhoc_task_complete($task);
|
||||
|
||||
// Check the task record is removed.
|
||||
$this->assertEquals(0, $DB->count_records('task_adhoc'));
|
||||
$asynctask->execute();
|
||||
|
||||
// Now delete the record and confirm an entirely missing controller is handled.
|
||||
$DB->delete_records('backup_controllers');
|
||||
@@ -244,19 +236,11 @@ final class async_restore_test extends \advanced_testcase {
|
||||
// Create the adhoc task.
|
||||
$asynctask = new \core\task\asynchronous_restore_task();
|
||||
$asynctask->set_custom_data(['backupid' => $restoreid]);
|
||||
\core\task\manager::queue_adhoc_task($asynctask);
|
||||
|
||||
// We are expecting a specific message output during this test.
|
||||
$this->expectOutputRegex('/Unable to find restore controller/');
|
||||
|
||||
// Execute adhoc task.
|
||||
$now = time();
|
||||
$task = \core\task\manager::get_next_adhoc_task($now);
|
||||
$this->assertInstanceOf('\\core\\task\\asynchronous_restore_task', $task);
|
||||
$task->execute();
|
||||
\core\task\manager::adhoc_task_complete($task);
|
||||
|
||||
// Check the task record is removed.
|
||||
$this->assertEquals(0, $DB->count_records('task_adhoc'));
|
||||
$asynctask->execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
{}
|
||||
}}
|
||||
|
||||
<div id="block-myoverview-{{uniqid}}" class="block-myoverview block-cards" data-region="myoverview" role="navigation">
|
||||
<div id="block-myoverview-{{uniqid}}" class="block-myoverview block-cards" data-region="myoverview">
|
||||
<div role="search" data-region="filter" class="d-flex align-items-center my-2" aria-label="{{#str}} aria:controls, block_myoverview {{/str}}">
|
||||
<div class="row g-0">
|
||||
{{> block_myoverview/nav-grouping-selector }}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
}
|
||||
}}
|
||||
|
||||
<div class="block-myoverview block-cards" data-region="myoverview" role="navigation">
|
||||
<div class="block-myoverview block-cards" data-region="myoverview">
|
||||
<hr class="mt-0"/>
|
||||
|
||||
<div class="container-fluid p-0">
|
||||
@@ -53,7 +53,7 @@
|
||||
src="{{nocoursesimg}}"
|
||||
alt=""
|
||||
>
|
||||
<h5 class="h5 mt-3 mb-0">{{{ title }}}</h5>
|
||||
<h4 class="h4 mt-3 mb-0">{{{ title }}}</h4>
|
||||
<p class="mt-3 mb-0">{{{ intro }}}</p>
|
||||
|
||||
<div class="mt-5 mb-0 whitebutton" id="action_bar">
|
||||
|
||||
@@ -314,6 +314,6 @@ Feature: The my overview block allows users to easily access their courses
|
||||
Then I should not see "Category 1" in the "Course overview" "block"
|
||||
|
||||
@accessibility
|
||||
Scenario: The dashboard page must have sufficient colour contrast
|
||||
Scenario: The My courses page must meet accessibility standards
|
||||
When I am on the "My courses" page logged in as "student1"
|
||||
Then the page should meet "wcag143" accessibility standards
|
||||
Then the page should meet accessibility standards with "best-practice" extra tests
|
||||
|
||||
@@ -88,4 +88,4 @@ Feature: Zero state on my overview block
|
||||
@javascript @accessibility
|
||||
Scenario: Evaluate the accessibility of the My courses (zero state)
|
||||
When I am on the "My courses" page logged in as "manager"
|
||||
Then the page should meet accessibility standards
|
||||
Then the page should meet accessibility standards with "best-practice" extra tests
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<div class="pb-2" data-region="event-list-wrapper">
|
||||
{{#eventsbyday}}
|
||||
<div class="mt-3" data-region="event-list-content-date" data-timestamp="{{dayTimestamp}}">
|
||||
<h5 class="h6 d-inline {{^courseview}}font-weight-bold px-2{{/courseview}}">{{#userdate}} {{dayTimestamp}}, {{#str}} strftimedaydate, core_langconfig {{/str}} {{/userdate}}</h5>
|
||||
<h4 class="h6 d-inline {{^courseview}}font-weight-bold px-2{{/courseview}}">{{#userdate}} {{dayTimestamp}}, {{#str}} strftimedaydate, core_langconfig {{/str}} {{/userdate}}</h4>
|
||||
</div>
|
||||
{{> block_timeline/event-list-items }}
|
||||
{{/eventsbyday}}
|
||||
|
||||
@@ -66,13 +66,13 @@
|
||||
</div>
|
||||
<div class="event-name-container flex-grow-1 line-height-3 nowrap text-truncate">
|
||||
<div class="d-flex">
|
||||
<h6 class="event-name mb-0 pb-1 text-truncate">
|
||||
<h5 class="h6 event-name mb-0 pb-1 text-truncate">
|
||||
{{#overdue}}<span class="badge rounded-pill bg-danger text-white ms-1 float-end">{{#str}} overdue, block_timeline {{/str}}</span>{{/overdue}}
|
||||
<a href="{{url}}"
|
||||
title="{{name}}"
|
||||
aria-label='{{#cleanstr}} ariaeventlistitem, block_timeline, { "name": {{#quote}}{{{activityname}}}{{/quote}}, "course": {{#quote}}{{{course.fullnamedisplay}}}{{/quote}}, "date": "{{#userdate}} {{timesort}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}" } {{/cleanstr}}'>
|
||||
{{{activityname}}}</a>
|
||||
</h6>
|
||||
</h5>
|
||||
</div>
|
||||
<small class="mb-0">
|
||||
{{#courseview}}
|
||||
@@ -86,7 +86,7 @@
|
||||
</div>
|
||||
{{#action.actionable}}
|
||||
<div class="d-flex timeline-action-button">
|
||||
<h6 class="event-action">
|
||||
<h5 class="h6 event-action">
|
||||
<a class="list-group-item-action btn btn-outline-secondary btn-sm text-nowrap"
|
||||
href="{{action.url}}"
|
||||
aria-label="{{action.name}}"
|
||||
@@ -96,7 +96,7 @@
|
||||
<span class="badge bg-secondary text-dark">{{action.itemcount}}</span>
|
||||
{{/action.showitemcount}}
|
||||
</a>
|
||||
</h6>
|
||||
</h5>
|
||||
</div>
|
||||
{{/action.actionable}}
|
||||
</div>
|
||||
|
||||
@@ -60,54 +60,56 @@
|
||||
{{#str}} overdue, block_timeline {{/str}}
|
||||
</a>
|
||||
<div class="dropdown-divider" role="separator"></div>
|
||||
<h6 class="dropdown-header">{{#str}} duedate, block_timeline {{/str}}</h6>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
data-from="0"
|
||||
data-to="7"
|
||||
data-filtername="next7days"
|
||||
{{#next7days}}aria-current="true"{{/next7days}}
|
||||
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next7days, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
>
|
||||
{{#str}} next7days, block_timeline {{/str}}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
data-from="0"
|
||||
data-to="30"
|
||||
data-filtername="next30days"
|
||||
{{#next30days}}aria-current="true"{{/next30days}}
|
||||
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next30days, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
>
|
||||
{{#str}} next30days, block_timeline {{/str}}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
data-from="0"
|
||||
data-to="90"
|
||||
data-filtername="next3months"
|
||||
{{#next3months}}aria-current="true"{{/next3months}}
|
||||
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next3months, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
>
|
||||
{{#str}} next3months, block_timeline {{/str}}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
data-from="0"
|
||||
data-to="180"
|
||||
data-filtername="next6months"
|
||||
{{#next6months}}aria-current="true"{{/next6months}}
|
||||
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next6months, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
>
|
||||
{{#str}} next6months, block_timeline {{/str}}
|
||||
</a>
|
||||
<div role="group" aria-labelledby="duedatefiltergrouplabel">
|
||||
<div class="h6 dropdown-header" role="presentation" id="duedatefiltergrouplabel">{{#str}} duedate, block_timeline {{/str}}</div>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
data-from="0"
|
||||
data-to="7"
|
||||
data-filtername="next7days"
|
||||
{{#next7days}}aria-current="true"{{/next7days}}
|
||||
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next7days, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
>
|
||||
{{#str}} next7days, block_timeline {{/str}}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
data-from="0"
|
||||
data-to="30"
|
||||
data-filtername="next30days"
|
||||
{{#next30days}}aria-current="true"{{/next30days}}
|
||||
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next30days, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
>
|
||||
{{#str}} next30days, block_timeline {{/str}}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
data-from="0"
|
||||
data-to="90"
|
||||
data-filtername="next3months"
|
||||
{{#next3months}}aria-current="true"{{/next3months}}
|
||||
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next3months, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
>
|
||||
{{#str}} next3months, block_timeline {{/str}}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
href="#"
|
||||
data-from="0"
|
||||
data-to="180"
|
||||
data-filtername="next6months"
|
||||
{{#next6months}}aria-current="true"{{/next6months}}
|
||||
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next6months, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
>
|
||||
{{#str}} next6months, block_timeline {{/str}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -37,8 +37,10 @@ Feature: The timeline block allows users to see upcoming courses
|
||||
| student1 | C3 | student |
|
||||
| student1 | C4 | student |
|
||||
|
||||
@accessibility
|
||||
Scenario: Next 30 days in course view
|
||||
Given I log in as "student1"
|
||||
And the "Timeline" "block" should meet accessibility standards with "best-practice" extra tests
|
||||
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
|
||||
And I click on "Next 30 days" "link" in the "Timeline" "block"
|
||||
And I click on "Sort timeline items" "button" in the "Timeline" "block"
|
||||
@@ -61,6 +63,7 @@ Feature: The timeline block allows users to see upcoming courses
|
||||
And "Test feedback 2" "link" should not exist in the "Timeline" "block"
|
||||
And "Test feedback 3" "link" should not exist in the "Timeline" "block"
|
||||
And "Test assign 1" "link" should not exist in the "Timeline" "block"
|
||||
And the "Timeline" "block" should meet accessibility standards with "best-practice" extra tests
|
||||
|
||||
Scenario: All in course view
|
||||
Given I log in as "student1"
|
||||
|
||||
@@ -50,9 +50,11 @@ Feature: The timeline block allows users to see upcoming activities
|
||||
And "Test assign 1" "link" should not exist in the "Timeline" "block"
|
||||
And I should not see "Assignment is due · Course 1" in the "Timeline" "block"
|
||||
|
||||
@accessibility
|
||||
Scenario: Overdue in date view
|
||||
Given I log in as "student1"
|
||||
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
|
||||
And the "Timeline" "block" should meet accessibility standards with "best-practice" extra tests
|
||||
When I click on "Overdue" "link" in the "Timeline" "block"
|
||||
Then "Test assign 1" "link" should exist in the "Timeline" "block"
|
||||
And I should see "Assignment is due · Course 1" in the "Timeline" "block"
|
||||
@@ -63,6 +65,7 @@ Feature: The timeline block allows users to see upcoming activities
|
||||
And "Test choice 3" "link" should not exist in the "Timeline" "block"
|
||||
And "Test feedback 3" "link" should not exist in the "Timeline" "block"
|
||||
And "Test feedback 4" "link" should not exist in the "Timeline" "block"
|
||||
And the "Timeline" "block" should meet accessibility standards with "best-practice" extra tests
|
||||
|
||||
Scenario: All in date view
|
||||
Given I log in as "student1"
|
||||
|
||||
Generated
+6
-6
@@ -4037,16 +4037,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v6.4.4",
|
||||
"version": "v6.4.33",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "710e27879e9be3395de2b98da3f52a946039f297"
|
||||
"reference": "596cc67dc84d1718679240e0805dd50dbbd33992"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/710e27879e9be3395de2b98da3f52a946039f297",
|
||||
"reference": "710e27879e9be3395de2b98da3f52a946039f297",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/596cc67dc84d1718679240e0805dd50dbbd33992",
|
||||
"reference": "596cc67dc84d1718679240e0805dd50dbbd33992",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4078,7 +4078,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v6.4.4"
|
||||
"source": "https://github.com/symfony/process/tree/v6.4.33"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4094,7 +4094,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-02-20T12:31:00+00:00"
|
||||
"time": "2026-01-28T10:02:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -22,10 +22,11 @@
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import CustomEvents from 'core/custom_interaction_events';
|
||||
import * as ModalEvents from 'core/modal_events';
|
||||
import selectors from 'core_course/local/activitychooser/selectors';
|
||||
import * as Templates from 'core/templates';
|
||||
import {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';
|
||||
import {enter, space} from 'core/key_codes';
|
||||
import {addIconToContainer} from 'core/loadingicon';
|
||||
import * as Repository from 'core_course/local/activitychooser/repository';
|
||||
import Notification from 'core/notification';
|
||||
@@ -246,7 +247,54 @@ const initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOption
|
||||
const chooserOptions = chooserOptionsContainer.querySelectorAll(selectors.regions.chooserOption.container);
|
||||
|
||||
Array.from(chooserOptions).forEach((element) => {
|
||||
return element.addEventListener('keydown', (e) => {
|
||||
const $element = $(element);
|
||||
|
||||
// Set up custom interaction events for RTL-aware keyboard navigation.
|
||||
CustomEvents.define($element, [
|
||||
CustomEvents.events.next,
|
||||
CustomEvents.events.previous,
|
||||
CustomEvents.events.home,
|
||||
CustomEvents.events.end,
|
||||
]);
|
||||
|
||||
// Handle focus move (automatically handles RTL).
|
||||
const createNavHandler = (resolver) => (e, data) => {
|
||||
const currentOption = data.originalEvent.target.closest(
|
||||
selectors.regions.chooserOption.container
|
||||
);
|
||||
if (currentOption !== null) {
|
||||
const toFocusOption = resolver(currentOption);
|
||||
if (toFocusOption) {
|
||||
focusChooserOption(toFocusOption, currentOption);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$element.on(
|
||||
CustomEvents.events.next,
|
||||
createNavHandler(
|
||||
(current) => current.nextElementSibling || chooserOptionsContainer.firstElementChild
|
||||
)
|
||||
);
|
||||
|
||||
$element.on(
|
||||
CustomEvents.events.previous,
|
||||
createNavHandler(
|
||||
(current) => current.previousElementSibling || chooserOptionsContainer.lastElementChild
|
||||
)
|
||||
);
|
||||
|
||||
$element.on(
|
||||
CustomEvents.events.home,
|
||||
createNavHandler(() => chooserOptionsContainer.firstElementChild)
|
||||
);
|
||||
|
||||
$element.on(
|
||||
CustomEvents.events.end,
|
||||
createNavHandler(() => chooserOptionsContainer.lastElementChild)
|
||||
);
|
||||
|
||||
element.addEventListener('keydown', (e) => {
|
||||
|
||||
// Check for enter/ space triggers for showing the help.
|
||||
if (e.keyCode === enter || e.keyCode === space) {
|
||||
@@ -267,40 +315,6 @@ const initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOption
|
||||
showModuleHelp(carousel, moduleData, modal);
|
||||
}
|
||||
}
|
||||
|
||||
// Next.
|
||||
if (e.keyCode === arrowRight) {
|
||||
e.preventDefault();
|
||||
const currentOption = e.target.closest(selectors.regions.chooserOption.container);
|
||||
const nextOption = currentOption.nextElementSibling;
|
||||
const firstOption = chooserOptionsContainer.firstElementChild;
|
||||
const toFocusOption = clickErrorHandler(nextOption, firstOption);
|
||||
focusChooserOption(toFocusOption, currentOption);
|
||||
}
|
||||
|
||||
// Previous.
|
||||
if (e.keyCode === arrowLeft) {
|
||||
e.preventDefault();
|
||||
const currentOption = e.target.closest(selectors.regions.chooserOption.container);
|
||||
const previousOption = currentOption.previousElementSibling;
|
||||
const lastOption = chooserOptionsContainer.lastElementChild;
|
||||
const toFocusOption = clickErrorHandler(previousOption, lastOption);
|
||||
focusChooserOption(toFocusOption, currentOption);
|
||||
}
|
||||
|
||||
if (e.keyCode === home) {
|
||||
e.preventDefault();
|
||||
const currentOption = e.target.closest(selectors.regions.chooserOption.container);
|
||||
const firstOption = chooserOptionsContainer.firstElementChild;
|
||||
focusChooserOption(firstOption, currentOption);
|
||||
}
|
||||
|
||||
if (e.keyCode === end) {
|
||||
e.preventDefault();
|
||||
const currentOption = e.target.closest(selectors.regions.chooserOption.container);
|
||||
const lastOption = chooserOptionsContainer.lastElementChild;
|
||||
focusChooserOption(lastOption, currentOption);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -348,22 +362,6 @@ const toggleFocusableChooserOption = (chooserOption, isFocusable) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Small error handling function to make sure the navigated to object exists
|
||||
*
|
||||
* @method clickErrorHandler
|
||||
* @param {HTMLElement} item What we want to check exists
|
||||
* @param {HTMLElement} fallback If we dont match anything fallback the focus
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
const clickErrorHandler = (item, fallback) => {
|
||||
if (item !== null) {
|
||||
return item;
|
||||
} else {
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the search results in a defined container
|
||||
*
|
||||
|
||||
@@ -3274,6 +3274,7 @@ class core_course_category implements renderable, cacheable_object, IteratorAggr
|
||||
ON uc.roleid = rc.roleid
|
||||
AND ( $eqpaths OR $likechild )
|
||||
WHERE (ra.id IS NOT NULL OR uc.upath IS NOT NULL)
|
||||
ORDER BY cc.sortorder, cc.id
|
||||
";
|
||||
|
||||
$params = [
|
||||
|
||||
@@ -74,6 +74,7 @@ class activity_navigation implements renderable, templatable {
|
||||
$attributes = [
|
||||
'class' => 'btn btn-link',
|
||||
'id' => 'prev-activity-link',
|
||||
'aria-label' => get_string('activitynavigation:preva', 'course', $linkname),
|
||||
];
|
||||
$this->prevlink = new \action_link($linkurl, $OUTPUT->larrow() . ' ' . $linkname, null, $attributes);
|
||||
}
|
||||
@@ -89,15 +90,16 @@ class activity_navigation implements renderable, templatable {
|
||||
$attributes = [
|
||||
'class' => 'btn btn-link',
|
||||
'id' => 'next-activity-link',
|
||||
'aria-label' => get_string('activitynavigation:nexta', 'course', $linkname),
|
||||
];
|
||||
$this->nextlink = new \action_link($linkurl, $linkname . ' ' . $OUTPUT->rarrow(), null, $attributes);
|
||||
}
|
||||
|
||||
// Render the activity list dropdown menu if available.
|
||||
if (!empty($activitylist)) {
|
||||
$select = new url_select($activitylist, '', array('' => get_string('jumpto')));
|
||||
$select->set_label(get_string('jumpto'), array('class' => 'sr-only'));
|
||||
$select->attributes = array('id' => 'jump-to-activity');
|
||||
$select = new url_select($activitylist, '', ['' => get_string('activitynavigation:jumptoactivity', 'course')]);
|
||||
$select->set_label(get_string('activitynavigation:jumptoactivity', 'course'), ['class' => 'sr-only']);
|
||||
$select->attributes = ['id' => 'jump-to-activity'];
|
||||
$this->activitylist = $select;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,23 +39,23 @@ Feature: Activity navigation in a single activity course
|
||||
Scenario: Jump to a hidden activity as a teacher
|
||||
Given I log in as "teacher1"
|
||||
When I am on "Course 1" course homepage
|
||||
Then "Jump to..." "field" should exist
|
||||
Then "Jump to activity" "field" should exist
|
||||
# The current activity (Forum 1) will not be listed.
|
||||
And the "Jump to..." select box should not contain "Forum 1"
|
||||
And the "Jump to activity" select box should not contain "Forum 1"
|
||||
# Check drop down menu contents.
|
||||
And the "Jump to..." select box should contain "Assignment 1 (hidden)"
|
||||
And the "Jump to..." select box should contain "Lesson 1 (hidden)"
|
||||
And the "Jump to activity" select box should contain "Assignment 1 (hidden)"
|
||||
And the "Jump to activity" select box should contain "Lesson 1 (hidden)"
|
||||
# Jump to a hidden activity somewhere in the middle.
|
||||
When I select "Assignment 1 (hidden)" from the "Jump to..." singleselect
|
||||
When I select "Assignment 1 (hidden)" from the "Jump to activity" singleselect
|
||||
Then I should see "Assignment 1"
|
||||
And I should see "Forum 1" in the "#prev-activity-link" "css_element"
|
||||
And I should see "Lesson 1 (hidden)" in the "#next-activity-link" "css_element"
|
||||
# Jump to the first activity.
|
||||
And I select "Forum 1" from the "Jump to..." singleselect
|
||||
And I select "Forum 1" from the "Jump to activity" singleselect
|
||||
And I should see "Assignment 1 (hidden)" in the "#next-activity-link" "css_element"
|
||||
But "#prev-activity-link" "css_element" should not exist
|
||||
# Jump to the last activity.
|
||||
And I select "Lesson 1 (hidden)" from the "Jump to..." singleselect
|
||||
And I select "Lesson 1 (hidden)" from the "Jump to activity" singleselect
|
||||
And I should see "Assignment 1 (hidden)" in the "#prev-activity-link" "css_element"
|
||||
But "#next-activity-link" "css_element" should not exist
|
||||
|
||||
@@ -65,7 +65,7 @@ Feature: Activity navigation in a single activity course
|
||||
# The first activity won't have the previous activity link.
|
||||
Then "#prev-activity-link" "css_element" should not exist
|
||||
And "#next-activity-link" "css_element" should not exist
|
||||
And "Jump to..." "field" should not exist
|
||||
And "Jump to activity" "field" should not exist
|
||||
|
||||
Scenario: The activity navigation asks for login to guest user
|
||||
Given I log in as "guest"
|
||||
|
||||
@@ -73,7 +73,8 @@
|
||||
class="collapse-list-link icons-collapse-expand collapsed"
|
||||
>
|
||||
<span class="collapsed-icon icon-no-margin me-1" data-toggle="tooltip" title="{{#str}} expand, core {{/str}}">
|
||||
{{#pix}} t/collapsedchevron, core {{/pix}}
|
||||
<span class="dir-rtl-hide">{{#pix}} t/collapsedchevron, core {{/pix}}</span>
|
||||
<span class="dir-ltr-hide">{{#pix}} t/collapsedchevron_rtl, core {{/pix}}</span>
|
||||
<span class="sr-only">{{#str}} expand, core {{/str}}</span>
|
||||
</span>
|
||||
<span class="expanded-icon icon-no-margin me-1" data-toggle="tooltip" title="{{#str}} collapse, core {{/str}}">
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
"id": "url_select_test",
|
||||
"action": "#",
|
||||
"options": [
|
||||
{"name": "Jump to...", "value": "#0"},
|
||||
{"name": "Jump to activity", "value": "#0"},
|
||||
{"name": "Activity A", "value": "#1"},
|
||||
{"name": "Activity B", "value": "#2"},
|
||||
{"name": "Activity C", "value": "#3"}
|
||||
@@ -64,7 +64,7 @@
|
||||
}
|
||||
}
|
||||
}}
|
||||
<div class="mt-5 mb-1 activity-navigation container-fluid">
|
||||
<nav class="mt-5 mb-1 activity-navigation container-fluid" aria-label="{{#str}}activitynavigation, course{{/str}}">
|
||||
{{< core/columns-1to1to1}}
|
||||
{{$column1}}
|
||||
<div class="float-start">
|
||||
@@ -82,4 +82,4 @@
|
||||
</div>
|
||||
{{/column3}}
|
||||
{{/ core/columns-1to1to1}}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -38,7 +38,7 @@ Feature: Display and choose from the available activities in course
|
||||
Scenario: The teacher can choose to add an activity from the activity summary in the activity chooser
|
||||
Given I click on "Add an activity or resource" "button" in the "New section" "section"
|
||||
When I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue"
|
||||
When I click on "Add a new Assignment" "link" in the "help" "core_course > Activity chooser screen"
|
||||
And I click on "Add a new Assignment" "link" in the ".modal-footer" "css_element"
|
||||
Then I should see "New Assignment"
|
||||
|
||||
Scenario: Show summary
|
||||
@@ -57,8 +57,8 @@ Feature: Display and choose from the available activities in course
|
||||
Given I click on "Add an activity or resource" "button" in the "New section" "section"
|
||||
When I click on "Information about the Assignment activity" "button" in the "modules" "core_course > Activity chooser screen"
|
||||
And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "help" "core_course > Activity chooser screen"
|
||||
And I should see "Back" in the "help" "core_course > Activity chooser screen"
|
||||
When I click on "Back" "button" in the "help" "core_course > Activity chooser screen"
|
||||
And I should see "Back" in the ".modal-footer" "css_element"
|
||||
When I click on "Back" "button" in the ".modal-footer" "css_element"
|
||||
Then "modules" "core_course > Activity chooser screen" should be visible
|
||||
And "help" "core_course > Activity chooser screen" should not be visible
|
||||
And "Back" "button" should not exist in the "modules" "core_course > Activity chooser screen"
|
||||
@@ -67,7 +67,7 @@ Feature: Display and choose from the available activities in course
|
||||
And I am on "C2" course homepage with editing mode on
|
||||
And I click on "Add an activity or resource" "button" in the "13 January - 19 January" "section"
|
||||
And I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue"
|
||||
And I click on "Back" "button" in the "help" "core_course > Activity chooser screen"
|
||||
And I click on "Back" "button" in the ".modal-footer" "css_element"
|
||||
And "modules" "core_course > Activity chooser screen" should be visible
|
||||
And "help" "core_course > Activity chooser screen" should not be visible
|
||||
And "Back" "button" should not exist in the "modules" "core_course > Activity chooser screen"
|
||||
|
||||
@@ -191,49 +191,49 @@ Feature: Activity navigation
|
||||
Given I log in as "teacher1"
|
||||
When I am on "Course 1" course homepage
|
||||
And I follow "Assignment 1"
|
||||
Then "Jump to..." "field" should exist
|
||||
Then "Jump to activity" "field" should exist
|
||||
# The current activity will not be listed.
|
||||
And the "Jump to..." select box should not contain "Assignment 1"
|
||||
And the "Jump to activity" select box should not contain "Assignment 1"
|
||||
# Stealth activities will not be listed.
|
||||
And the "Jump to..." select box should not contain "Forum 1"
|
||||
And the "Jump to activity" select box should not contain "Forum 1"
|
||||
# Resources without view URL (e.g. labels) will not be listed.
|
||||
And the "Jump to..." select box should not contain "Label 1"
|
||||
And the "Jump to activity" select box should not contain "Label 1"
|
||||
# Check drop down menu contents.
|
||||
And the "Jump to..." select box should contain "Book 1"
|
||||
And the "Jump to..." select box should contain "Chat 1"
|
||||
And the "Jump to..." select box should contain "Choice 1"
|
||||
And the "Jump to..." select box should contain "Database 1"
|
||||
And the "Jump to..." select box should contain "Feedback 1"
|
||||
And the "Jump to..." select box should contain "Folder 1"
|
||||
And the "Jump to..." select box should contain "Imscp 1"
|
||||
And the "Jump to..." select box should contain "Lesson 1"
|
||||
And the "Jump to..." select box should contain "Lti 1"
|
||||
And the "Jump to..." select box should contain "Page 1"
|
||||
And the "Jump to..." select box should contain "Quiz 1"
|
||||
And the "Jump to..." select box should contain "Url 1"
|
||||
And the "Jump to..." select box should contain "Wiki 1"
|
||||
And the "Jump to..." select box should contain "Workshop 1"
|
||||
And the "Jump to activity" select box should contain "Book 1"
|
||||
And the "Jump to activity" select box should contain "Chat 1"
|
||||
And the "Jump to activity" select box should contain "Choice 1"
|
||||
And the "Jump to activity" select box should contain "Database 1"
|
||||
And the "Jump to activity" select box should contain "Feedback 1"
|
||||
And the "Jump to activity" select box should contain "Folder 1"
|
||||
And the "Jump to activity" select box should contain "Imscp 1"
|
||||
And the "Jump to activity" select box should contain "Lesson 1"
|
||||
And the "Jump to activity" select box should contain "Lti 1"
|
||||
And the "Jump to activity" select box should contain "Page 1"
|
||||
And the "Jump to activity" select box should contain "Quiz 1"
|
||||
And the "Jump to activity" select box should contain "Url 1"
|
||||
And the "Jump to activity" select box should contain "Wiki 1"
|
||||
And the "Jump to activity" select box should contain "Workshop 1"
|
||||
# Hidden activities will be rendered with a '(hidden)' text.
|
||||
And the "Jump to..." select box should contain "Glossary 1 (hidden)"
|
||||
And the "Jump to activity" select box should contain "Glossary 1 (hidden)"
|
||||
# Activities in hidden sections will be rendered with a '(hidden)' text.
|
||||
And the "Jump to..." select box should contain "Resource 1 (hidden)"
|
||||
And the "Jump to..." select box should contain "Scorm 1 (hidden)"
|
||||
And the "Jump to..." select box should contain "Survey 1 (hidden)"
|
||||
And the "Jump to activity" select box should contain "Resource 1 (hidden)"
|
||||
And the "Jump to activity" select box should contain "Scorm 1 (hidden)"
|
||||
And the "Jump to activity" select box should contain "Survey 1 (hidden)"
|
||||
# Jump to an activity somewhere in the middle.
|
||||
When I select "Page 1" from the "Jump to..." singleselect
|
||||
When I select "Page 1" from the "Jump to activity" singleselect
|
||||
Then I should see "Page 1"
|
||||
And I should see "Lti 1" in the "#prev-activity-link" "css_element"
|
||||
And I should see "Quiz 1" in the "#next-activity-link" "css_element"
|
||||
# Jump to the first activity.
|
||||
And I select "Assignment 1" from the "Jump to..." singleselect
|
||||
And I select "Assignment 1" from the "Jump to activity" singleselect
|
||||
And I should see "Book 1" in the "#next-activity-link" "css_element"
|
||||
But "#prev-activity-link" "css_element" should not exist
|
||||
# Jump to the last activity.
|
||||
And I select "Workshop 1" from the "Jump to..." singleselect
|
||||
And I select "Workshop 1" from the "Jump to activity" singleselect
|
||||
And I should see "Wiki 1" in the "#prev-activity-link" "css_element"
|
||||
But "#next-activity-link" "css_element" should not exist
|
||||
# Jump to a hidden activity.
|
||||
And I select "Glossary 1" from the "Jump to..." singleselect
|
||||
And I select "Glossary 1" from the "Jump to activity" singleselect
|
||||
And I should see "Folder 1" in the "#prev-activity-link" "css_element"
|
||||
And I should see "Imscp 1" in the "#next-activity-link" "css_element"
|
||||
|
||||
@@ -241,45 +241,45 @@ Feature: Activity navigation
|
||||
Given I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Assignment 1"
|
||||
And "Jump to..." "field" should exist
|
||||
And "Jump to activity" "field" should exist
|
||||
# The current activity will not be listed.
|
||||
And the "Jump to..." select box should not contain "Assignment 1"
|
||||
And the "Jump to activity" select box should not contain "Assignment 1"
|
||||
# Stealth activities will not be listed for students.
|
||||
And the "Jump to..." select box should not contain "Forum 1"
|
||||
And the "Jump to activity" select box should not contain "Forum 1"
|
||||
# Resources without view URL (e.g. labels) will not be listed.
|
||||
And the "Jump to..." select box should not contain "Label 1"
|
||||
And the "Jump to activity" select box should not contain "Label 1"
|
||||
# Hidden activities will not be listed for students.
|
||||
And the "Jump to..." select box should not contain "Glossary 1"
|
||||
And the "Jump to activity" select box should not contain "Glossary 1"
|
||||
# Activities in hidden sections will not be listed for students.
|
||||
And the "Jump to..." select box should not contain "Resource 1"
|
||||
And the "Jump to..." select box should not contain "Scorm 1"
|
||||
And the "Jump to..." select box should not contain "Survey 1"
|
||||
And the "Jump to activity" select box should not contain "Resource 1"
|
||||
And the "Jump to activity" select box should not contain "Scorm 1"
|
||||
And the "Jump to activity" select box should not contain "Survey 1"
|
||||
# Only activities visible to students will be listed.
|
||||
And the "Jump to..." select box should contain "Book 1"
|
||||
And the "Jump to..." select box should contain "Chat 1"
|
||||
And the "Jump to..." select box should contain "Choice 1"
|
||||
And the "Jump to..." select box should contain "Database 1"
|
||||
And the "Jump to..." select box should contain "Feedback 1"
|
||||
And the "Jump to..." select box should contain "Folder 1"
|
||||
And the "Jump to..." select box should contain "Imscp 1"
|
||||
And the "Jump to..." select box should contain "Lesson 1"
|
||||
And the "Jump to..." select box should contain "Lti 1"
|
||||
And the "Jump to..." select box should contain "Page 1"
|
||||
And the "Jump to..." select box should contain "Quiz 1"
|
||||
And the "Jump to..." select box should contain "Url 1"
|
||||
And the "Jump to..." select box should contain "Wiki 1"
|
||||
And the "Jump to..." select box should contain "Workshop 1"
|
||||
And the "Jump to activity" select box should contain "Book 1"
|
||||
And the "Jump to activity" select box should contain "Chat 1"
|
||||
And the "Jump to activity" select box should contain "Choice 1"
|
||||
And the "Jump to activity" select box should contain "Database 1"
|
||||
And the "Jump to activity" select box should contain "Feedback 1"
|
||||
And the "Jump to activity" select box should contain "Folder 1"
|
||||
And the "Jump to activity" select box should contain "Imscp 1"
|
||||
And the "Jump to activity" select box should contain "Lesson 1"
|
||||
And the "Jump to activity" select box should contain "Lti 1"
|
||||
And the "Jump to activity" select box should contain "Page 1"
|
||||
And the "Jump to activity" select box should contain "Quiz 1"
|
||||
And the "Jump to activity" select box should contain "Url 1"
|
||||
And the "Jump to activity" select box should contain "Wiki 1"
|
||||
And the "Jump to activity" select box should contain "Workshop 1"
|
||||
# Jump to an activity somewhere in the middle.
|
||||
When I select "Page 1" from the "Jump to..." singleselect
|
||||
When I select "Page 1" from the "Jump to activity" singleselect
|
||||
Then I should see "Page 1"
|
||||
And I should see "Lti 1" in the "#prev-activity-link" "css_element"
|
||||
And I should see "Quiz 1" in the "#next-activity-link" "css_element"
|
||||
# Jump to the first activity.
|
||||
And I select "Assignment 1" from the "Jump to..." singleselect
|
||||
And I select "Assignment 1" from the "Jump to activity" singleselect
|
||||
And I should see "Book 1" in the "#next-activity-link" "css_element"
|
||||
But "#prev-activity-link" "css_element" should not exist
|
||||
# Jump to the last activity.
|
||||
And I select "Workshop 1" from the "Jump to..." singleselect
|
||||
And I select "Workshop 1" from the "Jump to activity" singleselect
|
||||
And I should see "Wiki 1" in the "#prev-activity-link" "css_element"
|
||||
But "#next-activity-link" "css_element" should not exist
|
||||
|
||||
@@ -289,4 +289,4 @@ Feature: Activity navigation
|
||||
And I follow "Assignment 1"
|
||||
Then "#prev-activity-link" "css_element" should not exist
|
||||
And "#next-activity-link" "css_element" should not exist
|
||||
And "Jump to..." "field" should not exist
|
||||
And "Jump to activity" "field" should not exist
|
||||
|
||||
@@ -48,13 +48,13 @@ Feature: Activity navigation involving activities with access restrictions
|
||||
Given I am on the "Page 1" "page activity" page logged in as student1
|
||||
Then I should see "Page 2" in the "#next-activity-link" "css_element"
|
||||
# Activity that has access restriction should not show up in the dropdown.
|
||||
And the "Jump to..." select box should not contain "Page 3"
|
||||
And I select "Page 4" from the "Jump to..." singleselect
|
||||
And the "Jump to activity" select box should not contain "Page 3"
|
||||
And I select "Page 4" from the "Jump to activity" singleselect
|
||||
# Page 2 should be shown in the previous link since Page 3 is not yet available.
|
||||
And I should see "Page 2" in the "#prev-activity-link" "css_element"
|
||||
And the "Jump to..." select box should not contain "Page 3"
|
||||
And the "Jump to activity" select box should not contain "Page 3"
|
||||
# Navigate to Page 2.
|
||||
And I click on "Page 2" "link" in the "page-content" "region"
|
||||
# Since Page 2 has now been viewed and deemed completed, Page 3 can now be accessed.
|
||||
And I should see "Page 3" in the "#next-activity-link" "css_element"
|
||||
And the "Jump to..." select box should contain "Page 3"
|
||||
And the "Jump to activity" select box should contain "Page 3"
|
||||
|
||||
@@ -17,6 +17,7 @@ Feature: Restrict activities availability
|
||||
And the following "activities" exist:
|
||||
| activity | course | name |
|
||||
| assign | C1 | Test assign name |
|
||||
And I enable "bigbluebuttonbn" "mod" plugin
|
||||
|
||||
Scenario: Activities can be added with the default permissions
|
||||
Given I log in as "teacher1"
|
||||
@@ -27,17 +28,34 @@ Feature: Restrict activities availability
|
||||
And I should see "Test assign name"
|
||||
|
||||
@javascript @skip_chrome_zerosize
|
||||
Scenario: Activities can not be added when the admin restricts the permissions
|
||||
Scenario Outline: Activities can not be added when the admin restricts the permissions
|
||||
Given the following "role capability" exists:
|
||||
| role | editingteacher |
|
||||
| mod/assign:addinstance | prohibit |
|
||||
And I log in as "admin"
|
||||
And I am on the "Course 1" "permissions" page
|
||||
And I override the system permissions of "Teacher" role with:
|
||||
| mod/glossary:addinstance | Prohibit |
|
||||
And I log out
|
||||
| role | editingteacher |
|
||||
| <activityrole> | prohibit |
|
||||
And I log in as "teacher1"
|
||||
When I am on "Course 1" course homepage with editing mode on
|
||||
And I click on "Add an activity or resource" "button" in the "New section" "section"
|
||||
Then "Add a new Assignment" "link" should not exist in the "Add an activity or resource" "dialogue"
|
||||
Then "Add a new Glossary" "link" should not exist in the "Add an activity or resource" "dialogue"
|
||||
And I open the activity chooser
|
||||
Then "Add a new <activitytype>" "link" should not exist in the "Add an activity or resource" "dialogue"
|
||||
|
||||
Examples:
|
||||
| activityrole | activitytype |
|
||||
| mod/assign:addinstance | Assignment |
|
||||
| mod/bigbluebuttonbn:addinstance | BigBlueButton |
|
||||
| mod/book:addinstance | Book |
|
||||
| mod/choice:addinstance | Choice |
|
||||
| mod/data:addinstance | Database |
|
||||
| mod/feedback:addinstance | Feedback |
|
||||
| mod/folder:addinstance | Folder |
|
||||
| mod/forum:addinstance | Forum |
|
||||
| mod/glossary:addinstance | Glossary |
|
||||
| mod/h5pactivity:addinstance | H5P |
|
||||
| mod/imscp:addinstance | IMS content package |
|
||||
| mod/label:addinstance | Text and media area |
|
||||
| mod/lesson:addinstance | Lesson |
|
||||
| mod/page:addinstance | Page |
|
||||
| mod/quiz:addinstance | Quiz |
|
||||
| mod/resource:addinstance | File |
|
||||
| mod/scorm:addinstance | SCORM package |
|
||||
| mod/url:addinstance | URL |
|
||||
| mod/wiki:addinstance | Wiki |
|
||||
| mod/workshop:addinstance | Workshop |
|
||||
|
||||
@@ -58,15 +58,19 @@ function xmldb_enrol_database_upgrade($oldversion) {
|
||||
|
||||
// Migrate enrolments where possible.
|
||||
// First, get the user enrolments that can be migrated.
|
||||
// Only select the earliest (MIN id) user_enrolments per user to avoid
|
||||
// duplicate key violations when a user has multiple enrolments across
|
||||
// duplicate database enrol instances.
|
||||
$migrateusers = $DB->get_records_sql(
|
||||
"SELECT ue.id
|
||||
"SELECT MIN(ue.id) AS id
|
||||
FROM {user_enrolments} ue
|
||||
WHERE ue.enrolid $insql
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM {user_enrolments} ue2
|
||||
WHERE ue2.userid = ue.userid
|
||||
AND ue2.enrolid = :idtokeep)",
|
||||
AND ue2.enrolid = :idtokeep)
|
||||
GROUP BY ue.userid",
|
||||
array_merge($inparams, ['idtokeep' => $idtokeep]),
|
||||
);
|
||||
|
||||
|
||||
@@ -122,6 +122,7 @@ class sync_tool_grades extends adhoc_task {
|
||||
mtrace("Skipping - Invalid grade $mtracecontent.");
|
||||
continue;
|
||||
}
|
||||
$grade = floatval($grade); // Grade must be sent as a numeric value, not a string.
|
||||
|
||||
if (empty($grademax)) {
|
||||
mtrace("Skipping - Invalid grademax $mtracecontent.");
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
@enrol @enrol_self
|
||||
Feature: Self-enrolment cohort restriction
|
||||
In order to prevent unauthorized access
|
||||
As a admin
|
||||
I need to restrict self-enrolment to cohort members
|
||||
|
||||
Background:
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
| student2 | Student | 2 | student2@example.com |
|
||||
And the following "cohorts" exist:
|
||||
| name | idnumber | contextid |
|
||||
| Cohort A | CH1 | 1 |
|
||||
And the following "cohort members" exist:
|
||||
| user | cohort |
|
||||
| student1 | CH1 |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And I log in as "admin"
|
||||
And I add "Self enrolment" enrolment method in "Course 1" with:
|
||||
| Custom instance name | Test student enrolment |
|
||||
| Only cohort members | Cohort A |
|
||||
|
||||
Scenario: Self enrolment as cohort member
|
||||
Given I am on the "C1" "Course" page logged in as "student1"
|
||||
When I press "Enrol me"
|
||||
Then I should see "You are enrolled in the course."
|
||||
|
||||
Scenario: Self enrolment as non cohort member
|
||||
Given I am on the "C1" "Course" page logged in as "student2"
|
||||
Then I should see "Only members of cohort 'Cohort A' can self-enrol."
|
||||
@@ -143,7 +143,7 @@
|
||||
|
||||
// Run convert on document (.ps to .gif/.png) or run dvisvgm (.ps to .svg).
|
||||
if ($background) {
|
||||
$bg_opt = "-transparent \"$background\""; // Makes transparent background
|
||||
$bg_opt = '-transparent ' . escapeshellarg($background); // Makes transparent background
|
||||
} else {
|
||||
$bg_opt = "";
|
||||
}
|
||||
|
||||
+181
-2
@@ -26,10 +26,22 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
// Default timeout in seconds for mimetex command execution.
|
||||
defined('FILTER_TEX_MIMETEX_TIMEOUT') || define('FILTER_TEX_MIMETEX_TIMEOUT', 5);
|
||||
|
||||
/**
|
||||
* Check if the current operating system is Windows.
|
||||
*
|
||||
* @return bool True if running on Windows, false otherwise.
|
||||
*/
|
||||
function filter_tex_is_windows(): bool {
|
||||
return (PHP_OS == "WINNT") || (PHP_OS == "WIN32") || (PHP_OS == "Windows");
|
||||
}
|
||||
|
||||
function filter_tex_get_executable($debug=false) {
|
||||
global $CFG;
|
||||
|
||||
if ((PHP_OS == "WINNT") || (PHP_OS == "WIN32") || (PHP_OS == "Windows")) {
|
||||
if (filter_tex_is_windows()) {
|
||||
return "$CFG->dirroot/filter/tex/mimetex.exe";
|
||||
}
|
||||
|
||||
@@ -134,7 +146,7 @@ function filter_tex_get_cmd($pathname, $texexp) {
|
||||
$texexp = escapeshellarg($texexp);
|
||||
$executable = filter_tex_get_executable(false);
|
||||
|
||||
if ((PHP_OS == "WINNT") || (PHP_OS == "WIN32") || (PHP_OS == "Windows")) {
|
||||
if (filter_tex_is_windows()) {
|
||||
$executable = str_replace(' ', '^ ', $executable);
|
||||
return "$executable ++ -e \"$pathname\" -- $texexp";
|
||||
|
||||
@@ -143,6 +155,173 @@ function filter_tex_get_cmd($pathname, $texexp) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run mimetex command with a timeout on Windows.
|
||||
*
|
||||
* @param string $cmd Command string to execute.
|
||||
* @param int $timeoutmicros Timeout in microseconds.
|
||||
* @return array Array with keys: code, timedout, status, errors.
|
||||
*/
|
||||
function filter_tex_exec_windows(string $cmd, int $timeoutmicros): array {
|
||||
// Create temporary file for stderr.
|
||||
$temperr = tempnam(sys_get_temp_dir(), 'err_');
|
||||
|
||||
$descriptors = [
|
||||
0 => ['file', 'NUL', 'r'], // STDIN.
|
||||
1 => ['file', 'NUL', 'w'], // STDOUT.
|
||||
2 => ['file', $temperr, 'w'], // STDERR.
|
||||
];
|
||||
|
||||
$process = proc_open($cmd, $descriptors, $pipes);
|
||||
if (!is_resource($process)) {
|
||||
unlink($temperr);
|
||||
return [
|
||||
'code' => 127, // Command not found.
|
||||
'timedout' => false,
|
||||
'status' => [],
|
||||
'errors' => '',
|
||||
];
|
||||
}
|
||||
|
||||
$timedout = false;
|
||||
while ($timeoutmicros > 0) {
|
||||
$start = microtime(true);
|
||||
$status = proc_get_status($process);
|
||||
|
||||
if (!$status['running']) {
|
||||
break;
|
||||
}
|
||||
|
||||
$timeoutmicros -= (microtime(true) - $start) * 1000000;
|
||||
if ($timeoutmicros <= 0) {
|
||||
$timedout = true;
|
||||
$pid = (int)($status['pid'] ?? 0);
|
||||
exec('taskkill /F /T /PID ' . $pid . ' 2>NUL');
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(50000); // Sleep for 50ms.
|
||||
}
|
||||
|
||||
$status = proc_get_status($process);
|
||||
$code = proc_close($process);
|
||||
|
||||
// Capture stderr from temp file.
|
||||
$errors = file_get_contents($temperr);
|
||||
unlink($temperr);
|
||||
|
||||
return [
|
||||
'code' => $code,
|
||||
'timedout' => $timedout,
|
||||
'status' => $status,
|
||||
'errors' => $errors,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run mimetex command with a timeout on Unix-like systems.
|
||||
*
|
||||
* @param string $cmd Command string to execute.
|
||||
* @param int $timeoutmicros Timeout in microseconds.
|
||||
* @return array Array with keys: code, timedout, status, errors.
|
||||
*/
|
||||
function filter_tex_exec_unix(string $cmd, int $timeoutmicros): array {
|
||||
// File descriptors passed to the process.
|
||||
$descriptors = [
|
||||
0 => ['pipe', 'r'], // STDIN.
|
||||
1 => ['pipe', 'w'], // STDOUT.
|
||||
2 => ['pipe', 'w'], // STDERR.
|
||||
];
|
||||
|
||||
$process = proc_open('exec ' . $cmd, $descriptors, $pipes);
|
||||
if (!is_resource($process)) {
|
||||
return [
|
||||
'code' => 127, // Command not found.
|
||||
'timedout' => false,
|
||||
'status' => [],
|
||||
'errors' => '',
|
||||
];
|
||||
}
|
||||
|
||||
fclose($pipes[0]);
|
||||
stream_set_blocking($pipes[1], false);
|
||||
stream_set_blocking($pipes[2], false);
|
||||
|
||||
$errors = '';
|
||||
$timedout = false;
|
||||
while ($timeoutmicros > 0) {
|
||||
$start = microtime(true);
|
||||
|
||||
$read = [$pipes[1], $pipes[2]];
|
||||
$other = [];
|
||||
stream_select($read, $other, $other, 0, (int)$timeoutmicros);
|
||||
|
||||
$status = proc_get_status($process);
|
||||
|
||||
stream_get_contents($pipes[1]); // Discard stdout to prevent pipe blocking.
|
||||
$errors .= stream_get_contents($pipes[2]);
|
||||
|
||||
if (!$status['running']) {
|
||||
break;
|
||||
}
|
||||
|
||||
$timeoutmicros -= (microtime(true) - $start) * 1000000;
|
||||
if ($timeoutmicros <= 0) {
|
||||
$timedout = true;
|
||||
proc_terminate($process);
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(50000); // Sleep for 50ms.
|
||||
}
|
||||
|
||||
// Read any remaining data from pipes.
|
||||
stream_get_contents($pipes[1]); // Discard remaining stdout.
|
||||
$errors .= stream_get_contents($pipes[2]);
|
||||
|
||||
fclose($pipes[1]);
|
||||
fclose($pipes[2]);
|
||||
|
||||
$status = proc_get_status($process);
|
||||
$code = proc_close($process);
|
||||
|
||||
return [
|
||||
'code' => $code,
|
||||
'timedout' => $timedout,
|
||||
'status' => $status,
|
||||
'errors' => $errors,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run mimetex command with a timeout.
|
||||
*
|
||||
* @param string $cmd Command string to execute.
|
||||
* @param int|null &$code Exit code (passed by reference, set by function).
|
||||
* @return void
|
||||
*/
|
||||
function filter_tex_exec(string $cmd, ?int &$code): void {
|
||||
$timeoutmicros = FILTER_TEX_MIMETEX_TIMEOUT * 1000000;
|
||||
|
||||
if (filter_tex_is_windows()) {
|
||||
$result = filter_tex_exec_windows($cmd, $timeoutmicros);
|
||||
} else {
|
||||
$result = filter_tex_exec_unix($cmd, $timeoutmicros);
|
||||
}
|
||||
|
||||
if ($result['errors']) {
|
||||
debugging('filter_tex_exec errors: ' . $result['errors'], DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
if ($result['timedout']) {
|
||||
$code = 124;
|
||||
} else if ($result['code'] === -1 && isset($result['status']['exitcode']) && $result['status']['exitcode'] !== -1) {
|
||||
$code = $result['status']['exitcode'];
|
||||
} else {
|
||||
$code = $result['code'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge all caches when settings changed.
|
||||
*/
|
||||
|
||||
+1
-2
@@ -18,7 +18,6 @@ define('NO_MOODLE_COOKIES', true); // Because it interferes with caching
|
||||
require_once($CFG->dirroot.'/filter/tex/latex.php');
|
||||
|
||||
$cmd = ''; // Initialise these variables
|
||||
$status = '';
|
||||
|
||||
$relativepath = get_file_argument();
|
||||
|
||||
@@ -58,7 +57,7 @@ define('NO_MOODLE_COOKIES', true); // Because it interferes with caching
|
||||
$texexp = preg_replace('!\r\n?!', ' ', $texexp);
|
||||
$texexp = '\Large '.$texexp;
|
||||
$cmd = filter_tex_get_cmd($pathname, $texexp);
|
||||
system($cmd, $status);
|
||||
filter_tex_exec($cmd, $status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ if ($ADMIN->fulltree) {
|
||||
$items[] = new admin_setting_heading('filter_tex/latexheading', get_string('latexsettings', 'filter_tex'), '');
|
||||
$items[] = new admin_setting_configtextarea('filter_tex/latexpreamble', get_string('latexpreamble','filter_tex'),
|
||||
'', "\\usepackage[latin1]{inputenc}\n\\usepackage{amsmath}\n\\usepackage{amsfonts}\n\\RequirePackage{amsmath,amssymb,latexsym}\n");
|
||||
$items[] = new admin_setting_configtext('filter_tex/latexbackground', get_string('backgroundcolour', 'admin'), '', '#FFFFFF');
|
||||
$items[] = new admin_setting_configcolourpicker('filter_tex/latexbackground', get_string('backgroundcolour', 'admin'), '', '#FFFFFF');
|
||||
$items[] = new admin_setting_configtext('filter_tex/density', get_string('density', 'admin'), '', '120', PARAM_INT);
|
||||
|
||||
$default_filter_tex_pathlatex = '';
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
$texexp = '\Large '.$texexp;
|
||||
$commandpath = filter_tex_get_executable(true);
|
||||
$cmd = filter_tex_get_cmd($pathname, $texexp);
|
||||
system($cmd, $status);
|
||||
filter_tex_exec($cmd, $status);
|
||||
|
||||
if ($return) {
|
||||
return $image;
|
||||
@@ -172,6 +172,10 @@
|
||||
echo "Status corresponds to bus error<br />\n";
|
||||
} else if ($status == 22) {
|
||||
echo "Status corresponds to abnormal termination<br />\n";
|
||||
} else if ($status == 124) {
|
||||
echo "Status corresponds to timeout<br />\n";
|
||||
} else if ($status == 127) {
|
||||
echo "Status corresponds to command not found<br />\n";
|
||||
}
|
||||
if (file_exists($commandpath)) {
|
||||
echo "File size of mimetex executable $commandpath is " . filesize($commandpath) . "<br />";
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
.gradingform_guide .plainvalue.empty {
|
||||
font-style: italic;
|
||||
color: #aaa;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.gradingform_guide .plainvalue.editname {
|
||||
|
||||
@@ -97,6 +97,55 @@ class behat_gradingform_guide extends behat_base {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits an existing marking guide with the provided data.
|
||||
*
|
||||
* This method edits the marking guide of the marking guide definition
|
||||
* form; the provided TableNode should contain one row for
|
||||
* each field and each cell of the row should contain:
|
||||
* | Field name | New value |
|
||||
* | shortname | Updated Grade criterion |
|
||||
*
|
||||
* @When /^I edit the marking guide criterion "([^"]*)" with the following values:$/
|
||||
* @param string $criterionname
|
||||
* @param TableNode $fields
|
||||
*/
|
||||
public function i_edit_the_marking_guide_criterion_with_the_following_values(string $criterionname, TableNode $fields) {
|
||||
if ($fieldvalues = $fields->getHash()) {
|
||||
$criterionid = 0;
|
||||
$locator = "//tr[contains(@class, 'criterion')]//div[@class='criterionname']"
|
||||
. "//span[@class='textvalue'][text()='$criterionname']/ancestor::tr";
|
||||
if ($criterionrow = $this->find('xpath', $locator)) {
|
||||
$criterionid = str_replace('guide-criteria-', '', $criterionrow->getAttribute('id'));
|
||||
}
|
||||
|
||||
if ($criterionid) {
|
||||
$criterionroot = 'guide[criteria]' . '[' . $criterionid . ']';
|
||||
|
||||
foreach ($fieldvalues as $fieldvalue) {
|
||||
// Make sure the fieldvalue array has 2 elements.
|
||||
if (count($fieldvalue) != 2) {
|
||||
throw new ExpectationException(
|
||||
'The field definition should contain field name and new value. ' .
|
||||
'Please follow this format: | Field name | New value |',
|
||||
$this->getSession()
|
||||
);
|
||||
}
|
||||
|
||||
$fieldname = $fieldvalue['Field name'];
|
||||
$newvalue = $fieldvalue['New value'];
|
||||
|
||||
$this->set_guide_field_value($criterionroot . "[$fieldname]", $newvalue);
|
||||
}
|
||||
} else {
|
||||
throw new ExpectationException(
|
||||
'Criterion with name "' . $criterionname . '" not found.',
|
||||
$this->getSession()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the marking guide with the provided data, following marking guide's definition grid cells.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
@gradingform @gradingform_guide
|
||||
Feature: Editing a marking guide already used for grading updates regrade state and student visible grades
|
||||
In order to update marking guide details
|
||||
As a teacher
|
||||
I need to be able to grade a submission
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | One | teacher1@example.com |
|
||||
| student1 | Student | One | student1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
And the following "activities" exist:
|
||||
| activity | course | name | advancedgradingmethod_submissions | assignsubmission_onlinetext_enabled |
|
||||
| assign | C1 | Assign 1 | guide | 1 |
|
||||
And the following "mod_assign > submissions" exist:
|
||||
| assign | user | onlinetext |
|
||||
| Assign 1 | student1 | Assign 1 submission |
|
||||
And I am on the "Course 1" course page logged in as teacher1
|
||||
And I go to "Assign 1" advanced grading definition page
|
||||
And I set the following fields to these values:
|
||||
| Name | Assign 1 marking guide |
|
||||
| Description | Marking guide description |
|
||||
And I define the following marking guide:
|
||||
| Criterion name | Description for students | Description for markers | Maximum score |
|
||||
| Grade criterion A | Grade 1 description for students | Grade 1 description for markers | 70 |
|
||||
| Grade criterion B | Grade 2 description for students | Grade 2 description for markers | 30 |
|
||||
And I press "Save marking guide and make it ready"
|
||||
And I navigate to "Assignment" in current page administration
|
||||
And I go to "Student One" "Assign 1" activity advanced grading page
|
||||
And I grade by filling the marking guide with:
|
||||
| Grade criterion A | 25 | Needs improvement |
|
||||
| Grade criterion B | 20 | Excellent! |
|
||||
And I press "Save changes"
|
||||
And I am on "Course 1" course homepage
|
||||
And I go to "Assign 1" advanced grading definition page
|
||||
And I edit the marking guide criterion "Grade criterion A" with the following values:
|
||||
| Field name | New value |
|
||||
| shortname | Updated Grade criterion A |
|
||||
| description | Updated description for students |
|
||||
| descriptionmarkers | Updated description for markers |
|
||||
| maxscore | 60 |
|
||||
And I press "Save"
|
||||
|
||||
@javascript
|
||||
Scenario: Teacher edits a used marking guide and mark it for regrade and student sees breakdown only after teacher regrades
|
||||
# Set the marking guide to be "Mark for regrade".
|
||||
Given I set the field "menuguideregrade" to "Mark for regrade"
|
||||
And I should see "You are about to save changes to a marking guide that has already been used for grading. Please indicate if existing grades need to be reviewed. If you set this then the marking guide will be hidden from students until their item is regraded."
|
||||
And I click on "Continue" "button"
|
||||
And I am on the "Assign 1" "assign activity" page logged in as student1
|
||||
# Student should not see the grade breakdown as the activity is marked for regrade.
|
||||
And I should not see "Grade breakdown"
|
||||
When I am on the "Assign 1" "assign activity" page logged in as teacher1
|
||||
And I go to "Student One" "Assign 1" activity advanced grading page
|
||||
And I grade by filling the marking guide with:
|
||||
| Updated Grade criterion A | 50 | I changed my mind |
|
||||
| Grade criterion B | 30 | It's all good now |
|
||||
And I press "Save changes"
|
||||
And I am on the "Assign 1" "assign activity" page logged in as student1
|
||||
# Now the student should see the updated grade breakdown.
|
||||
Then I should see "Grade breakdown"
|
||||
And I should see the marking guide information displayed as:
|
||||
| criteria | description | remark | maxscore | criteriascore |
|
||||
| Updated Grade criterion A | Updated description for students | I changed my mind | 60 | 50 / 60 |
|
||||
| Grade criterion B | Grade 2 description for students | It's all good now | 30 | 30 / 30 |
|
||||
|
||||
@javascript
|
||||
Scenario: Teacher edits a used marking guide does not mark for regrade existing student grades remain visible and unchanged
|
||||
# Set the marking guide to be "Do not mark for regrade".
|
||||
Given I set the field "menuguideregrade" to "Do not mark for regrade"
|
||||
And I should see "You are about to save changes to a marking guide that has already been used for grading. Please indicate if existing grades need to be reviewed. If you set this then the marking guide will be hidden from students until their item is regraded."
|
||||
And I click on "Continue" "button"
|
||||
When I am on the "Assign 1" "assign activity" page logged in as student1
|
||||
# Student should see the updated grade breakdown as the activity was not marked for regrading.
|
||||
Then I should see "Grade breakdown"
|
||||
And I should see the marking guide information displayed as:
|
||||
| criteria | description | remark | maxscore | criteriascore |
|
||||
| Updated Grade criterion A | Updated description for students | Needs improvement | 60 | 25 / 60 |
|
||||
| Grade criterion B | Grade 2 description for students | Excellent! | 30 | 20 / 30 |
|
||||
@@ -136,7 +136,7 @@
|
||||
|
||||
.gradingform_rubric .plainvalue.empty {
|
||||
font-style: italic;
|
||||
color: #aaa;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* Make invisible the buttons 'Move up' for the first criterion and
|
||||
|
||||
@@ -169,7 +169,7 @@ if (has_capability('moodle/grade:viewall', $context) && $courseid != SITEID) {
|
||||
|
||||
echo $OUTPUT->header();
|
||||
if ($report->fill_table(true, true)) {
|
||||
echo html_writer::tag('h3', get_string('coursesiamtaking', 'grades'));
|
||||
echo $OUTPUT->heading(get_string('coursesiamtaking', 'grades'));
|
||||
echo '<br />' . $report->print_table(true);
|
||||
}
|
||||
} else { // We have a course context. We must be navigating from the gradebook.
|
||||
@@ -189,7 +189,7 @@ if (has_capability('moodle/grade:viewall', $context) && $courseid != SITEID) {
|
||||
}
|
||||
|
||||
if (count($report->teachercourses)) {
|
||||
echo html_writer::tag('h3', get_string('coursesiamteaching', 'grades'));
|
||||
echo $OUTPUT->heading(get_string('coursesiamteaching', 'grades'));
|
||||
$report->print_teacher_table();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
@gradereport @gradereport_overview
|
||||
Feature: Grade overview report can be viewed
|
||||
In order to see the courses I am enrolled in
|
||||
As a user
|
||||
I should be able to access the grade overview report's index page
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Awesome course | C1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | t1@example.com |
|
||||
| student1 | Student | 1 | s1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
|
||||
@javascript @accessibility
|
||||
Scenario Outline: The grade overview report index page should be accessible
|
||||
Given I am logged in as "<user>"
|
||||
When I follow "Grades" in the user menu
|
||||
Then "<headingname>" "heading" should exist
|
||||
And the page should meet accessibility standards with "best-practice" extra tests
|
||||
|
||||
Examples:
|
||||
| user | headingname |
|
||||
| student1 | Courses I am taking |
|
||||
| teacher1 | Courses I am teaching |
|
||||
@@ -250,7 +250,8 @@ Feature: We can use calculated grade totals
|
||||
| Aggregation | Natural |
|
||||
| Include outcomes in aggregation | 1 |
|
||||
| Exclude empty grades | 0 |
|
||||
And I change window size to "large"
|
||||
# Change window size to ultra-wide to avoid 'element-click-intercepted' failures.
|
||||
And I change window size to "5120x2160"
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
And I give the grade "Excellent" to the user "Student 1" for the grade item "Test outcome item one"
|
||||
And I press "Save changes"
|
||||
@@ -305,7 +306,8 @@ Feature: We can use calculated grade totals
|
||||
And I set the following settings for grade item "Test outcome item one" of type "gradeitem" on "setup" page:
|
||||
| Weight adjusted | 1 |
|
||||
| aggregationcoef2 | 100 |
|
||||
And I change window size to "large"
|
||||
# Change window size to ultra-wide to avoid 'element-click-intercepted' failures.
|
||||
And I change window size to "5120x2160"
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
And I give the grade "Excellent" to the user "Student 1" for the grade item "Test outcome item one"
|
||||
And I press "Save changes"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@core @core_grades @javascript
|
||||
@core @core_grades
|
||||
Feature: We can enter in grades and view reports from the gradebook
|
||||
In order to check the expected results are displayed
|
||||
As a teacher
|
||||
@@ -46,6 +46,7 @@ Feature: We can enter in grades and view reports from the gradebook
|
||||
And I give the grade "90.00" to the user "Student 1" for the grade item "Test assignment name 2"
|
||||
And I press "Save changes"
|
||||
|
||||
@javascript
|
||||
Scenario: Grade a grade item and ensure the results display correctly in the gradebook
|
||||
When I navigate to "View > User report" in the course gradebook
|
||||
And the "Gradebook navigation menu" select menu should contain "Grader report"
|
||||
@@ -68,6 +69,7 @@ Feature: We can enter in grades and view reports from the gradebook
|
||||
And "Course 1" row "Grade" column of "overview-grade" table should contain "170.00"
|
||||
And "Course 1" row "Grade" column of "overview-grade" table should not contain "90.00"
|
||||
|
||||
@javascript
|
||||
Scenario: We can add a weighting to a grade item and it is displayed properly in the user report
|
||||
When I navigate to "Setup > Gradebook setup" in the course gradebook
|
||||
And I set the following settings for grade item "Course 1" of type "course" on "setup" page:
|
||||
@@ -92,3 +94,8 @@ Feature: We can enter in grades and view reports from the gradebook
|
||||
| Test assignment name 1 | 0.72% | 0.72% |
|
||||
| Test assignment name 2 | 1.00% | 1.00% |
|
||||
| Course total | 1.00% | 1.00% |
|
||||
|
||||
Scenario: User not enrolled in any course should not see the grades report
|
||||
Given I log in as "admin"
|
||||
When I follow "Grades" in the user menu
|
||||
Then I should see "You are not enrolled in, nor teaching any courses on this site."
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
}}
|
||||
{{<core/local/comboboxsearch/resultitem }}
|
||||
{{$content}}
|
||||
<div class="pe-2 ps-1 w-25">
|
||||
<div class="pe-3 ps-1 flex-shrink-0">
|
||||
<img class="rounded-circle mx-auto img-fluid" src="{{groupimageurl}}" alt=""/>
|
||||
</div>
|
||||
<div class="pe-3 w-75">
|
||||
<span class="d-block w-100 p-0 text-truncate">
|
||||
<div class="pe-3">
|
||||
<span class="d-block w-100 p-0 text-wrap text-break">
|
||||
{{name}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -226,6 +226,48 @@ document.onreadystatechange = async() => {
|
||||
const Pending = await getPendingClass();
|
||||
var resizePending = new Pending('core_h5p/iframe:resize');
|
||||
|
||||
// Track when the embedded H5P content is fully attached.
|
||||
const contentLoadedPending = new Pending('core_h5p/iframe:contentLoaded');
|
||||
let contentLoaded = false;
|
||||
const markContentLoaded = function() {
|
||||
const body = iFrame?.contentDocument?.body;
|
||||
const hasContent = body && body.querySelector('.h5p-container, .h5p-content');
|
||||
|
||||
// Check that H5P instance is actually ready with proper initialization
|
||||
if (contentLoaded || !hasContent || !H5P?.instances?.[0]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
contentLoaded = true;
|
||||
contentLoadedPending.resolve();
|
||||
H5PEmbedCommunicator.send('contentLoaded');
|
||||
return true;
|
||||
};
|
||||
|
||||
// If the content is already there, mark it immediately.
|
||||
markContentLoaded();
|
||||
|
||||
// Observe the iframe document for the first appearance of the H5P container.
|
||||
if (!contentLoaded && iFrame.contentDocument?.body) {
|
||||
const contentObserver = new MutationObserver(function() {
|
||||
if (markContentLoaded()) {
|
||||
contentObserver.disconnect();
|
||||
}
|
||||
});
|
||||
contentObserver.observe(iFrame.contentDocument.body, {childList: true, subtree: true});
|
||||
}
|
||||
|
||||
// Extended fallback timeout to ensure pending resolves even if content detection fails.
|
||||
if (!contentLoaded) {
|
||||
setTimeout(function() {
|
||||
if (!contentLoaded) {
|
||||
contentLoaded = true;
|
||||
contentLoadedPending.resolve();
|
||||
H5PEmbedCommunicator.send('contentLoaded');
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
H5P.on(instance, 'resize', function() {
|
||||
if (H5P.isFullscreen) {
|
||||
return; // Skip iframe resize.
|
||||
@@ -249,6 +291,8 @@ document.onreadystatechange = async() => {
|
||||
}, 150);
|
||||
});
|
||||
|
||||
H5P.externalDispatcher.on('domChanged', markContentLoaded);
|
||||
|
||||
// Get emitted xAPI data.
|
||||
H5P.externalDispatcher.on('xAPI', function(event) {
|
||||
statementPosted = false;
|
||||
|
||||
@@ -33,7 +33,7 @@ $string['cannotcreatedboninstall'] = '<p>No es pot crear la base de dades.</p> <
|
||||
<p>L\'administrador del lloc hauria de verificar la configuració de la base de dades.</p>';
|
||||
$string['cannotcreatelangdir'] = 'No s\'ha pogut crear el directori d\'idiomes';
|
||||
$string['cannotcreatetempdir'] = 'No s\'ha pogut crear el directori temporal';
|
||||
$string['cannotdownloadcomponents'] = 'No s\'han pogut baixar components';
|
||||
$string['cannotdownloadcomponents'] = 'No s\'han pogut descarregar els components';
|
||||
$string['cannotdownloadzipfile'] = 'No s\'ha pogut baixar el fitxer ZIP';
|
||||
$string['cannotfindcomponent'] = 'No s\'ha pogut trobar el component';
|
||||
$string['cannotsavemd5file'] = 'No s\'ha pogut desar el fitxer md5';
|
||||
@@ -46,6 +46,6 @@ $string['invalidmd5'] = 'L\'md5 no és vàlid. Torneu a provar-ho';
|
||||
$string['missingrequiredfield'] = 'Falta algun camp necessari';
|
||||
$string['remotedownloaderror'] = '<p>No s\'ha pogut baixar el component al vostre servidor. Verifiqueu els paràmetres del servidor intermediari. Es recomana vivament l\'extensió cURL de PHP.</p>
|
||||
<p>Haureu de baixar manualment el fitxer <a href="{$a->url}">{$a->url}</a>, copiar-lo a la ubicació «{$a->dest}» del vostre servidor i descomprimir-lo allà.</p>';
|
||||
$string['wrongdestpath'] = 'El camí de destinació és erroni';
|
||||
$string['wrongsourcebase'] = 'L\'adreça (URL) base de la font és errònia';
|
||||
$string['wrongdestpath'] = 'El camí de destinació és incorrecte';
|
||||
$string['wrongsourcebase'] = 'L\'URL base de la font és errònia';
|
||||
$string['wrongzipfilename'] = 'El nom del fitxer ZIP és erroni';
|
||||
|
||||
@@ -30,20 +30,20 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['admindirname'] = 'Directori d\'administració';
|
||||
$string['availablelangs'] = 'Llista d\'idiomes disponibles';
|
||||
$string['availablelangs'] = 'Paquets d\'idiomes disponibles';
|
||||
$string['chooselanguagehead'] = 'Trieu un idioma';
|
||||
$string['chooselanguagesub'] = 'Trieu un idioma per a la instal·lació. S\'utilitzarà també com a idioma per defecte del lloc, tot i que després podeu canviar-lo.';
|
||||
$string['clialreadyconfigured'] = 'El fitxer config.php ja existeix; feu servir admin/cli/install_database.php si voleu instal·lar Moodle en aquest lloc web.';
|
||||
$string['clialreadyinstalled'] = 'El fitxer de configuració config.php ja existeix. Feu servir admin/cli/upgrade.php si voleu actualitzar Moodle per a aquest lloc web.';
|
||||
$string['cliinstallheader'] = 'Programa d\'instal·lació de línia d\'ordres de Moodle {$a}';
|
||||
$string['clitablesexist'] = 'Les taules de la base de dades ja existeixen. La instal·lació per línia d\'ordres no pot continuar.';
|
||||
$string['databasehost'] = 'Servidor de base de dades:';
|
||||
$string['databasename'] = 'Nom de la base de dades:';
|
||||
$string['databasehost'] = 'Servidor de base de dades';
|
||||
$string['databasename'] = 'Nom de la base de dades';
|
||||
$string['databasetypehead'] = 'Trieu el controlador de la base de dades';
|
||||
$string['dataroot'] = 'Directori de dades';
|
||||
$string['datarootpermission'] = 'Permisos dels directoris de dades';
|
||||
$string['dbprefix'] = 'Prefix de taules';
|
||||
$string['dirroot'] = 'Directori de Moodle';
|
||||
$string['dirroot'] = 'Directori del Moodle';
|
||||
$string['environmenthead'] = 'S\'està comprovant el vostre entorn';
|
||||
$string['environmentsub2'] = 'Cada versió de Moodle té uns requeriments mínims de versió de PHP i un nombre d\'extensions de PHP necessàries.
|
||||
Abans de cada instal·lació o actualització es realitza una comprovació exhaustiva de l\'entorn. Contacteu amb l\'administrador si no sabeu com instal·lar una nova versió de PHP o com habilitar les extensions.';
|
||||
@@ -72,7 +72,7 @@ $string['pathssubdataroot'] = '<p>Directori on Moodle emmagatzemarà els materia
|
||||
<p>Aquest directori hauria de tenir permisos de lectura i escriptura per a l\'usuari del servidor web (normalment «www-data», «nobody», or «apache»).</p>
|
||||
<p>No ha de ser accessible directament via web.</p>
|
||||
<p>L\'instal·lador provarà de crear-lo si no existeix.</p>';
|
||||
$string['pathssubdirroot'] = 'Camí complet del directori d\'instal·lació de Moodle.';
|
||||
$string['pathssubdirroot'] = '<p>El camí complet al directori que conté el codi del Moodle.</p>';
|
||||
$string['pathssubwwwroot'] = '<p>L\'adreça web completa on s\'accedirà a Moodle; per exemple, l\'adreça que els usuaris introduiran a la barra d\'adreces del navegador per accedir a Moodle.</p> <p> No és possible accedir a Moodle utilitzant diferents adreces. Si el vostre lloc és accessible a través de diferents adreces, trieu-ne la més fàcil i configureu una redirecció permanent per a cadascuna de les altres adreces.</p> <p>
|
||||
Si el vostre lloc és accessible tant des d\'Internet com des d\'una xarxa interna (anomenada de vegades intranet), utilitzeu l\'adreça pública aquí.</p> <p>Si l\'adreça actual no és correcta, canvieu l\'URL a la barra d\'adreces del navegador i reinicieu la instal·lació.';
|
||||
$string['pathsunsecuredataroot'] = 'La ubicació del dataroot no és segura.';
|
||||
@@ -86,9 +86,7 @@ $string['welcomep10'] = '{$a->installername} ({$a->installerversion})';
|
||||
$string['welcomep20'] = 'Esteu veient aquesta pàgina perquè heu instal·lat amb èxit i heu executat el paquet <strong>{$a->packname} {$a->packversion}</strong>. Felicitacions.';
|
||||
$string['welcomep30'] = 'Aquesta versió de <strong>{$a->installername}</strong> inclou les aplicacions necessàries per crear un entorn en el qual funcioni <strong>Moodle</strong>, concretament:';
|
||||
$string['welcomep40'] = 'El paquet inclou també <strong>Moodle {$a->moodlerelease} ({$a->moodleversion})</strong>.';
|
||||
$string['welcomep50'] = 'L\'ús de totes les aplicacions d\'aquest paquet és governat per les seves llicències respectives. El paquet <strong>{$a->installername}</strong> complet és
|
||||
<a href="http://www.opensource.org/docs/definition_plain.html">codi font obert</a> i es distribueix
|
||||
sota llicència <a href="http://www.gnu.org/copyleft/gpl.html">GPL</a>.';
|
||||
$string['welcomep60'] = 'Les pàgines següents us guiaran per una sèrie de passos fàcils de seguir per configurar <strong>Moodle</strong> en el vostre ordinador. Podeu acceptar els paràmetres per defecte o, opcionalment, modificar-los perquè s\'ajustin a les vostres necessitats.';
|
||||
$string['welcomep50'] = 'L\'ús de totes les aplicacions d\'aquest paquet es regeix per les seves respectives llicències. El paquet complet <strong>{$a->installername}</strong> és de <a href="https://www.opensource.org/docs/definition_plain.html">codi obert</a> i es distribueix sota la llicència <a href="https://www.gnu.org/copyleft/gpl.html">GPL</a>.';
|
||||
$string['welcomep60'] = 'Les pàgines següents us guiaran per una sèrie de passos fàcils de seguir per configurar el <strong>Moodle</strong> en el vostre ordinador. Podeu acceptar els paràmetres per defecte o, opcionalment, modificar-los perquè s\'ajustin a les vostres necessitats.';
|
||||
$string['welcomep70'] = 'Feu clic al botó «Següent» per continuar amb la configuració de <strong>Moodle</strong>.';
|
||||
$string['wwwroot'] = 'Adreça web';
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['clianswerno'] = 'n';
|
||||
$string['cliansweryes'] = 's';
|
||||
$string['cliincorrectvalueerror'] = 'Error, valor incorrecto "{$a->value}" para "{$a->option}"';
|
||||
$string['cliincorrectvalueretry'] = 'Valor incorrecto, por favor intenta de nuevo';
|
||||
$string['clitypevalue'] = 'escribe el valor';
|
||||
$string['clitypevaluedefault'] = 'escribe el valor, presiona Enter para usar el valor predeterminado ({$a})';
|
||||
$string['cliunknowoption'] = 'Opciones no reconocidas:
|
||||
{$a}
|
||||
Por favor usa la opción --help.';
|
||||
$string['cliyesnoprompt'] = 'escribe s (significa sí) o n (significa no)';
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['cannotcreatedboninstall'] = '<p>Chan urrainn an stòr-dàta a chruthachadh.</p>
|
||||
<p>Chan eil an stòr-dàta a chaidh a shònrachadh ann am bith agus chan eil cead aig an neach-cleachdaidh tugta an stòr-dàta a chruthachadh.</p>
|
||||
<p>Bu chòir do rianaire na làraich rèiteachadh an stòr-dàta a dhearbhadh.</p>';
|
||||
$string['cannotcreatelangdir'] = 'Chan urrainn iùl-lann cànain a chruthachadh';
|
||||
$string['cannotcreatetempdir'] = 'Chan urrainn iùl-lann shealach a chruthachadh';
|
||||
$string['cannotdownloadcomponents'] = 'Chan urrainn co-phàirtean a luchdachadh a-nuas';
|
||||
$string['cannotdownloadzipfile'] = 'Chan urrainn faidhle ZIP a luchdachadh a-nuas';
|
||||
$string['cannotfindcomponent'] = 'Chan urrainn a’ cho-phàirt a lorg';
|
||||
$string['cannotsavemd5file'] = 'Chan urrainn faidhle md5 a shàbhaladh';
|
||||
$string['cannotsavezipfile'] = 'Chan urrainn faidhle ZIP a shàbhaladh';
|
||||
$string['cannotunzipfile'] = 'Chan urrainn am faidhle siopach fhosgladh';
|
||||
$string['componentisuptodate'] = 'Tha a’ cho-phàirt ris an là';
|
||||
$string['dmlexceptiononinstall'] = '<p>Tha mearachd stòir-dàta air tachairt \\[{$a->errorcode}].<br />{$a->debuginfo}</p>';
|
||||
$string['downloadedfilecheckfailed'] = 'Dh’fhàillig sgrùdadh an fhaidhle a chaidh a luchdachadh a-nuas';
|
||||
$string['missingrequiredfield'] = 'Tha raon deatamach air choireigin a dhìth';
|
||||
$string['remotedownloaderror'] = '<p>Dh’fhàillig luchdachadh na co-phàirte a-nuas chun fhrithealaiche agad. Dearbh na suidheachaidhean procsaidh: thathas a’ moladh an leudachain PHP cURL gu mòr.</p>
|
||||
<p>Feumaidh tu am faidhle <a href="{$a->url}">{$a->url}</a> a luchdachadh a-nuas le làimh, lethbhreac dheth a chur ann an "{$a->dest}" san fhrithealaiche agad agus an siopadh fhosgladh an sin.</p>';
|
||||
$string['wrongdestpath'] = 'Slighe ceann-uidhe ceàrr';
|
||||
$string['wrongsourcebase'] = 'Bun-thùs URL ceàrr';
|
||||
$string['wrongzipfilename'] = 'Ainm ceàrr don fhaidhle ZIP';
|
||||
@@ -39,3 +39,6 @@ $string['cliunknowoption'] = 'अपरिचित विकल्प:
|
||||
{$a}
|
||||
कृपया --help विकल्प का उपयोग करें।';
|
||||
$string['cliyesnoprompt'] = 'टाइप Y (का मतलब है हाँ) या N (का मतलब है नहीं )';
|
||||
$string['environmentrequireinstall'] = 'स्थापित और सक्षम होना चाहिए';
|
||||
$string['environmentrequireversion'] = 'version {$a->needed} की आवश्यकता है और आप {$a-> current} चला रहे हैं';
|
||||
$string['upgradekeyset'] = 'उन्नयन कुंजी (इसे सेट न करने के लिए खाली छोड़ दें)';
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['cannotcreatedboninstall'] = '<p> डेटाबेस नहीं बना सकता है। </p> <p> निर्दिष्ट डेटाबेस मौजूद नहीं है और दिए गए उपयोगकर्ता के पास डेटाबेस बनाने की अनुमति नहीं है। </p> <p> साइट प्रशासक को डेटाबेस विन्यास को सत्यापित करना चाहिए। </p>';
|
||||
$string['cannotcreatelangdir'] = 'लैंग निर्देशिका नहीं बना सकता है';
|
||||
$string['cannotcreatetempdir'] = 'अस्थायी निर्देशिका नहीं बना सकता है';
|
||||
$string['cannotdownloadcomponents'] = 'घटकों को डाउनलोड नहीं कर सकते';
|
||||
$string['cannotdownloadzipfile'] = 'ज़िप दाखिल करना डाउनलोड नहीं कर सकते';
|
||||
$string['cannotfindcomponent'] = 'अवयव नहीं मिल रहा है';
|
||||
$string['cannotsavemd5file'] = 'md5 दाखिल करना को सहेज नहीं सकते';
|
||||
$string['cannotsavezipfile'] = 'ज़िप दाखिल करना को सहेज नहीं सकते';
|
||||
$string['cannotunzipfile'] = 'दाखिल करना को अनज़िप नहीं कर सकते';
|
||||
$string['componentisuptodate'] = 'अवयव अद्यतित है';
|
||||
$string['dmlexceptiononinstall'] = '<p>डेटाबेस में त्रुटि आ गई है [{$a->errorcode}]।<br />{$a->debuginfo}</p>';
|
||||
$string['downloadedfilecheckfailed'] = 'डाउनलोड की गई दाखिल करना जाँच विफल रही';
|
||||
$string['invalidmd5'] = 'चेक चर गलत था-फिर से कोशिश करें';
|
||||
$string['missingrequiredfield'] = 'कुछ अपेक्षित क्षेत्र गायब है';
|
||||
$string['remotedownloaderror'] = '<p>आपके सर्वर पर कंपोनेंट डाउनलोड करने में विफलता मिली। कृपया प्रॉक्सी सेटिंग्स की जाँच करें; PHP cURL एक्सटेंशन का उपयोग करने की अत्यधिक अनुशंसा की जाती है।</p>
|
||||
<p>आपको <a href="{$a->url}">{$a->url}</a> फ़ाइल को मैन्युअल रूप से डाउनलोड करना होगा, इसे अपने सर्वर के "{$a->dest}" फ़ोल्डर में कॉपी करना होगा और वहाँ इसे अनज़िप करना होगा।</p>';
|
||||
$string['wrongdestpath'] = 'गलत लक्ष्य मार्ग';
|
||||
$string['wrongsourcebase'] = 'गलत उद्गम URL मूल';
|
||||
$string['wrongzipfilename'] = 'गलत ज़िप दाखिल करना नाम';
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['admindirname'] = 'प्रशासक निर्देशिका';
|
||||
$string['availablelangs'] = 'उपलब्ध भाषा पैक';
|
||||
$string['chooselanguagehead'] = 'एक भाषा चुनें';
|
||||
$string['chooselanguagesub'] = 'कृपया अधिष्ठापन के लिए एक भाषा चुनें। इस भाषा का उपयोग साइट के लिए चूकना भाषा के रूप में भी किया जाएगा, हालांकि इसे बाद में बदला जा सकता है।';
|
||||
$string['clialreadyconfigured'] = 'कॉन्फ़िगरेशन फ़ाइल config.php पहले से मौजूद है। कृपया इस साइट के लिए Moodle इंस्टॉल करने के लिए admin/cli/install_database.php का उपयोग करें।';
|
||||
$string['clialreadyinstalled'] = 'कॉन्फ़िगरेशन फ़ाइल config.php पहले से मौजूद है। कृपया इस साइट के लिए Moodle इंस्टॉल करने के लिए admin/cli/install_database.php का उपयोग करें।';
|
||||
$string['cliinstallheader'] = 'मूडल {$a} कमांड लाइन अधिष्ठापन कार्यक्रम';
|
||||
$string['clitablesexist'] = 'डेटाबेस तालिकाएँ पहले से मौजूद हैं; CLI अधिष्ठापन जारी नहीं रह सकती है।';
|
||||
$string['databasehost'] = 'डेटाबेस मेजबान';
|
||||
$string['databasename'] = 'डेटाबेस का नाम';
|
||||
$string['databasetypehead'] = 'डेटाबेस चालक चुनें';
|
||||
$string['dataroot'] = 'डेटा निर्देशिका';
|
||||
$string['datarootpermission'] = 'डेटा निर्देशिकाओं की अनुमति';
|
||||
$string['dbprefix'] = 'तालिका उपसर्ग';
|
||||
$string['dirroot'] = 'मूडल निर्देशिका';
|
||||
$string['environmenthead'] = 'अपने पर्यावरण की जाँच करें।....';
|
||||
$string['environmentsub2'] = 'प्रत्येक मूडल रिलीज में कुछ न्यूनतम, कम से कम PHP संस्करण अपेक्षा, आवश्यकता और कई अनिवार्य पी. एच. पी. विस्तार होते हैं। प्रत्येक स्थापना और उन्नयन करना से पहले पूर्ण पर्अथवावरण जांच की जाती है। यदि आप नहीं जानकारी कि नअथवा संस्करण कैसे स्थापित करना है अथवा PHP विस्तार को सक्षम करना है तो कृपअथवा सर्वर प्रशासक से संपर्क करें।';
|
||||
$string['errorsinenvironment'] = 'पर्यावरण जांच विफल रही!';
|
||||
$string['installation'] = 'अधिष्ठापन';
|
||||
$string['langdownloaderror'] = 'दुर्भाग्य से "{$a}" भाषा डाउनलोड नहीं की जा सकी। अधिष्ठापन प्रक्रम अंग्रेजी में जारी रहेगी।';
|
||||
$string['memorylimithelp'] = '<p>आपके सर्वर के लिए PHP मेमोरी सीमा वर्तमान में {$a} पर सेट है।</p>
|
||||
|
||||
<p>इससे Moodle को बाद में मेमोरी संबंधी समस्याएँ हो सकती हैं, विशेष रूप से
|
||||
|
||||
यदि आपने कई मॉड्यूल सक्रिय किए हैं और/या कई उपयोगकर्ता हैं।</p>
|
||||
|
||||
<p>हमारा सुझाव है कि यदि संभव हो तो आप PHP को उच्च सीमा, जैसे 40M, पर कॉन्फ़िगर करें।
|
||||
|
||||
ऐसा करने के कई तरीके हैं जिन्हें आप आज़मा सकते हैं:</p>
|
||||
<ol>
|
||||
<li>यदि संभव हो, तो PHP को <i>--enable-memory-limit</i> के साथ पुनः संकलित करें।
|
||||
|
||||
इससे Moodle स्वयं मेमोरी सीमा निर्धारित कर सकेगा।</li>
|
||||
<li>यदि आपके पास अपनी php.ini फ़ाइल तक पहुँच है, तो आप उसमें <b>memory_limit</b>
|
||||
सेटिंग को 40M जैसी किसी मान पर बदल सकते हैं।</li> यदि आपके पास पहुँच नहीं है, तो आप
|
||||
अपने व्यवस्थापक से यह करने के लिए कह सकते हैं।</li>
|
||||
<li>कुछ PHP सर्वरों पर, आप Moodle निर्देशिका में एक .htaccess फ़ाइल बना सकते हैं,
|
||||
|
||||
जिसमें यह पंक्ति हो:
|
||||
|
||||
<blockquote><div>php_value memory_limit 40M</div></blockquote>
|
||||
<p>हालाँकि, कुछ सर्वरों पर यह सभी PHP पृष्ठों को काम करने से रोक देगा
|
||||
(पृष्ठों को देखते समय आपको त्रुटियाँ दिखाई देंगी), इसलिए आपको .htaccess फ़ाइल को हटाना होगा।</p></li>
|
||||
</ol>';
|
||||
$string['paths'] = 'रास्ते';
|
||||
$string['pathserrcreatedataroot'] = 'डेटा निर्देशिका ({$a-> dataroot}) इंस्टॉलर द्वारा नहीं बनाई जा सकती है।';
|
||||
$string['pathshead'] = 'रास्तों की पुष्टि';
|
||||
$string['pathsrodataroot'] = 'डेटारूट निर्देशिका लिखित नहीं है।';
|
||||
$string['pathsroparentdataroot'] = 'मूल निर्देशिका ({$a-> parent}) लिखने योग्य नहीं है। डेटा निर्देशिका ({$a-> dataroot}) इंस्टॉलर द्वारा नहीं बनाई जा सकती है।';
|
||||
$string['pathssubadmindir'] = 'बहुत कम वेबहोस्ट नियंत्रण कक्ष अथवा किसी चीज़ तक पहुँचने के लिए आपके लिए एक विशेष URL के रूप में प्रशासक का उपयोग करते हैं। दुर्भाग्य से यह मूडल प्रशासक पृष्ठों के लिए स्तर स्थान के साथ संघर्ष करता है। आप अपने इंस्टॉलेशन में प्रशासक निर्देशिका का नाम बदलकर और उस नए नाम को यहाँ रखकर इसे निश्चित करना सकते हैं। उदाहरण के लिए: <em>moodleadmin</em> यह मूडल में प्रशासक लिंक को निश्चित करनाेगा।';
|
||||
$string['pathssubdataroot'] = '<p> एक निर्देशिका जहाँ मूडल उपयोगकर्ताओं द्वारा अपलोड की गई सभी दाखिल करना सामग्री को संग्रहीत करेगा। </p> <p> यह निर्देशिका वेब सर्वर उपयोगकर्ता (आमतौर पर \'www-data\', \'कोई नहीं\' अथवा \'अपाचे\') द्वारा पढ़ने योग्य और लिखने योग्य दोनों होनी चाहिए। </p> <p> यह सीधे वेब पर सुलभ नहीं होना चाहिए। </p> <p> यदि निर्देशिका वर्तमान में मौजूद नहीं है, तो अधिष्ठापन प्रक्रम इसे बनाने का प्रअथवास करेगी। </p>';
|
||||
$string['pathssubdirroot'] = '<p> मॉड्यूल कोड वाली निर्देशिका का पूरा मार्ग। </p>';
|
||||
$string['pathssubwwwroot'] = '<p> पूरा सम्बोधन जहाँ मूडल तक पहुँचा जाएगा i.e। वह सम्बोधन जिसे उपयोगकर्ता मूडल तक पहुँचने के लिए अपने ब्राउज़र के पते की पट्टी में दर्ज करेंगे। </p> <p> कई पतों का उपयोग करके मूडल तक पहुँचना संभव नहीं है। यदि आपकी साइट कई पतों के माध्यम से सुलभ है तो सबसे आसान का चयन करें और अन्य पतों में से प्रत्येक के लिए एक स्थायी पुनर्निर्देश स्थापित करें। </p> <p> यदि आपकी साइट इंटरनेट और आंतरिक नेटवर्क (जिसे कभी-कभी इंट्रानेट कहा जाता है) दोनों से सुलभ है, तो यहाँ सार्वजनिक पते का उपयोग करें। </p> <p> यदि चालू, प्रचलित सम्बोधन सही नहीं है, तो कृपया अपने ब्राउज़र के पते की पट्टी में यूआरएल बदलें और स्थापना को फिर से शुरू करें। </p>';
|
||||
$string['pathsunsecuredataroot'] = 'डेटारूट स्थान सुरक्षित नहीं है';
|
||||
$string['pathswrongadmindir'] = 'प्रशासक निर्देशिका मौजूद नहीं है';
|
||||
$string['phpextension'] = '{$a} PHP विस्तार';
|
||||
$string['phpversion'] = 'PHP संस्करण';
|
||||
$string['phpversionhelp'] = '<p>Moodle के लिए PHP का कम से कम 5.6.5 या 7.1 संस्करण आवश्यक है (7.0.x में कुछ इंजन संबंधी सीमाएँ हैं)।</p>
|
||||
<p>आप वर्तमान में {$a} संस्करण चला रहे हैं।</p>
|
||||
<p>आपको PHP को अपग्रेड करना होगा या PHP के नए संस्करण वाले होस्ट पर जाना होगा।</p>';
|
||||
$string['welcomep20'] = 'आप इस पृष्ठ को इसलिए देख रहे हैं क्योंकि आपने अपने कंप्यूटर में <strong>{$a->packname} {$a->packversion}</strong> पैकेज को सफलतापूर्वक स्थापित और लॉन्च कर लिया है।
|
||||
बधाई हो!';
|
||||
$string['welcomep30'] = 'इस <strong>{$a->installername}</strong> रिलीज़ में निम्नलिखित एप्लिकेशन शामिल हैं:
|
||||
एक ऐसा वातावरण बनाने के लिए जिसमें <strong>Moodle</strong> काम करेगा, अर्थात्:';
|
||||
$string['welcomep40'] = 'इस पैकेज में <strong>Moodle {$a->moodlerelease} ({$a->moodleversion})</strong> भी शामिल है।';
|
||||
$string['welcomep50'] = 'इस पैकेज में शामिल सभी अनुप्रयोगों का उपयोग उनके संबंधित लाइसेंसों द्वारा नियंत्रित होता है। संपूर्ण <strong>{$a->installername}</strong> पैकेज <a href="https://www.opensource.org/docs/definition_plain.html">ओपन सोर्स</a> है और इसे <a href="https://www.gnu.org/copyleft/gpl.html">GPL</a> लाइसेंस के अंतर्गत वितरित किया जाता है।';
|
||||
$string['welcomep60'] = 'निम्नलिखित पृष्ठों में दिए गए आसान चरणों का पालन करके आप अपने कंप्यूटर पर <strong>मूडल </strong> को कॉन्फ़िगर और सेट अप कर सकते हैं।
|
||||
आप डिफ़ॉल्ट सेटिंग्स को स्वीकार कर सकते हैं या चाहें तो उन्हें अपनी आवश्यकताओं के अनुसार बदल सकते हैं।';
|
||||
$string['welcomep70'] = '<strong>मूडल </strong> के सेटअप के साथ जारी रखने के लिए नीचे दिए गए "अगले" बटन पर क्लिक करें।';
|
||||
$string['wwwroot'] = 'वेब सम्बोधन';
|
||||
@@ -41,3 +41,4 @@ Per favor usa le option --help.';
|
||||
$string['cliyesnoprompt'] = 'typa s (significa si) o n (significa no)';
|
||||
$string['environmentrequireinstall'] = 'debe esser installate e habilitate';
|
||||
$string['environmentrequireversion'] = 'version {$a->needed} es requirite e tu es executante {$a->current}';
|
||||
$string['upgradekeyset'] = 'Clave de actualisation (lassa vacue pro non fixar lo)';
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link https://moodledev.io/general/projects/api/amos}) using the
|
||||
* list of strings defined in install/stringnames.txt file.
|
||||
*
|
||||
* @package installer
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['thislanguage'] = 'Provençal Occitan';
|
||||
@@ -43,3 +43,10 @@ $string['cannotunzipfile'] = 'Faylni arxivdan chiqarib bo‘lmadi';
|
||||
$string['componentisuptodate'] = 'Komponent yangilangan holda';
|
||||
$string['dmlexceptiononinstall'] = '<p>Ma’lumotlar bazasida xatolik yuz berdi [{$a->errorcode}].<br />{$a->debuginfo}</p>';
|
||||
$string['downloadedfilecheckfailed'] = 'Yuklab olingan faylni tekshirish muvaffaqiyatsiz tugadi';
|
||||
$string['invalidmd5'] = 'Tekshiruv o‘zgaruvchisi noto‘g‘ri — qayta urinib ko‘ring';
|
||||
$string['missingrequiredfield'] = 'Ba’zi talab qilingan maydonlar mavjud emas';
|
||||
$string['remotedownloaderror'] = '<p>Komponentni serveringizga yuklab olish muvaffaqiyatsiz bo‘ldi. Iltimos, proksi sozlamalarini tekshiring; PHP cURL kengaytmasi tavsiya etiladi.</p>
|
||||
<p>Siz <a href="{$a->url}">{$a->url}</a> faylini qo‘lda yuklab olishingiz, serveringizdagi "{$a->dest}" ga nusxalashingiz va u yerda arxivdan chiqarishingiz kerak.</p>';
|
||||
$string['wrongdestpath'] = 'Noto‘g‘ri manzil yo‘li.';
|
||||
$string['wrongsourcebase'] = 'Noto‘g‘ri manba URL bazasi.';
|
||||
$string['wrongzipfilename'] = 'Noto‘g‘ri ZIP fayl nomi.';
|
||||
|
||||
@@ -31,10 +31,44 @@ defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['admindirname'] = 'Admin katalogi';
|
||||
$string['availablelangs'] = 'Mavjud til paketlari';
|
||||
$string['chooselanguagehead'] = 'Tilni tanlash';
|
||||
$string['chooselanguagesub'] = 'Iltimos, o‘rnatish uchun tilni tanlang. Ushbu til saytning standart tili sifatida ham ishlatiladi, ammo keyinchalik o‘zgartirish mumkin.';
|
||||
$string['clialreadyconfigured'] = 'config.php konfiguratsiya fayli allaqachon mavjud. Iltimos, ushbu sayt uchun Moodle’ni o‘rnatish uchun admin/cli/install_database.php faylidan foydalaning.';
|
||||
$string['clialreadyinstalled'] = 'config.php konfiguratsiya fayli allaqachon mavjud. Iltimos, ushbu sayt uchun Moodle’ni yangilash uchun admin/cli/install_database.php faylidan foydalaning.';
|
||||
$string['cliinstallheader'] = 'Moodle {$a} buyruq qatori o‘rnatish dasturi';
|
||||
$string['clitablesexist'] = 'Ma’lumotlar bazasi jadvallari allaqachon mavjud; CLI orqali o‘rnatish davom ettirib bo‘lmaydi.';
|
||||
$string['databasehost'] = 'Ma’lumotlar bazasi mezboni';
|
||||
$string['databasename'] = 'Ma’lumotlar bazasi nomi';
|
||||
$string['databasetypehead'] = 'Ma’lumotlar bazasi drayverini tanlang';
|
||||
$string['dataroot'] = 'Ma’lumotlar katalogi';
|
||||
$string['datarootpermission'] = 'Data kataloglari uchun ruxsatlar';
|
||||
$string['dbprefix'] = 'Jadvallar prefiksi';
|
||||
$string['dirroot'] = 'Moodle katalogi';
|
||||
$string['environmenthead'] = 'Muhitingiz tekshirilmoqda ...';
|
||||
$string['environmentsub2'] = 'Har bir Moodle versiyasi minimal PHP versiyasi talabi va majburiy PHP kengaytmalariga ega.
|
||||
To‘liq muhit tekshiruvi har bir o‘rnatish yoki yangilashdan oldin amalga oshiriladi. Agar yangi versiyani o‘rnatish yoki PHP kengaytmalarini yoqishni bilmasangiz, server administratoriga murojaat qiling.';
|
||||
$string['errorsinenvironment'] = 'Muhitni tekshirish muvaffaqiyatsiz tugadi!';
|
||||
$string['installation'] = 'O\'rnatish';
|
||||
$string['langdownloaderror'] = 'Afsuski, "{$a}" tili yuklab bo‘lmadi. O‘rnatish jarayoni ingliz tilida davom ettiriladi.';
|
||||
$string['paths'] = 'O‘tkazib yuborishlar';
|
||||
$string['pathserrcreatedataroot'] = 'Ma’lumotlar papkasi ({$a->dataroot}) o‘rnatish dasturi tomonidan yaratilolmayapti.';
|
||||
$string['pathshead'] = 'O’tkazib yuborishlarni tasdiqlash';
|
||||
$string['pathsrodataroot'] = 'Dataroot papkasiga yozish mumkin emas.';
|
||||
$string['pathsroparentdataroot'] = 'Ota papkaga ({$a->parent}) yozish mumkin emas. Shu sababli, ma’lumotlar papkasi ({$a->dataroot}) o‘rnatish dasturi tomonidan yaratilolmayapti.';
|
||||
$string['pathssubadmindir'] = 'Faqat juda kam web-hostlar sizga boshqaruv paneliga kirish uchun /admin URL’ini maxsus tarzda ishlatadi. Afsuski, bu Moodle administrator sahifalarining standart joylashuvi bilan to‘qnashadi. Buni tuzatish uchun o‘rnatishingizdagi admin papkasining nomini o‘zgartiring va yangi nomni bu yerga kiriting. Masalan: moodleadmin. Bu Moodle’dagi admin havolalarini tuzatadi.';
|
||||
$string['pathssubdataroot'] = '<p>Moodle foydalanuvchilar tomonidan yuklangan barcha fayl kontentini saqlaydigan papka.</p>
|
||||
<p>Ushbu papka veb-server foydalanuvchisi (odatda \'www-data\', \'nobody\' yoki \'apache\') tomonidan o‘qilishi va yozilishi mumkin bo‘lishi kerak.</p>
|
||||
<p>U to‘g‘ridan-to‘g‘ri veb orqali kirish mumkin bo‘lmasligi lozim.</p>
|
||||
<p>Agar papka hozir mavjud bo‘lmasa, o‘rnatish jarayoni uni yaratishga harakat qiladi.</p>';
|
||||
$string['pathssubdirroot'] = '<p>Moodle kodi joylashgan papkaning to‘liq yo‘li.</p>';
|
||||
$string['pathssubwwwroot'] = '<p>Moodle’ga kirish uchun to‘liq manzil, ya’ni foydalanuvchilar Moodle’ga kirish uchun brauzer manzil satriga kiritadigan manzil.</p>
|
||||
<p>Moodle’ga bir nechta manzil orqali kirish mumkin emas. Agar saytingiz bir nechta manzil orqali mavjud bo‘lsa, eng osonini tanlang va boshqa manzillar uchun doimiy yo‘naltirishni sozlang.</p>
|
||||
<p>Agar saytingiz Internetdan va ichki tarmoqdan (ba’zan Intranet deb ataladi) mavjud bo‘lsa, bu yerda jamoat manzilini ishlating.</p>
|
||||
<p>Agar hozirgi manzil noto‘g‘ri bo‘lsa, brauzeringiz manzil satrida URL’ni o‘zgartiring va o‘rnatishni qayta boshlang.</p>';
|
||||
$string['pathsunsecuredataroot'] = 'Dataroot joylashuvi xavfsiz emas.';
|
||||
$string['pathswrongadmindir'] = 'Administrator papkasi mavjud emas.';
|
||||
$string['phpextension'] = '{$a} PHP kengaytmasi';
|
||||
$string['phpversion'] = 'PHP versiyasi';
|
||||
$string['welcomep10'] = '{$a->installername} ({$a->installerversion})';
|
||||
$string['welcomep20'] = 'Siz bu sahifani ko‘rayotganingizning sababi, siz kompyuteringizda <strong>{$a->packname} {$a->packversion}</strong> paketini muvaffaqiyatli o‘rnatdingiz va ishga tushirdingiz. Tabriklaymiz!';
|
||||
$string['welcomep30'] = '<strong>{$a->installername}</strong> ning ushbu relizi <strong>Moodle</strong> ishlaydigan muhitni yaratish uchun ilovalarni o‘z ichiga oladi, ya’ni:';
|
||||
|
||||
+5
-7
@@ -825,16 +825,12 @@ $string['lockoutemailbody'] = 'Hi {$a->firstname},
|
||||
Your account with username {$a->username} on server \'{$a->sitename}\'
|
||||
was locked out after multiple invalid login attempts.
|
||||
|
||||
To unlock the account immediately go to the following address
|
||||
To unlock the account immediately, please click the link below:
|
||||
|
||||
{$a->link}
|
||||
<a href="{$a->link}">Unlock account</a>
|
||||
|
||||
In most mail programs, this should appear as a blue link
|
||||
which you can just click on. If that doesn\'t work,
|
||||
then copy and paste the address into the address
|
||||
line at the top of your web browser window.
|
||||
|
||||
If you need help, please contact the site administrator,
|
||||
If you need help, please contact the site administrator.
|
||||
{$a->admin}';
|
||||
$string['lockoutemailsubject'] = 'Your account on {$a} was locked out';
|
||||
$string['lockouterrorunlock'] = 'Invalid account unlock information supplied.';
|
||||
@@ -875,6 +871,7 @@ $string['manageformatsgotosettings'] = 'Default format can be changed in {$a}';
|
||||
$string['managelang'] = 'Manage';
|
||||
$string['manageqbehaviours'] = 'Manage question behaviours';
|
||||
$string['manageqtypes'] = 'Manage question types';
|
||||
$string['marketplaceavailablenotice'] = 'Browse and download plugins on <a href="{$a}" target="_blank" rel="noopener noreferrer">Moodle Marketplace <i class="fa fa-external-link" role="img" aria-label="Opens in new window" title="Opens in new window"></i></a>.';
|
||||
$string['maturity50'] = 'Alpha';
|
||||
$string['maturity100'] = 'Beta';
|
||||
$string['maturity150'] = 'Release candidate';
|
||||
@@ -972,6 +969,7 @@ $string['maxtimelimit'] = 'Maximum time limit';
|
||||
$string['maxtimelimit_desc'] = 'To restrict the maximum PHP execution time that Moodle will allow without any output being displayed, enter a value in seconds here. 0 means that Moodle default restrictions are used. If you have a front-end server with its own time limit, set this value lower to receive PHP errors in logs. Does not apply to CLI scripts.';
|
||||
$string['moodlebrandedapp'] = 'Branded Moodle app';
|
||||
$string['moodlebrandedappreference'] = 'Alternatively, get a <a href="https://moodle.com/branded-app/">Branded Moodle app</a> with your own custom branding.';
|
||||
$string['moodlenetremovalwarning'] = 'The MoodleNet service will be shut down on 20 April 2026. If you wish to continue using MoodleNet on your site, install the MoodleNet plugin from the <a href="https://github.com/moodlehq/moodle-tool_moodlenet" target="_blank">Moodle HQ GitHub repository <i class="fa fa-external-link" role="img" aria-label="Opens in new window" title="Opens in new window"></i></a> and connect it to a self-hosted MoodleNet instance. Following this, the MoodleNet profile ID field will be removed; please migrate that data if you are using it for other purposes.';
|
||||
$string['noreplyaddress'] = 'No-reply address';
|
||||
$string['noreplydomain'] = 'No-reply and domain';
|
||||
$string['noreplydomaindetail'] = 'Settings for No-reply and configured domains';
|
||||
|
||||
+7
-3
@@ -79,10 +79,14 @@ $string['emailnowexists'] = 'The email address you tried to assign to your profi
|
||||
$string['emailupdate'] = 'Email address update';
|
||||
$string['emailupdatemessage'] = 'Hi {$a->firstname},
|
||||
|
||||
You have requested a change of your email address for your account on {$a->site}. To confirm this change, please go to the following web address:
|
||||
You have requested a change of your email address for your account on {$a->site}.
|
||||
|
||||
{$a->url}
|
||||
The confirmation link will expire in <b>10 minutes</b>.
|
||||
To confirm this change, please click the link below:
|
||||
|
||||
<a href="{$a->url}">Confirm email change</a>
|
||||
|
||||
|
||||
The confirmation link will expire in <strong>10 minutes</strong>.
|
||||
|
||||
{$a->supportemail}';
|
||||
$string['emailupdatesuccess'] = 'Email address of user <em>{$a->fullname}</em> was successfully updated to <em>{$a->email}</em>.';
|
||||
|
||||
@@ -40,6 +40,10 @@ $string['activitydate:closed'] = 'Closed:';
|
||||
$string['activitydate:closes'] = 'Closes:';
|
||||
$string['activitydate:opened'] = 'Opened:';
|
||||
$string['activitydate:opens'] = 'Opens:';
|
||||
$string['activitynavigation'] = 'Activity navigation';
|
||||
$string['activitynavigation:jumptoactivity'] = 'Jump to activity';
|
||||
$string['activitynavigation:nexta'] = 'Next activity: {$a}';
|
||||
$string['activitynavigation:preva'] = 'Previous activity: {$a}';
|
||||
$string['aria:coursecategory'] = 'Course category';
|
||||
$string['aria:courseshortname'] = 'Course short name';
|
||||
$string['aria:coursename'] = 'Course name';
|
||||
|
||||
+21
-33
@@ -649,17 +649,12 @@ $string['emailagain'] = 'Email (again)';
|
||||
$string['emailconfirm'] = 'Confirm your account';
|
||||
$string['emailconfirmation'] = 'Hi {$a->firstname},
|
||||
|
||||
A new account has been requested at \'{$a->sitename}\'
|
||||
using your email address.
|
||||
A new account has been requested at \'{$a->sitename}\' using your email address.
|
||||
|
||||
To confirm your new account, please go to this web address:
|
||||
To confirm your new account, please click the link below:
|
||||
|
||||
{$a->link}
|
||||
<a href="{$a->link}">Confirm your account</a>
|
||||
|
||||
In most mail programs, this should appear as a blue link
|
||||
which you can just click on. If that doesn\'t work,
|
||||
then cut and paste the address into the address
|
||||
line at the top of your web browser window.
|
||||
|
||||
If you need help, please contact the site administrator,
|
||||
{$a->admin}';
|
||||
@@ -706,20 +701,14 @@ $string['emailnotfound'] = 'The email address was not found in the database';
|
||||
$string['emailonlyallowed'] = 'This email cannot be used. Allowed email domains are: {$a}.';
|
||||
$string['emailpasswordconfirmation'] = 'Hi {$a->firstname},
|
||||
|
||||
Someone (probably you) has requested a new password for your
|
||||
account on \'{$a->sitename}\'.
|
||||
Someone (probably you) has requested a new password for your account on \'{$a->sitename}\'.
|
||||
|
||||
To confirm this and have a new password sent to you via email,
|
||||
go to the following web address:
|
||||
To confirm this and have a new password sent to you via email, please click the link below:
|
||||
|
||||
{$a->link}
|
||||
<a href="{$a->link}">Get a new password</a>
|
||||
|
||||
In most mail programs, this should appear as a blue link
|
||||
which you can just click on. If that doesn\'t work,
|
||||
then cut and paste the address into the address
|
||||
line at the top of your web browser window.
|
||||
|
||||
If you need help, please contact the site administrator,
|
||||
If you need help, please contact the site administrator.
|
||||
{$a->admin}';
|
||||
$string['emailpasswordconfirmationsubject'] = '{$a}: Change password confirmation';
|
||||
$string['emailpasswordconfirmmaybesent'] = '<p>If you supplied a correct username or unique email address then an email should have been sent to you.</p>
|
||||
@@ -735,19 +724,14 @@ $string['emailpasswordconfirmsent'] = 'An email should have been sent to your ad
|
||||
If you continue to have difficulty, contact the site administrator.';
|
||||
$string['emailpasswordchangeinfo'] = 'Hi {$a->firstname},
|
||||
|
||||
Someone (probably you) has requested a new password for your
|
||||
account \'{$a->username}\' on \'{$a->sitename}\'.
|
||||
Someone (probably you) has requested a new password for your account \'{$a->username}\' on \'{$a->sitename}\'.
|
||||
|
||||
To change your password, please go to the following web address:
|
||||
To change your password, please click the link below:
|
||||
|
||||
{$a->link}
|
||||
<a href="{$a->link}">Change password</a>
|
||||
|
||||
In most mail programs, this should appear as a blue link
|
||||
which you can just click on. If that doesn\'t work,
|
||||
then cut and paste the address into the address
|
||||
line at the top of your web browser window.
|
||||
|
||||
If you need help, please contact the site administrator,
|
||||
If you need help, please contact the site administrator.
|
||||
{$a->admin}';
|
||||
$string['emailpasswordchangeinfodisabled'] = 'Hi {$a->firstname},
|
||||
|
||||
@@ -764,14 +748,17 @@ $string['emailresetconfirmation'] = 'Hi {$a->firstname},
|
||||
|
||||
A password reset was requested for your account \'{$a->username}\' at {$a->sitename}.
|
||||
|
||||
To confirm this request, and set a new password for your account, please go to the following web address:
|
||||
<a href="{$a->link}">{$a->link}</a>
|
||||
To confirm this request, and set a new password for your account, please click the link below:
|
||||
|
||||
<a href="{$a->link}">Reset password</a>
|
||||
|
||||
|
||||
(This link is valid for {$a->resetminutes} minutes from the time this reset was first requested.)
|
||||
|
||||
If this password reset was not requested by you, no action is needed.
|
||||
|
||||
If you need help, please contact the site administrator, {$a->admin}';
|
||||
If you need help, please contact the site administrator.
|
||||
{$a->admin}';
|
||||
$string['emailresetconfirmationsubject'] = '{$a}: Password reset request';
|
||||
$string['emailresetconfirmsent'] = 'An email has been sent to your address at <b>{$a}</b>.
|
||||
<br />It contains easy instructions to confirm and complete this password change.
|
||||
@@ -1245,6 +1232,7 @@ $string['loginstepsnone'] = '<p>Hi!</p>
|
||||
<p>If someone else has already chosen your username then you\'ll have to try again using a different username.</p>';
|
||||
$string['loginto'] = 'Log in to {$a}';
|
||||
$string['loginagain'] = 'Log in again';
|
||||
$string['loginrequired'] = 'Login required';
|
||||
$string['logoof'] = 'Logo of {$a}';
|
||||
$string['logout'] = 'Log out';
|
||||
$string['logoutconfirm'] = 'Do you really want to log out?';
|
||||
@@ -1512,10 +1500,10 @@ An account has been created for you at \'{$a->sitename}\'.
|
||||
|
||||
(You will be prompted to change your password when you log in for the first time.)
|
||||
|
||||
To start using \'{$a->sitename}\', log in at
|
||||
{$a->link}
|
||||
|
||||
If you need help, contact the site administrator,
|
||||
<a href="{$a->link}">Click to log in</a> and start using \'{$a->sitename}\'.
|
||||
|
||||
If you need help, contact the site administrator.
|
||||
{$a->signoff}';
|
||||
$string['newusers'] = 'New users';
|
||||
$string['newwindow'] = 'New window';
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# core (subsystem) Upgrade notes
|
||||
|
||||
## 4.5.10+
|
||||
|
||||
### Added
|
||||
|
||||
- When creating upgrade notes, the issue number will be inferred from the current Git branch name by default
|
||||
|
||||
For more information see [MDL-87100](https://tracker.moodle.org/browse/MDL-87100)
|
||||
- There is a new Behat `toast_message` named selector to more easily assert the presence of Toast messages on the page
|
||||
|
||||
For more information see [MDL-87443](https://tracker.moodle.org/browse/MDL-87443)
|
||||
- The `core/toast` JS module now accepts a `visuallyHidden` configuration parameter to render visually hidden toast messages for screen reader users.
|
||||
|
||||
For more information see [MDL-87993](https://tracker.moodle.org/browse/MDL-87993)
|
||||
|
||||
## 4.5.9
|
||||
|
||||
### Changed
|
||||
|
||||
- `\core\output\core_renderer::confirm()`'s `$displayoptions` parameter now also accepts a `headinglevel` option that developers can use to specify the heading level of the confirmation's heading. If not specified, the confirmation heading will be rendered in an `h4` tag.
|
||||
|
||||
For more information see [MDL-87694](https://tracker.moodle.org/browse/MDL-87694)
|
||||
|
||||
## 4.5.8
|
||||
|
||||
### Changed
|
||||
|
||||
+4
-4
@@ -10525,13 +10525,13 @@ class admin_setting_configcolourpicker extends admin_setting {
|
||||
return $data;
|
||||
} else if (in_array(strtolower($data), $colornames)) {
|
||||
return $data;
|
||||
} else if (preg_match('/rgb\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\)/i', $data)) {
|
||||
} else if (preg_match('/^rgb\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\)$/i', $data)) {
|
||||
return $data;
|
||||
} else if (preg_match('/rgba\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\, ?\d(\.\d)?\)/i', $data)) {
|
||||
} else if (preg_match('/^rgba\(\d{0,3}%?\, ?\d{0,3}%?, ?\d{0,3}%?\, ?\d(\.\d)?\)$/i', $data)) {
|
||||
return $data;
|
||||
} else if (preg_match('/hsl\(\d{0,3}\, ?\d{0,3}%, ?\d{0,3}%\)/i', $data)) {
|
||||
} else if (preg_match('/^hsl\(\d{0,3}\, ?\d{0,3}%, ?\d{0,3}%\)$/i', $data)) {
|
||||
return $data;
|
||||
} else if (preg_match('/hsla\(\d{0,3}\, ?\d{0,3}%,\d{0,3}%\, ?\d(\.\d)?\)/i', $data)) {
|
||||
} else if (preg_match('/^hsla\(\d{0,3}\, ?\d{0,3}%, ?\d{0,3}%\, ?\d(\.\d)?\)$/i', $data)) {
|
||||
return $data;
|
||||
} else if (($data == 'transparent') || ($data == 'currentColor') || ($data == 'inherit')) {
|
||||
return $data;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user