Compare commits
169 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1533fca4b5 | |||
| 7b4933db3a | |||
| 5208168e0c | |||
| ed4e707577 | |||
| 265ddbf30c | |||
| b036215acc | |||
| af8472a91f | |||
| 531bcb13f6 | |||
| 1d1f08a045 | |||
| f076ce2834 | |||
| 0bca129775 | |||
| 2759c1218d | |||
| ceb689b9fc | |||
| cd0c9ac87d | |||
| e24188c881 | |||
| 19f14de362 | |||
| cefa583e71 | |||
| c73acaa7ef | |||
| 8acf1ccd22 | |||
| 7062e7c6b3 | |||
| da737c7201 | |||
| f8e685af44 | |||
| 03b1f63d40 | |||
| 0db240c1dd | |||
| 90e815390a | |||
| 19168d0934 | |||
| 47d5c29202 | |||
| 4bd4d4eee8 | |||
| 02c256c941 | |||
| 3dd0ac559f | |||
| 4bb9e1ad8a | |||
| 31d0bf81af | |||
| 45ae1da263 | |||
| 3f08e9b98a | |||
| 541c5b8552 | |||
| c73f6d03e5 | |||
| 861c1dea98 | |||
| 1f7273af6a | |||
| 9fe1cb0421 | |||
| 77b0811021 | |||
| 6da79391ca | |||
| 6c57f48034 | |||
| 01451bbc5a | |||
| 5d2c5ba3e4 | |||
| 4456c306df | |||
| 99b9751f0d | |||
| 4d95d3f0e5 | |||
| 4a9a9bad73 | |||
| ca3e8022d1 | |||
| 36e323645a | |||
| 245f45f4d6 | |||
| 4cef723c22 | |||
| 561413695d | |||
| 217c3e0fc9 | |||
| 836c6d7883 | |||
| 728754de6b | |||
| 40a44cdd52 | |||
| 52617af3c9 | |||
| 7a89772cfb | |||
| 180fdb2f7c | |||
| 08dc6b095e | |||
| c47b6f97d7 | |||
| 80ad34e720 | |||
| f497fdcc21 | |||
| 0b4b0a7eea | |||
| e8b75e6292 | |||
| da6ecfd8c1 | |||
| 2cc040c6c0 | |||
| e88100f673 | |||
| 373748277a | |||
| 5294052875 | |||
| 28aadc2b12 | |||
| 2088c22e41 | |||
| 57ee89569a | |||
| d3069afd27 | |||
| deec056650 | |||
| cfca338d7a | |||
| 693ed49c10 | |||
| df8095156e | |||
| bb0ac1fdc2 | |||
| 117dc470bc | |||
| 9dfe870449 | |||
| 273b255673 | |||
| 97d2ea7f87 | |||
| be9b036a33 | |||
| e0b2ad1f35 | |||
| bbbdfc8610 | |||
| 821ab27c19 | |||
| 999aa8ca45 | |||
| d708bf59e4 | |||
| 4b274a10c2 | |||
| ee0546291a | |||
| 42b3a58c47 | |||
| 22d0a8f11e | |||
| 63a03a8b05 | |||
| 1a9d1593e4 | |||
| ca9daf0fb0 | |||
| 02315eb2bb | |||
| 1f0f002e1d | |||
| 7774cf96f8 | |||
| aa4a2716ef | |||
| 3adcb91d55 | |||
| ab0ad70a83 | |||
| 81e3251512 | |||
| ecbc2a2f25 | |||
| b5df2c1db0 | |||
| 37d0021369 | |||
| a72f3add06 | |||
| f8f477ece5 | |||
| 68881fb620 | |||
| 1c53bc813e | |||
| de342562cc | |||
| bb63c18443 | |||
| b2aaab60f3 | |||
| 413c321a18 | |||
| 2d391d1b10 | |||
| 9f555a7e77 | |||
| 494fec0071 | |||
| 527bde6e0f | |||
| d412f8552d | |||
| 6fb6859945 | |||
| af39ce0f8d | |||
| 7dea2c1707 | |||
| f45937e17d | |||
| e360ff5db6 | |||
| 61257ae417 | |||
| 101df9a634 | |||
| 68b35af353 | |||
| 7c549a17f4 | |||
| 39f360ae38 | |||
| 346ba4f443 | |||
| 10a8d040dc | |||
| 161ffc5605 | |||
| 53759a0c91 | |||
| f5e587c1db | |||
| 4727a4a207 | |||
| d891a6dcb7 | |||
| 70ae75674d | |||
| 42406bc413 | |||
| d3cd5f1f8f | |||
| 3ec49c6de3 | |||
| ff1543e0df | |||
| eb154d9c14 | |||
| f7d8c61ae7 | |||
| 0bc41cfcec | |||
| d304952b43 | |||
| adebc069bd | |||
| abfaf50217 | |||
| 81c471e2a9 | |||
| bdd60287e5 | |||
| 3365244fda | |||
| 37e51a5daa | |||
| 32f3de567f | |||
| fd424b99f2 | |||
| 5459e754c6 | |||
| abc2491211 | |||
| 3953d0b7a7 | |||
| 0fc2bccd60 | |||
| 3363cbe55b | |||
| 8b3090c13b | |||
| 3dbdeb5ccb | |||
| b395f7b599 | |||
| 1bc3f19a5b | |||
| 626d833543 | |||
| b71890687a | |||
| ca50279d4e | |||
| dbcade703b | |||
| cef93f97ae | |||
| 1dc462baa5 |
+2
-12
@@ -165,20 +165,10 @@ script:
|
||||
# The local_ci repository does the actual checking.
|
||||
git clone https://github.com/moodlehq/moodle-local_ci.git local/ci
|
||||
|
||||
# Determine the branch
|
||||
grep '^$release.*dev' version.php > /dev/null
|
||||
if [ $? -eq 0 ];
|
||||
then
|
||||
# This is master
|
||||
export branchname='master';
|
||||
else
|
||||
# This is a stable branch. Use the version from version.php to determine which one.
|
||||
export branchname="MOODLE_`grep '^$branch' version.php | sed "s/^.*'\([0-9]*\)'.*$/\1/"`_STABLE";
|
||||
fi
|
||||
|
||||
# We need the official upstream for comparison
|
||||
git remote add upstream https://github.com/moodle/moodle.git;
|
||||
git fetch upstream "$branchname";
|
||||
|
||||
git fetch upstream master;
|
||||
export GIT_PREVIOUS_COMMIT="`git merge-base FETCH_HEAD $TRAVIS_COMMIT`";
|
||||
export GIT_COMMIT="$TRAVIS_COMMIT";
|
||||
export UPSTREAM_FETCH_HEAD=`git rev-parse FETCH_HEAD`
|
||||
|
||||
@@ -39,6 +39,8 @@ require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/forms.php');
|
||||
require_once($CFG->dirroot . '/webservice/lib.php');
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/lib.php');
|
||||
|
||||
require_sesskey();
|
||||
|
||||
$huburl = required_param('huburl', PARAM_URL);
|
||||
$huburl = rtrim($huburl, "/");
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
$params = new stdClass();
|
||||
$params->bytes = $defaultuserquota;
|
||||
$params->displaysize = display_size($defaultuserquota);
|
||||
$temp->add(new admin_setting_configtext('userquota', new lang_string('userquota', 'admin'), new lang_string('configuserquota', 'admin', $params), $defaultuserquota));
|
||||
$temp->add(new admin_setting_configtext('userquota', new lang_string('userquota', 'admin'),
|
||||
new lang_string('configuserquota', 'admin', $params), $defaultuserquota, PARAM_INT, 30));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('allowobjectembed', new lang_string('allowobjectembed', 'admin'), new lang_string('configallowobjectembed', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configcheckbox('enabletrusttext', new lang_string('enabletrusttext', 'admin'), new lang_string('configenabletrusttext', 'admin'), 0));
|
||||
|
||||
@@ -11,7 +11,7 @@ $hassiteconfig = has_capability('moodle/site:config', $systemcontext);
|
||||
$ADMIN->add('root', new admin_externalpage('adminnotifications', new lang_string('notifications'), "$CFG->wwwroot/$CFG->admin/index.php"));
|
||||
|
||||
$ADMIN->add('root', new admin_externalpage('registrationmoodleorg', new lang_string('registration', 'admin'),
|
||||
"$CFG->wwwroot/$CFG->admin/registration/register.php?huburl=" . HUB_MOODLEORGHUBURL . "&hubname=Moodle.org"));
|
||||
"$CFG->wwwroot/$CFG->admin/registration/register.php?huburl=" . HUB_MOODLEORGHUBURL . "&hubname=Moodle.org&sesskey=" . sesskey()));
|
||||
$ADMIN->add('root', new admin_externalpage('registrationhub', new lang_string('registerwith', 'hub'),
|
||||
"$CFG->wwwroot/$CFG->admin/registration/register.php", 'moodle/site:config', true));
|
||||
$ADMIN->add('root', new admin_externalpage('registrationhubs', new lang_string('hubs', 'admin'),
|
||||
|
||||
@@ -253,7 +253,8 @@ class tool_generator_course_backend extends tool_generator_backend {
|
||||
$courserecord = array(
|
||||
'shortname' => $this->shortname,
|
||||
'fullname' => $this->fullname,
|
||||
'numsections' => self::$paramsections[$this->size]
|
||||
'numsections' => self::$paramsections[$this->size],
|
||||
'startdate' => usergetmidnight(time())
|
||||
);
|
||||
if (strlen($this->summary) > 0) {
|
||||
$courserecord['summary'] = $this->summary;
|
||||
|
||||
@@ -37,7 +37,7 @@ $string['installfromzip'] = 'Install plugin from ZIP file';
|
||||
$string['installfromzip_help'] = 'An alternative to installing a plugin directly from the Moodle plugins directory is to upload a ZIP package of the plugin. The ZIP package should have the same structure as a package downloaded from the Moodle plugins directory.';
|
||||
$string['installfromzipfile'] = 'ZIP package';
|
||||
$string['installfromzipfile_help'] = 'The plugin ZIP package must contain just one directory, named to match the plugin name. The ZIP will be extracted into an appropriate location for the plugin type. If the package has been downloaded from the Moodle plugins directory then it will have this structure.';
|
||||
$string['installfromzipinvalid'] = 'The plugin ZIP package must contain just one directory, named to match the plugin name. Provided file is not a valid plugin ZIP package.';
|
||||
$string['installfromzipinvalid'] = 'The plugin ZIP package must contain just one directory, named to match the plugin name. The file provided is not a valid plugin ZIP package.';
|
||||
$string['installfromziprootdir'] = 'Rename the root directory';
|
||||
$string['installfromziprootdir_help'] = 'Some ZIP packages, such as those generated by Github, may contain an incorrect root directory name. If so, the correct name may be entered here.';
|
||||
$string['installfromzipsubmit'] = 'Install plugin from the ZIP file';
|
||||
|
||||
@@ -39,12 +39,12 @@ $string['langpackupdatedevent'] = 'Language pack updated';
|
||||
$string['langupdatecomplete'] = 'Language pack update completed';
|
||||
$string['missingcfglangotherroot'] = 'Missing configuration value $CFG->langotherroot';
|
||||
$string['missinglangparent'] = 'Missing parent language <em>{$a->parent}</em> of <em>{$a->lang}</em>.';
|
||||
$string['noenglishuninstall'] = 'English language pack can not be uninstalled';
|
||||
$string['noenglishuninstall'] = 'The English language pack cannot be uninstalled.';
|
||||
$string['nolangupdateneeded'] = 'All your language packs are up to date, no update is needed';
|
||||
$string['pluginname'] = 'Language packs';
|
||||
$string['purgestringcaches'] = 'Purge string caches';
|
||||
$string['remotelangnotavailable'] = 'Because Moodle cannot connect to download.moodle.org, it is not possible for language packs to be installed automatically. Please download the appropriate ZIP file(s) from <a href="https://download.moodle.org/langpack/">download.moodle.org/langpack</a>, copy them to your {$a} directory and unzip them manually.';
|
||||
$string['selectlangs'] = 'Select languages to unistall!';
|
||||
$string['selectlangs'] = 'Select languages to uninstall';
|
||||
$string['uninstall'] = 'Uninstall selected language pack(s)';
|
||||
$string['uninstallconfirm'] = 'You are about to completely uninstall these language packs: <strong>{$a}</strong>. Are you sure?';
|
||||
$string['updatelangs'] = 'Update all installed language packs';
|
||||
|
||||
@@ -53,7 +53,7 @@ Feature: Manage language packs
|
||||
And I navigate to "Language packs" node in "Site administration > Language"
|
||||
When I set the field "Installed language packs" to "English (en)"
|
||||
And I press "Uninstall selected language pack(s)"
|
||||
Then I should see "English language pack can not be uninstalled"
|
||||
Then I should see "The English language pack cannot be uninstalled."
|
||||
And I navigate to "Live logs" node in "Site administration > Reports"
|
||||
And I should not see "Language pack uninstalled"
|
||||
And I log out
|
||||
|
||||
@@ -102,6 +102,10 @@ class manager {
|
||||
try {
|
||||
$this->client->login();
|
||||
mtrace("Connection established.");
|
||||
|
||||
// Ensure that mailboxes exist.
|
||||
$this->ensure_mailboxes_exist();
|
||||
|
||||
return true;
|
||||
|
||||
} catch (\Horde_Imap_Client_Exception $e) {
|
||||
@@ -741,6 +745,27 @@ class manager {
|
||||
return in_array($flag, $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that all mailboxes exist.
|
||||
*/
|
||||
private function ensure_mailboxes_exist() {
|
||||
$requiredmailboxes = array(
|
||||
self::MAILBOX,
|
||||
self::CONFIRMATIONFOLDER,
|
||||
);
|
||||
|
||||
$existingmailboxes = $this->client->listMailboxes($requiredmailboxes);
|
||||
foreach ($requiredmailboxes as $mailbox) {
|
||||
if (isset($existingmailboxes[$mailbox])) {
|
||||
// This mailbox was found.
|
||||
continue;
|
||||
}
|
||||
|
||||
mtrace("Unable to find the '{$mailbox}' mailbox - creating it.");
|
||||
$this->client->createMailbox($mailbox);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to determine whether this message is a bulk message (e.g. automated reply).
|
||||
*
|
||||
|
||||
@@ -20,10 +20,6 @@
|
||||
* This script is meant to be called from a cronjob to sync moodle with the CAS
|
||||
* backend in those setups where the CAS backend acts as 'master'.
|
||||
*
|
||||
* Sample cron entry:
|
||||
* # 5 minutes past 4am
|
||||
* 5 4 * * * $sudo -u www-data /usr/bin/php /var/www/moodle/auth/cas/cli/sync_users.php
|
||||
*
|
||||
* Notes:
|
||||
* - it is required to use the web server account when executing PHP CLI scripts
|
||||
* - you need to change the "www-data" to match the apache user account
|
||||
@@ -40,12 +36,15 @@
|
||||
* @package auth_cas
|
||||
* @copyright 2007 Jerome Gutierrez - based on code by Martin Langhoff
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @deprecated since Moodle 3.0 MDL-51824 - please do not use this CLI script any more, use scheduled task instead.
|
||||
* @todo MDL-50264 This will be deleted in Moodle 3.2.
|
||||
*/
|
||||
|
||||
define('CLI_SCRIPT', true);
|
||||
|
||||
require(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
|
||||
require_once($CFG->dirroot.'/course/lib.php');
|
||||
require_once($CFG->libdir.'/clilib.php');
|
||||
|
||||
// Ensure errors are well explained
|
||||
set_debugging(DEBUG_DEVELOPER, true);
|
||||
@@ -55,6 +54,14 @@ if (!is_enabled_auth('cas')) {
|
||||
die;
|
||||
}
|
||||
|
||||
cli_problem('[AUTH CAS] The sync users cron has been deprecated. Please use the scheduled task instead.');
|
||||
|
||||
// Abort execution of the CLI script if the auth_cas\task\sync_task is enabled.
|
||||
$task = \core\task\manager::get_scheduled_task('auth_cas\task\sync_task');
|
||||
if (!$task->get_disabled()) {
|
||||
cli_error('[AUTH CAS] The scheduled task sync_task is enabled, the cron execution has been aborted.');
|
||||
}
|
||||
|
||||
$casauth = get_auth_plugin('cas');
|
||||
$casauth->sync_users(true);
|
||||
|
||||
|
||||
@@ -20,10 +20,6 @@
|
||||
* This script is meant to be called from a cronjob to sync moodle with the LDAP
|
||||
* backend in those setups where the LDAP backend acts as 'master'.
|
||||
*
|
||||
* Sample cron entry:
|
||||
* # 5 minutes past 4am
|
||||
* 5 4 * * * $sudo -u www-data /usr/bin/php /var/www/moodle/auth/ldap/cli/sync_users.php
|
||||
*
|
||||
* Notes:
|
||||
* - it is required to use the web server account when executing PHP CLI scripts
|
||||
* - you need to change the "www-data" to match the apache user account
|
||||
@@ -44,12 +40,15 @@
|
||||
* @package auth_ldap
|
||||
* @copyright 2004 Martin Langhoff
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @deprecated since Moodle 3.0 MDL-51824 - please do not use this CLI script any more, use scheduled task instead.
|
||||
* @todo MDL-50264 This will be deleted in Moodle 3.2.
|
||||
*/
|
||||
|
||||
define('CLI_SCRIPT', true);
|
||||
|
||||
require(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php'); // global moodle config file.
|
||||
require_once($CFG->dirroot.'/course/lib.php');
|
||||
require_once($CFG->libdir.'/clilib.php');
|
||||
|
||||
// Ensure errors are well explained
|
||||
set_debugging(DEBUG_DEVELOPER, true);
|
||||
@@ -59,6 +58,14 @@ if (!is_enabled_auth('ldap')) {
|
||||
die;
|
||||
}
|
||||
|
||||
cli_problem('[AUTH LDAP] The users sync cron has been deprecated. Please use the scheduled task instead.');
|
||||
|
||||
// Abort execution of the CLI script if the auth_ldap\task\sync_task is enabled.
|
||||
$taskdisabled = \core\task\manager::get_scheduled_task('auth_ldap\task\sync_task');
|
||||
if (!$taskdisabled->get_disabled()) {
|
||||
cli_error('[AUTH LDAP] The scheduled task sync_task is enabled, the cron execution has been aborted.');
|
||||
}
|
||||
|
||||
$ldapauth = get_auth_plugin('ldap');
|
||||
$ldapauth->sync_users(true);
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ $string['auth_ldap_expiration_desc'] = 'Select No to disable expired password ch
|
||||
$string['auth_ldap_expiration_key'] = 'Expiration';
|
||||
$string['auth_ldap_expiration_warning_desc'] = 'Number of days before password expiration warning is issued.';
|
||||
$string['auth_ldap_expiration_warning_key'] = 'Expiration warning';
|
||||
$string['auth_ldap_expireattr_desc'] = 'Optional: overrides ldap-attribute that stores password expiration time';
|
||||
$string['auth_ldap_expireattr_desc'] = 'Optional: Overrides the LDAP attribute that stores password expiration time.';
|
||||
$string['auth_ldap_expireattr_key'] = 'Expiration attribute';
|
||||
$string['auth_ldapextrafields'] = 'These fields are optional. You can choose to pre-fill some Moodle user fields with information from the <b>LDAP fields</b> that you specify here. <p>If you leave these fields blank, then nothing will be transferred from LDAP and Moodle defaults will be used instead.</p><p>In either case, the user will be able to edit all of these fields after they log in.</p>';
|
||||
$string['auth_ldap_graceattr_desc'] = 'Optional: Overrides gracelogin attribute';
|
||||
@@ -71,7 +71,7 @@ $string['auth_ldap_noconnect_all'] = 'LDAP-module cannot connect to any servers:
|
||||
$string['auth_ldap_noextension'] = '<em>The PHP LDAP module does not seem to be present. Please ensure it is installed and enabled if you want to use this authentication plugin.</em>';
|
||||
$string['auth_ldap_no_mbstring'] = 'You need the mbstring extension to create users in Active Directory.';
|
||||
$string['auth_ldapnotinstalled'] = 'Cannot use LDAP authentication. The PHP LDAP module is not installed.';
|
||||
$string['auth_ldap_objectclass'] = 'Optional: Overrides objectClass used to name/search users on ldap_user_type. Usually you dont need to chage this.';
|
||||
$string['auth_ldap_objectclass'] = 'Optional: Overrides objectClass used to name/search users on ldap_user_type. Usually you don\'t need to change this.';
|
||||
$string['auth_ldap_objectclass_key'] = 'Object class';
|
||||
$string['auth_ldap_opt_deref'] = 'Determines how aliases are handled during search. Select one of the following values: "No" (LDAP_DEREF_NEVER) or "Yes" (LDAP_DEREF_ALWAYS)';
|
||||
$string['auth_ldap_opt_deref_key'] = 'Dereference aliases';
|
||||
|
||||
+3
-1
@@ -61,6 +61,8 @@ $PAGE->set_heading($badge->name);
|
||||
$PAGE->set_title($badge->name);
|
||||
$PAGE->navbar->add($badge->name);
|
||||
|
||||
require_capability('moodle/badges:viewbadges', $context);
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(print_badge_image($badge, $context, 'small') . ' ' . $badge->name);
|
||||
|
||||
@@ -75,4 +77,4 @@ echo $output->print_badge_status_box($badge);
|
||||
$output->print_badge_tabs($badgeid, $context, 'overview');
|
||||
echo $output->print_badge_overview($badge, $context);
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -75,6 +75,8 @@ if ($type == BADGE_TYPE_SITE) {
|
||||
$PAGE->set_heading($coursename);
|
||||
}
|
||||
|
||||
require_capability('moodle/badges:viewbadges', $PAGE->context);
|
||||
|
||||
$PAGE->set_title($title);
|
||||
$output = $PAGE->get_renderer('core', 'badges');
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ class block_activity_results extends block_base {
|
||||
return $result;
|
||||
}
|
||||
// See if it is a gradable activity.
|
||||
if (($rec->gradetype != GRADE_TYPE_VALUE) || ($rec->gradetype != GRADE_TYPE_SCALE)) {
|
||||
if (($rec->gradetype != GRADE_TYPE_VALUE) && ($rec->gradetype != GRADE_TYPE_SCALE)) {
|
||||
return $result;
|
||||
}
|
||||
return $rec;
|
||||
|
||||
@@ -28,31 +28,44 @@ Feature: The activity results block displays student scores
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
And I add a "Assignment" to section "1" and I fill the form with:
|
||||
| Assignment name | Test assignment |
|
||||
| Assignment name | Test assignment 1 |
|
||||
| Description | Offline text |
|
||||
| assignsubmission_file_enabled | 0 |
|
||||
And I follow "Course 1"
|
||||
And I add a "Assignment" to section "1" and I fill the form with:
|
||||
| Assignment name | Test assignment 2 |
|
||||
| Description | Offline text |
|
||||
| assignsubmission_file_enabled | 0 |
|
||||
And I follow "Course 1"
|
||||
And I add a "Assignment" to section "1" and I fill the form with:
|
||||
| Assignment name | Test assignment 3 |
|
||||
| Description | Offline text |
|
||||
| assignsubmission_file_enabled | 0 |
|
||||
And I follow "Course 1"
|
||||
And I add a "Page" to section "1"
|
||||
And I set the following fields to these values:
|
||||
| Name | Test page name |
|
||||
| Description | Test page description |
|
||||
| Page content | This is a page |
|
||||
And I press "Save and return to course"
|
||||
And I follow "Course 1"
|
||||
And I should see "Test page name"
|
||||
And I navigate to "Grades" node in "Course administration"
|
||||
And I turn editing mode on
|
||||
And I give the grade "90.00" to the user "Student 1" for the grade item "Test assignment"
|
||||
And I give the grade "80.00" to the user "Student 2" for the grade item "Test assignment"
|
||||
And I give the grade "70.00" to the user "Student 3" for the grade item "Test assignment"
|
||||
And I give the grade "60.00" to the user "Student 4" for the grade item "Test assignment"
|
||||
And I give the grade "50.00" to the user "Student 5" for the grade item "Test assignment"
|
||||
And I give the grade "90.00" to the user "Student 1" for the grade item "Test assignment 1"
|
||||
And I give the grade "80.00" to the user "Student 2" for the grade item "Test assignment 1"
|
||||
And I give the grade "70.00" to the user "Student 3" for the grade item "Test assignment 1"
|
||||
And I give the grade "60.00" to the user "Student 4" for the grade item "Test assignment 1"
|
||||
And I give the grade "50.00" to the user "Student 5" for the grade item "Test assignment 1"
|
||||
And I press "Save changes"
|
||||
And I follow "Course 1"
|
||||
|
||||
Scenario: Configure the block on a non-graded activity to show 3 high scores
|
||||
Given I add a "Page" to section "1"
|
||||
Given I follow "Test page name"
|
||||
And I add the "Activity results" block
|
||||
When I configure the "Activity results" block
|
||||
And I set the following fields to these values:
|
||||
| Name | Test page name |
|
||||
| Description | Test page description |
|
||||
| page | This is a page |
|
||||
And I press "Save and display"
|
||||
When I add the "Activity results" block
|
||||
And I configure the "Activity results" block
|
||||
And I set the following fields to these values:
|
||||
| id_config_activitygradeitemid | Test assignment |
|
||||
| id_config_activitygradeitemid | Test assignment 1 |
|
||||
| id_config_showbest | 3 |
|
||||
| id_config_showworst | 0 |
|
||||
| id_config_gradeformat | Absolute numbers |
|
||||
@@ -63,4 +76,25 @@ Scenario: Configure the block on a non-graded activity to show 3 high scores
|
||||
And I should see "Student 2" in the "Activity results" "block"
|
||||
And I should see "80.00" in the "Activity results" "block"
|
||||
And I should see "Student 3" in the "Activity results" "block"
|
||||
And I should see "70.00" in the "Activity results" "block"
|
||||
And I should see "70.00" in the "Activity results" "block"
|
||||
|
||||
Scenario: Block should select current activity by default
|
||||
Given I follow "Test assignment 1"
|
||||
When I add the "Activity results" block
|
||||
And I configure the "Activity results" block
|
||||
Then the field "id_config_activitygradeitemid" matches value "Test assignment 1"
|
||||
And I follow "Course 1"
|
||||
And I follow "Test assignment 2"
|
||||
And I add the "Activity results" block
|
||||
And I configure the "Activity results" block
|
||||
And the field "id_config_activitygradeitemid" matches value "Test assignment 2"
|
||||
And I follow "Course 1"
|
||||
And I follow "Test assignment 3"
|
||||
And I add the "Activity results" block
|
||||
And I configure the "Activity results" block
|
||||
And the field "id_config_activitygradeitemid" matches value "Test assignment 3"
|
||||
And I follow "Course 1"
|
||||
And I follow "Test page name"
|
||||
And I add the "Activity results" block
|
||||
And I configure the "Activity results" block
|
||||
And the field "id_config_activitygradeitemid" does not match value "Test page name"
|
||||
|
||||
@@ -23,6 +23,12 @@
|
||||
*/
|
||||
|
||||
class block_course_summary extends block_base {
|
||||
|
||||
/**
|
||||
* @var bool Flag to indicate whether the header should be hidden or not.
|
||||
*/
|
||||
private $headerhidden = true;
|
||||
|
||||
function init() {
|
||||
$this->title = get_string('pluginname', 'block_course_summary');
|
||||
}
|
||||
@@ -32,8 +38,10 @@ class block_course_summary extends block_base {
|
||||
}
|
||||
|
||||
function specialization() {
|
||||
if($this->page->pagetype == PAGE_COURSE_VIEW && $this->page->course->id != SITEID) {
|
||||
// Page type starts with 'course-view' and the page's course ID is not equal to the site ID.
|
||||
if (strpos($this->page->pagetype, PAGE_COURSE_VIEW) === 0 && $this->page->course->id != SITEID) {
|
||||
$this->title = get_string('coursesummary', 'block_course_summary');
|
||||
$this->headerhidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +79,7 @@ class block_course_summary extends block_base {
|
||||
}
|
||||
|
||||
function hide_header() {
|
||||
return true;
|
||||
return $this->headerhidden;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,19 +25,22 @@ Feature: Course summary block used in a course
|
||||
Scenario: Student can view course summary
|
||||
When I log in as "student1"
|
||||
And I follow "Course 1"
|
||||
Then "Course/site summary" "block" should exist
|
||||
And I should see "Proved the course summary block works!" in the "Course/site summary" "block"
|
||||
Then "Course summary" "block" should exist
|
||||
And I should see "Course summary" in the "Course summary" "block"
|
||||
And I should see "Proved the course summary block works!" in the "Course summary" "block"
|
||||
|
||||
Scenario: Teacher can see an edit icon when edit mode is on and follow it to the course edit page
|
||||
When I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
Then I should see "Proved the course summary block works!" in the "Course/site summary" "block"
|
||||
And I click on "Edit" "link" in the "Course/site summary" "block"
|
||||
Then I should see "Proved the course summary block works!" in the "Course summary" "block"
|
||||
And I should see "Course summary" in the "Course summary" "block"
|
||||
And I click on "Edit" "link" in the "Course summary" "block"
|
||||
Then I should see "Edit course settings" in the "h2" "css_element"
|
||||
|
||||
Scenario: Teacher can not see edit icon when edit mode is off
|
||||
When I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
Then I should see "Proved the course summary block works!" in the "Course/site summary" "block"
|
||||
And "Edit" "link" should not exist in the "Course/site summary" "block"
|
||||
Then I should see "Proved the course summary block works!" in the "Course summary" "block"
|
||||
And I should see "Course summary" in the "Course summary" "block"
|
||||
And "Edit" "link" should not exist in the "Course summary" "block"
|
||||
|
||||
@@ -17,6 +17,7 @@ Feature: Course summary block used on the frontpage
|
||||
Scenario: Guest can view site summary
|
||||
When I am on site homepage
|
||||
Then "Course/site summary" "block" should exist
|
||||
And I should not see "Course summary" in the "Course/site summary" "block"
|
||||
And I should see "Proved the summary block works!" in the "Course/site summary" "block"
|
||||
|
||||
Scenario: Admin can see an edit icon when edit mode is on and follow it to the front page settings
|
||||
@@ -24,6 +25,7 @@ Feature: Course summary block used on the frontpage
|
||||
And I am on site homepage
|
||||
And I follow "Turn editing on"
|
||||
Then I should see "Proved the summary block works!" in the "Course/site summary" "block"
|
||||
And I should not see "Course summary" in the "Course/site summary" "block"
|
||||
And I click on "Edit" "link" in the "Course/site summary" "block"
|
||||
Then I should see "Front page settings" in the "h2" "css_element"
|
||||
|
||||
@@ -31,4 +33,5 @@ Feature: Course summary block used on the frontpage
|
||||
When I log in as "admin"
|
||||
And I am on site homepage
|
||||
Then I should see "Proved the summary block works!" in the "Course/site summary" "block"
|
||||
And I should not see "Course summary" in the "Course/site summary" "block"
|
||||
And "Edit" "link" should not exist in the "Course/site summary" "block"
|
||||
|
||||
@@ -39,11 +39,11 @@ Feature: Expand the courses nodes within the navigation block
|
||||
And I log in as "admin"
|
||||
And I am on site homepage
|
||||
And I follow "Course 2"
|
||||
And I turn editing mode on
|
||||
And I click on "Edit settings" "link" in the "Administration" "block"
|
||||
And I navigate to "Enrolment methods" node in "Course administration > Users"
|
||||
And I click on "Edit" "link" in the "Guest access" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Allow guest access | Yes |
|
||||
And I press "Save and display"
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
|
||||
@javascript
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
*/
|
||||
|
||||
$string['anycategory'] = 'Any category';
|
||||
$string['apierror'] = 'The Youtube API key is not set. Contact your administrator.';
|
||||
$string['apierror'] = 'The YouTube API key is not set. Contact your administrator.';
|
||||
$string['apikey'] = 'API key';
|
||||
$string['apikeyinfo'] = 'Get a <a href="https://developers.google.com/youtube/v3/getting-started">Google API key</a> for your Moodle site.';
|
||||
$string['autosvehicles'] = 'Autos & Vehicles';
|
||||
@@ -42,7 +42,7 @@ $string['numberofvideos'] = 'Number of videos';
|
||||
$string['peopleblogs'] = 'People & Blogs';
|
||||
$string['petsanimals'] = 'Pets & Animals';
|
||||
$string['pluginname'] = 'YouTube';
|
||||
$string['requesterror'] = 'Data could not be obtained from the server. Contact your administrator if the problem persist.';
|
||||
$string['requesterror'] = 'Data could not be obtained from the server. Contact your administrator if the problem persists.';
|
||||
$string['scienceandtech'] = 'Science & Tech';
|
||||
$string['sports'] = 'Sports';
|
||||
$string['tag_youtube:addinstance'] = 'Add a new YouTube block';
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
require_once(dirname(dirname(__FILE__)).'/config.php');
|
||||
require_once('lib.php');
|
||||
require_once('locallib.php');
|
||||
require_once($CFG->dirroot .'/comment/lib.php');
|
||||
|
||||
$action = required_param('action', PARAM_ALPHA);
|
||||
$id = optional_param('entryid', 0, PARAM_INT);
|
||||
@@ -115,6 +116,9 @@ $output = $PAGE->get_renderer('blog');
|
||||
$strblogs = get_string('blogs', 'blog');
|
||||
|
||||
if ($action === 'delete') {
|
||||
// Init comment JS strings.
|
||||
comment::init();
|
||||
|
||||
if (empty($entry->id)) {
|
||||
print_error('wrongentryid', 'blog');
|
||||
}
|
||||
|
||||
@@ -232,6 +232,20 @@ abstract class completion_criteria extends data_object {
|
||||
*/
|
||||
abstract public function get_details($completion);
|
||||
|
||||
/**
|
||||
* Return pix_icon for display in reports.
|
||||
*
|
||||
* @param string $alt The alt text to use for the icon
|
||||
* @param array $attributes html attributes
|
||||
* @return pix_icon
|
||||
*/
|
||||
public function get_icon($alt, array $attributes = null) {
|
||||
global $COMPLETION_CRITERIA_TYPES;
|
||||
|
||||
$criteriatype = $COMPLETION_CRITERIA_TYPES[$this->criteriatype];
|
||||
return new pix_icon('i/'.$criteriatype, $alt, 'moodle', $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return criteria status text for display in reports
|
||||
*
|
||||
|
||||
@@ -288,4 +288,15 @@ class completion_criteria_activity extends completion_criteria {
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return pix_icon for display in reports.
|
||||
*
|
||||
* @param string $alt The alt text to use for the icon
|
||||
* @param array $attributes html attributes
|
||||
* @return pix_icon
|
||||
*/
|
||||
public function get_icon($alt, array $attributes = null) {
|
||||
return new pix_icon('icon', $alt, 'mod_'.$this->module, $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,4 +202,15 @@ class completion_criteria_date extends completion_criteria {
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return pix_icon for display in reports.
|
||||
*
|
||||
* @param string $alt The alt text to use for the icon
|
||||
* @param array $attributes html attributes
|
||||
* @return pix_icon
|
||||
*/
|
||||
public function get_icon($alt, array $attributes = null) {
|
||||
return new pix_icon('i/calendar', $alt, 'moodle', $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,4 +248,15 @@ class completion_criteria_grade extends completion_criteria {
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return pix_icon for display in reports.
|
||||
*
|
||||
* @param string $alt The alt text to use for the icon
|
||||
* @param array $attributes html attributes
|
||||
* @return pix_icon
|
||||
*/
|
||||
public function get_icon($alt, array $attributes = null) {
|
||||
return new pix_icon('i/grades', $alt, 'moodle', $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,4 +149,15 @@ class completion_criteria_self extends completion_criteria {
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return pix_icon for display in reports.
|
||||
*
|
||||
* @param string $alt The alt text to use for the icon
|
||||
* @param array $attributes html attributes
|
||||
* @return pix_icon
|
||||
*/
|
||||
public function get_icon($alt, array $attributes = null) {
|
||||
return new pix_icon('i/completion_self', $alt, 'moodle', $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,4 +131,15 @@ class completion_criteria_unenrol extends completion_criteria {
|
||||
$details['status'] = '';
|
||||
return $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return pix_icon for display in reports.
|
||||
*
|
||||
* @param string $alt The alt text to use for the icon
|
||||
* @param array $attributes html attributes
|
||||
* @return pix_icon
|
||||
*/
|
||||
public function get_icon($alt, array $attributes = null) {
|
||||
return new pix_icon('i/user', $alt, 'moodle', $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
$url = empty($value['url']) ? '' : $value['url'];
|
||||
$icon = empty($value['icon']) ? '' : $value['icon'];
|
||||
$name = empty($value['name']) ? '' : $value['name'];
|
||||
$attr = empty($value['attr']) ? '' : $value['attr'];
|
||||
$attr = empty($value['attr']) ? array() : $value['attr'];
|
||||
$class = empty($item['pixattr']['class']) ? '' : $item['pixattr']['class'];
|
||||
$alt = empty($item['pixattr']['alt']) ? '' : $item['pixattr']['alt'];
|
||||
$al = new action_menu_link_secondary(
|
||||
|
||||
+28
-6
@@ -378,7 +378,10 @@ class core_enrol_external extends external_api {
|
||||
)
|
||||
), 'Option names:
|
||||
* withcapability (string) return only users with this capability. This option requires \'moodle/role:review\' on the course context.
|
||||
* groupid (integer) return only users in this group id. This option requires \'moodle/site:accessallgroups\' on the course context.
|
||||
* groupid (integer) return only users in this group id. If the course has groups enabled and this param
|
||||
isn\'t defined, returns all the viewable users.
|
||||
This option requires \'moodle/site:accessallgroups\' on the course context if the
|
||||
user doesn\'t belong to the group.
|
||||
* onlyactive (integer) return only users with active enrolments and matching time restrictions. This option requires \'moodle/course:enrolreview\' on the course context.
|
||||
* userfields (\'string, string, ...\') return only the values of these user fields.
|
||||
* limitfrom (integer) sql limit from.
|
||||
@@ -466,7 +469,7 @@ class core_enrol_external extends external_api {
|
||||
require_capability('moodle/role:review', $coursecontext);
|
||||
}
|
||||
// need accessallgroups capability if you want to overwrite this option
|
||||
if (!empty($groupid) && groups_is_member($groupid)) {
|
||||
if (!empty($groupid) && !groups_is_member($groupid)) {
|
||||
require_capability('moodle/site:accessallgroups', $coursecontext);
|
||||
}
|
||||
// to overwrite this option, you need course:enrolereview permission
|
||||
@@ -478,10 +481,29 @@ class core_enrol_external extends external_api {
|
||||
$ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
|
||||
$ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel)";
|
||||
$enrolledparams['contextlevel'] = CONTEXT_USER;
|
||||
$sql = "SELECT u.* $ctxselect
|
||||
FROM {user} u $ctxjoin
|
||||
WHERE u.id IN ($enrolledsql)
|
||||
ORDER BY u.id ASC";
|
||||
|
||||
$groupjoin = '';
|
||||
if (empty($groupid) && groups_get_course_groupmode($course) == SEPARATEGROUPS &&
|
||||
!has_capability('moodle/site:accessallgroups', $coursecontext)) {
|
||||
// Filter by groups the user can view.
|
||||
$usergroups = groups_get_user_groups($course->id);
|
||||
if (!empty($usergroups['0'])) {
|
||||
list($groupsql, $groupparams) = $DB->get_in_or_equal($usergroups['0'], SQL_PARAMS_NAMED);
|
||||
$groupjoin = "JOIN {groups_members} gm ON (u.id = gm.userid AND gm.groupid $groupsql)";
|
||||
$enrolledparams = array_merge($enrolledparams, $groupparams);
|
||||
} else {
|
||||
// User doesn't belong to any group, so he can't see any user. Return an empty array.
|
||||
return array();
|
||||
}
|
||||
}
|
||||
$sql = "SELECT us.*
|
||||
FROM {user} us
|
||||
JOIN (
|
||||
SELECT DISTINCT u.id $ctxselect
|
||||
FROM {user} u $ctxjoin $groupjoin
|
||||
WHERE u.id IN ($enrolledsql)
|
||||
) q ON q.id = us.id
|
||||
ORDER BY us.id ASC";
|
||||
$enrolledusers = $DB->get_recordset_sql($sql, $enrolledparams, $limitfrom, $limitnumber);
|
||||
$users = array();
|
||||
foreach ($enrolledusers as $user) {
|
||||
|
||||
+5
-1
@@ -172,7 +172,11 @@ class course_enrolment_manager {
|
||||
FROM {user} u
|
||||
JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid $instancessql)
|
||||
JOIN {enrol} e ON (e.id = ue.enrolid)
|
||||
LEFT JOIN {groups_members} gm ON u.id = gm.userid
|
||||
LEFT JOIN {groups_members} gm ON u.id = gm.userid AND gm.groupid IN (
|
||||
SELECT g.id
|
||||
FROM {groups} g
|
||||
WHERE g.courseid = e.courseid
|
||||
)
|
||||
WHERE $filtersql";
|
||||
$this->totalusers = (int)$DB->count_records_sql($sqltotal, $params);
|
||||
}
|
||||
|
||||
@@ -101,9 +101,9 @@ $string['sendcoursewelcomemessage'] = 'Send course welcome message';
|
||||
$string['sendcoursewelcomemessage_help'] = 'If enabled, users receive a welcome message via email when they self-enrol in a course.';
|
||||
$string['showhint'] = 'Show hint';
|
||||
$string['showhint_desc'] = 'Show first letter of the guest access key.';
|
||||
$string['status'] = 'Enable existing enrolments';
|
||||
$string['status'] = 'Allow existing enrolments';
|
||||
$string['status_desc'] = 'Enable self enrolment method in new courses.';
|
||||
$string['status_help'] = 'If disabled all existing self enrolments are suspended and new users can not enrol.';
|
||||
$string['status_help'] = 'If enabled together with \'Allow new enrolments\' disabled, only users who self enrolled previously can access the course. If disabled, this self enrolment method is effectively disabled, since all existing self enrolments are suspended and new users cannot self enrol.';
|
||||
$string['unenrol'] = 'Unenrol user';
|
||||
$string['unenrolselfconfirm'] = 'Do you really want to unenrol yourself from course "{$a}"?';
|
||||
$string['unenroluser'] = 'Do you really want to unenrol "{$a->user}" from course "{$a->course}"?';
|
||||
|
||||
@@ -27,7 +27,7 @@ Feature: Users can auto-enrol themself in courses where self enrolment is allowe
|
||||
And I log out
|
||||
When I follow "Course 1"
|
||||
And I press "Log in as a guest"
|
||||
Then I should see "Guests can not access this course, please try to log in."
|
||||
Then I should see "Guests cannot access this course. Please log in."
|
||||
And I press "Continue"
|
||||
And I should see "Log in"
|
||||
|
||||
|
||||
@@ -50,11 +50,12 @@ Feature: Enrolled users can be filtered by group
|
||||
And I should see "<expected3>"
|
||||
And I should not see "<notexpected1>"
|
||||
And I should not see "<notexpected2>"
|
||||
And I should see "<expected4>"
|
||||
|
||||
# Note the 'XX-IGNORE-XX' elements are for when there is less than 2 'not expected' items.
|
||||
Examples:
|
||||
| group | expected1 | expected2 | expected3 | notexpected1 | notexpected2 |
|
||||
| All participants | Student 1 | Student 2 | Student 3 | XX-IGNORE-XX | XX-IGNORE-XX |
|
||||
| No group | Student 1 | | | Student 2 | Student 3 |
|
||||
| Group 1 | Student 2 | | | Student 1 | Student 3 |
|
||||
| Group 2 | Student 2 | Student 3 | | Student 1 | XX-IGNORE-XX |
|
||||
| group | expected1 | expected2 | expected3 | expected4 | notexpected1 | notexpected2 |
|
||||
| All participants | Student 1 | Student 2 | Student 3 | 4 enrolled users | XX-IGNORE-XX | XX-IGNORE-XX |
|
||||
| No group | Student 1 | | | 2 enrolled users | Student 2 | Student 3 |
|
||||
| Group 1 | Student 2 | | | 1 enrolled users | Student 1 | Student 3 |
|
||||
| Group 2 | Student 2 | Student 3 | | 2 enrolled users | Student 1 | XX-IGNORE-XX |
|
||||
|
||||
+490
-129
@@ -33,59 +33,325 @@ require_once($CFG->dirroot . '/enrol/externallib.php');
|
||||
class core_enrol_externallib_testcase extends externallib_advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test get_enrolled_users
|
||||
* dataProvider for test_get_enrolled_users_visibility().
|
||||
*/
|
||||
public function test_get_enrolled_users() {
|
||||
public function get_enrolled_users_visibility_provider() {
|
||||
return array(
|
||||
'Course without groups, default behavior (not filtering by cap, group, active)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => NOGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => null,
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array(),
|
||||
),
|
||||
'results' => array( // Everybody can view everybody.
|
||||
'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with visible groups, default behavior (not filtering by cap, group, active)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => VISIBLEGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => null,
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array(),
|
||||
),
|
||||
'results' => array( // Everybody can view everybody.
|
||||
'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, default behavior (not filtering by cap, group, active)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => null,
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array(),
|
||||
),
|
||||
'results' => array( // Only users from own groups are visible.
|
||||
'user0' => array('canview' => array()), // Poor guy, cannot see anybody, himself included.
|
||||
'user1' => array('canview' => array('user1', 'userall')),
|
||||
'user2' => array('canview' => array('user2', 'user2su', 'userall')),
|
||||
'user31' => array('canview' => array('user31', 'user32', 'userall')),
|
||||
'userall' => array('canview' => array('user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, default behavior (not filtering but having moodle/site:accessallgroups)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => VISIBLEGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => null,
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array('moodle/site:accessallgroups'),
|
||||
),
|
||||
'results' => array( // Everybody can view everybody.
|
||||
'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, filtering onlyactive (missing moodle/course:enrolreview)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => null,
|
||||
'onlyactive' => true,
|
||||
'allowedcaps' => array(),
|
||||
),
|
||||
'results' => array( // returns exception, cannot view anybody without the cap.
|
||||
'user2' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Review course enrolments')),
|
||||
'userall' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Review course enrolments')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, filtering onlyactive (having moodle/course:enrolreview)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => null,
|
||||
'onlyactive' => true,
|
||||
'allowedcaps' => array('moodle/course:enrolreview'),
|
||||
),
|
||||
'results' => array( // Suspended are not returned.
|
||||
'user2' => array('canview' => array('user2', 'userall')),
|
||||
'user31' => array('canview' => array('user31', 'user32', 'userall')),
|
||||
'userall' => array('canview' => array('user1', 'user2', 'user31', 'user32', 'userall')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, filtering by groupid (not having moodle/site:accessallgroups)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => 'group2',
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array(),
|
||||
),
|
||||
'results' => array( // Only group 2 members and only for members. Exception for non-members.
|
||||
'user0' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Access all groups')),
|
||||
'user1' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Access all groups')),
|
||||
'user2' => array('canview' => array('user2', 'user2su', 'userall')),
|
||||
'userall' => array('canview' => array('user2', 'user2su', 'userall')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, filtering by groupid (having moodle/site:accessallgroups)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => null,
|
||||
'groupid' => 'group2',
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array('moodle/site:accessallgroups'),
|
||||
),
|
||||
'results' => array( // All users with 'moodle/site:accessallgroups' can view group 2
|
||||
'user0' => array('canview' => array('user2', 'user2su', 'userall')),
|
||||
'user1' => array('canview' => array('user2', 'user2su', 'userall')),
|
||||
'user2' => array('canview' => array('user2', 'user2su', 'userall')),
|
||||
'userall' => array('canview' => array('user2', 'user2su', 'userall')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, filtering by withcapability (not having moodle/role:review)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => 'moodle/course:bulkmessaging',
|
||||
'groupid' => null,
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array(),
|
||||
),
|
||||
'results' => array( // No user has 'moodle/role:review' so exception.
|
||||
'user0' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Review permissions for others')),
|
||||
'user1' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Review permissions for others')),
|
||||
'user2' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Review permissions for others')),
|
||||
'userall' => array('exception' => array(
|
||||
'type' => 'required_capability_exception',
|
||||
'message' => 'Review permissions for others')),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, filtering by withcapability (having moodle/role:review)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => 'moodle/course:bulkmessaging',
|
||||
'groupid' => null,
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array('moodle/role:review'),
|
||||
),
|
||||
'results' => array( // No user has withcapability, but all have 'moodle/role:review'. Empties.
|
||||
'user0' => array('canview' => array()),
|
||||
'user1' => array('canview' => array()),
|
||||
'user2' => array('canview' => array()),
|
||||
'userall' => array('canview' => array()),
|
||||
),
|
||||
),
|
||||
|
||||
'Course with separate groups, filtering by withcapability (having moodle/role:review)' =>
|
||||
array(
|
||||
'settings' => array(
|
||||
'coursegroupmode' => SEPARATEGROUPS,
|
||||
'withcapability' => 'moodle/course:bulkmessaging',
|
||||
'groupid' => null,
|
||||
'onlyactive' => false,
|
||||
'allowedcaps' => array('moodle/role:review', 'moodle/course:bulkmessaging'),
|
||||
),
|
||||
'results' => array( // Users (previous) have withcapability, and all have 'moodle/role:review'.
|
||||
'user0' => array('canview' => array()),
|
||||
'user1' => array('canview' => array('user1')),
|
||||
'user2' => array('canview' => array('user2')),
|
||||
'userall' => array('canview' => array('user1', 'user2', 'userall')),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify get_enrolled_users() returned users are the expected in every situation.
|
||||
*
|
||||
* @dataProvider get_enrolled_users_visibility_provider
|
||||
*/
|
||||
public function test_get_enrolled_users_visibility($settings, $results) {
|
||||
|
||||
global $USER;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = self::getDataGenerator()->create_course();
|
||||
$user1 = self::getDataGenerator()->create_user();
|
||||
$user2 = self::getDataGenerator()->create_user();
|
||||
$user3 = self::getDataGenerator()->create_user();
|
||||
$this->setUser($user3);
|
||||
// Create the course and the users.
|
||||
$course = $this->getDataGenerator()->create_course(array('groupmode' => $settings['coursegroupmode']));
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
$user0 = $this->getDataGenerator()->create_user(array('username' => 'user0')); // A user without group.
|
||||
$user1 = $this->getDataGenerator()->create_user(array('username' => 'user1')); // User for group 1.
|
||||
$user2 = $this->getDataGenerator()->create_user(array('username' => 'user2')); // Two users for group 2.
|
||||
$user2su = $this->getDataGenerator()->create_user(array('username' => 'user2su')); // (one suspended).
|
||||
$user31 = $this->getDataGenerator()->create_user(array('username' => 'user31')); // Two users for group 3.
|
||||
$user32 = $this->getDataGenerator()->create_user(array('username' => 'user32')); // (both enabled).
|
||||
$userall = $this->getDataGenerator()->create_user(array('username' => 'userall')); // A user in all groups.
|
||||
|
||||
// Set the required capabilities by the external function.
|
||||
$context = context_course::instance($course->id);
|
||||
$roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id);
|
||||
$this->assignUserCapability('moodle/user:viewdetails', $context->id, $roleid);
|
||||
// Create utility array of created users, to produce better assertion messages.
|
||||
$createdusers = array();
|
||||
foreach (array($user0, $user1, $user2, $user2su, $user31, $user32, $userall) as $createduser) {
|
||||
$createdusers[$createduser->id] = $createduser->username;
|
||||
}
|
||||
|
||||
// Enrol the users in the course.
|
||||
$this->getDataGenerator()->enrol_user($user1->id, $course->id, $roleid, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($user2->id, $course->id, $roleid, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($user3->id, $course->id, $roleid, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($user0->id, $course->id);
|
||||
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
|
||||
$this->getDataGenerator()->enrol_user($user2->id, $course->id);
|
||||
$this->getDataGenerator()->enrol_user($user2su->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
|
||||
$this->getDataGenerator()->enrol_user($user31->id, $course->id);
|
||||
$this->getDataGenerator()->enrol_user($user32->id, $course->id);
|
||||
$this->getDataGenerator()->enrol_user($userall->id, $course->id);
|
||||
|
||||
// Call the external function.
|
||||
$enrolledusers = core_enrol_external::get_enrolled_users($course->id);
|
||||
// Create 3 groups.
|
||||
$group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
|
||||
$group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
|
||||
$group3 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
|
||||
// Add the users to the groups.
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user2su->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user31->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user32->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $userall->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $userall->id));
|
||||
$this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $userall->id));
|
||||
|
||||
// Check the result set.
|
||||
$this->assertEquals(3, count($enrolledusers));
|
||||
$this->assertArrayHasKey('email', $enrolledusers[0]);
|
||||
// Create a role to add the allowedcaps. Users will have this role assigned.
|
||||
$roleid = $this->getDataGenerator()->create_role();
|
||||
// Allow the specified capabilities.
|
||||
if (!empty($settings['allowedcaps'])) {
|
||||
foreach ($settings['allowedcaps'] as $capability) {
|
||||
assign_capability($capability, CAP_ALLOW, $roleid, $coursecontext);
|
||||
}
|
||||
}
|
||||
|
||||
// Call the function with some parameters set.
|
||||
$enrolledusers = core_enrol_external::get_enrolled_users($course->id, array(
|
||||
array('name' => 'limitfrom', 'value' => 2),
|
||||
array('name' => 'limitnumber', 'value' => 1),
|
||||
array('name' => 'userfields', 'value' => 'id')
|
||||
));
|
||||
// For each of the users, configure everything, perform the call, and assert results.
|
||||
foreach ($results as $user => $expectations) {
|
||||
// Convert canview expectations into a nice array of ids for easier handling.
|
||||
$canview = array();
|
||||
$exception = null;
|
||||
// Analyse the expectations.
|
||||
if (isset($expectations['canview'])) {
|
||||
foreach ($expectations['canview'] as $canviewuser) {
|
||||
$canview[] = $createdusers[${$canviewuser}->id];
|
||||
}
|
||||
} else if (isset($expectations['exception'])) {
|
||||
$exception = $expectations['exception'];
|
||||
$this->setExpectedException($exception['type'], $exception['message']);
|
||||
} else {
|
||||
// Failed, only canview and exception are supported.
|
||||
$this->markTestIncomplete('Incomplete, only canview and exception are supported');
|
||||
}
|
||||
// Switch to the user and assign the role.
|
||||
$this->setUser(${$user});
|
||||
role_assign($roleid, $USER->id, $coursecontext);
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
|
||||
// Convert groupid to proper id.
|
||||
$groupid = 0;
|
||||
if (isset($settings['groupid'])) {
|
||||
$groupid = ${$settings['groupid']}->id;
|
||||
}
|
||||
|
||||
// Check the result set, we should only get the 3rd result, which is $user3.
|
||||
$this->assertCount(1, $enrolledusers);
|
||||
$this->assertEquals($user3->id, $enrolledusers[0]['id']);
|
||||
$this->assertArrayHasKey('id', $enrolledusers[0]);
|
||||
$this->assertArrayNotHasKey('email', $enrolledusers[0]);
|
||||
// Call to the function.
|
||||
$options = array(
|
||||
array('name' => 'withcapability', 'value' => $settings['withcapability']),
|
||||
array('name' => 'groupid', 'value' => $groupid),
|
||||
array('name' => 'onlyactive', 'value' => $settings['onlyactive']),
|
||||
array('name' => 'userfields', 'value' => 'id')
|
||||
);
|
||||
$enrolledusers = core_enrol_external::get_enrolled_users($course->id, $options);
|
||||
|
||||
// Call without required capability.
|
||||
$this->unassignUserCapability('moodle/course:viewparticipants', $context->id, $roleid);
|
||||
$this->setExpectedException('moodle_exception');
|
||||
$categories = core_enrol_external::get_enrolled_users($course->id);
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
|
||||
|
||||
// We are only interested in ids to check visibility.
|
||||
$viewed = array();
|
||||
// Verify the user canview the expected users.
|
||||
foreach ($enrolledusers as $enrolleduser) {
|
||||
$viewed[] = $createdusers[$enrolleduser['id']];
|
||||
}
|
||||
// Verify viewed matches canview expectation (using canonicalize to ignore ordering).
|
||||
$this->assertEquals($canview, $viewed, "Problem checking visible users for '{$createdusers[$USER->id]}'", 0, 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,96 +410,6 @@ class core_enrol_externallib_testcase extends externallib_advanced_testcase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_enrolled_users_with_capability
|
||||
*/
|
||||
public function test_get_enrolled_users_with_capability () {
|
||||
global $DB, $USER;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
|
||||
$coursedata['idnumber'] = 'idnumbercourse1';
|
||||
$coursedata['fullname'] = 'Lightwork Course 1';
|
||||
$coursedata['summary'] = 'Lightwork Course 1 description';
|
||||
$coursedata['summaryformat'] = FORMAT_MOODLE;
|
||||
$course1 = self::getDataGenerator()->create_course($coursedata);
|
||||
|
||||
// Create a manual enrolment record.
|
||||
$manual_enrol_data['enrol'] = 'manual';
|
||||
$manual_enrol_data['status'] = 0;
|
||||
$manual_enrol_data['courseid'] = $course1->id;
|
||||
$enrolid = $DB->insert_record('enrol', $manual_enrol_data);
|
||||
|
||||
// Create the users and give them capabilities in the course context.
|
||||
$context = context_course::instance($course1->id);
|
||||
$roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);
|
||||
|
||||
// Create 2 students.
|
||||
$student1 = self::getDataGenerator()->create_user();
|
||||
$student2 = self::getDataGenerator()->create_user();
|
||||
|
||||
// Give the capability to student2.
|
||||
assign_capability('moodle/course:viewparticipants', CAP_ALLOW, 3, $context->id);
|
||||
role_assign(3, $student2->id, $context->id);
|
||||
accesslib_clear_all_caches_for_unit_testing();
|
||||
|
||||
// Enrol both the user and the students in the course.
|
||||
$user_enrolment_data['status'] = 0;
|
||||
$user_enrolment_data['enrolid'] = $enrolid;
|
||||
$user_enrolment_data['userid'] = $USER->id;
|
||||
$DB->insert_record('user_enrolments', $user_enrolment_data);
|
||||
|
||||
$user_enrolment_data['status'] = 0;
|
||||
$user_enrolment_data['enrolid'] = $enrolid;
|
||||
$user_enrolment_data['userid'] = $student1->id;
|
||||
$DB->insert_record('user_enrolments', $user_enrolment_data);
|
||||
|
||||
$user_enrolment_data['status'] = 0;
|
||||
$user_enrolment_data['enrolid'] = $enrolid;
|
||||
$user_enrolment_data['userid'] = $student2->id;
|
||||
$DB->insert_record('user_enrolments', $user_enrolment_data);
|
||||
|
||||
$params = array("coursecapabilities" => array('courseid' => $course1->id,
|
||||
'capabilities' => array('moodle/course:viewparticipants')));
|
||||
$options = array();
|
||||
$result = core_enrol_external::get_enrolled_users_with_capability($params, $options);
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
|
||||
|
||||
// Check an array containing the expected user for the course capability is returned.
|
||||
$expecteduserlist = $result[0];
|
||||
$this->assertEquals($course1->id, $expecteduserlist['courseid']);
|
||||
$this->assertEquals('moodle/course:viewparticipants', $expecteduserlist['capability']);
|
||||
$this->assertEquals(2, count($expecteduserlist['users']));
|
||||
|
||||
// Now doing the query again with options.
|
||||
$params = array(
|
||||
"coursecapabilities" => array(
|
||||
'courseid' => $course1->id,
|
||||
'capabilities' => array('moodle/course:viewparticipants')
|
||||
)
|
||||
);
|
||||
$options = array(
|
||||
array('name' => 'limitfrom', 'value' => 1),
|
||||
array('name' => 'limitnumber', 'value' => 1),
|
||||
array('name' => 'userfields', 'value' => 'id')
|
||||
);
|
||||
|
||||
$result = core_enrol_external::get_enrolled_users_with_capability($params, $options);
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
|
||||
|
||||
// Check an array containing the expected user for the course capability is returned.
|
||||
$expecteduserlist = $result[0]['users'];
|
||||
$expecteduser = reset($expecteduserlist);
|
||||
$this->assertEquals(1, count($expecteduserlist));
|
||||
$this->assertEquals($student2->id, $expecteduser['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_course_enrolment_methods
|
||||
*/
|
||||
@@ -299,4 +475,189 @@ class core_enrol_externallib_testcase extends externallib_advanced_testcase {
|
||||
$this->assertTrue($enrolmentmethod['status']);
|
||||
$this->assertEquals('enrol_self_get_instance_info', $enrolmentmethod['wsfunction']);
|
||||
}
|
||||
|
||||
public function get_enrolled_users_setup($capability) {
|
||||
global $USER;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$return = new stdClass();
|
||||
|
||||
$return->course = self::getDataGenerator()->create_course();
|
||||
$return->user1 = self::getDataGenerator()->create_user();
|
||||
$return->user2 = self::getDataGenerator()->create_user();
|
||||
$return->user3 = self::getDataGenerator()->create_user();
|
||||
$this->setUser($return->user3);
|
||||
|
||||
// Set the required capabilities by the external function.
|
||||
$return->context = context_course::instance($return->course->id);
|
||||
$return->roleid = $this->assignUserCapability($capability, $return->context->id);
|
||||
$this->assignUserCapability('moodle/user:viewdetails', $return->context->id, $return->roleid);
|
||||
|
||||
// Enrol the users in the course.
|
||||
$this->getDataGenerator()->enrol_user($return->user1->id, $return->course->id, $return->roleid, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($return->user2->id, $return->course->id, $return->roleid, 'manual');
|
||||
$this->getDataGenerator()->enrol_user($return->user3->id, $return->course->id, $return->roleid, 'manual');
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_enrolled_users from core_enrol_external without additional
|
||||
* parameters.
|
||||
*/
|
||||
public function test_get_enrolled_users_without_parameters() {
|
||||
$capability = 'moodle/course:viewparticipants';
|
||||
$data = $this->get_enrolled_users_setup($capability);
|
||||
|
||||
// Call the external function.
|
||||
$enrolledusers = core_enrol_external::get_enrolled_users($data->course->id);
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
|
||||
|
||||
// Check the result set.
|
||||
$this->assertEquals(3, count($enrolledusers));
|
||||
$this->assertArrayHasKey('email', $enrolledusers[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_enrolled_users from core_enrol_external with some parameters set.
|
||||
*/
|
||||
public function test_get_enrolled_users_with_parameters() {
|
||||
$capability = 'moodle/course:viewparticipants';
|
||||
$data = $this->get_enrolled_users_setup($capability);
|
||||
|
||||
// Call the function with some parameters set.
|
||||
$enrolledusers = core_enrol_external::get_enrolled_users($data->course->id, array(
|
||||
array('name' => 'limitfrom', 'value' => 2),
|
||||
array('name' => 'limitnumber', 'value' => 1),
|
||||
array('name' => 'userfields', 'value' => 'id')
|
||||
));
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$enrolledusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $enrolledusers);
|
||||
|
||||
// Check the result set, we should only get the 3rd result, which is $user3.
|
||||
$this->assertCount(1, $enrolledusers);
|
||||
$this->assertEquals($data->user3->id, $enrolledusers[0]['id']);
|
||||
$this->assertArrayHasKey('id', $enrolledusers[0]);
|
||||
$this->assertArrayNotHasKey('email', $enrolledusers[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_enrolled_users from core_enrol_external with capability to
|
||||
* viewparticipants removed.
|
||||
*/
|
||||
public function test_get_enrolled_users_without_capability() {
|
||||
$capability = 'moodle/course:viewparticipants';
|
||||
$data = $this->get_enrolled_users_setup($capability);
|
||||
|
||||
// Call without required capability.
|
||||
$this->unassignUserCapability($capability, $data->context->id, $data->roleid);
|
||||
$this->setExpectedException('moodle_exception');
|
||||
$categories = core_enrol_external::get_enrolled_users($data->course->id);
|
||||
}
|
||||
|
||||
public function get_enrolled_users_with_capability_setup($capability) {
|
||||
global $USER, $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$return = new stdClass();
|
||||
|
||||
// Create the course and fetch its context.
|
||||
$return->course = self::getDataGenerator()->create_course();
|
||||
$context = context_course::instance($return->course->id);
|
||||
|
||||
// Create one teacher, and two students.
|
||||
$return->teacher = self::getDataGenerator()->create_user();
|
||||
$return->student1 = self::getDataGenerator()->create_user();
|
||||
$return->student2 = self::getDataGenerator()->create_user();
|
||||
|
||||
// Create a new student role based on the student archetype but with the capability prohibitted.
|
||||
$fakestudentroleid = create_role('Fake student role', 'fakestudent', 'Fake student role', 'student');
|
||||
assign_capability($capability, CAP_PROHIBIT, $fakestudentroleid, $context->id);
|
||||
|
||||
// Enrol all of the users in the course.
|
||||
// * 'teacher' is an editing teacher.
|
||||
// * 'student1' is a standard student.
|
||||
// * 'student2' is a student with the capability prohibitted.
|
||||
$editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
|
||||
$studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
|
||||
$this->getDataGenerator()->enrol_user($return->teacher->id, $return->course->id, $editingteacherroleid);
|
||||
$this->getDataGenerator()->enrol_user($return->student1->id, $return->course->id, $studentroleid);
|
||||
$this->getDataGenerator()->enrol_user($return->student2->id, $return->course->id, $fakestudentroleid);
|
||||
|
||||
// Log in as the teacher.
|
||||
$this->setUser($return->teacher);
|
||||
|
||||
// Clear caches.
|
||||
accesslib_clear_all_caches_for_unit_testing();
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_enrolled_users_with_capability without additional paramaters.
|
||||
*/
|
||||
public function test_get_enrolled_users_with_capability_without_parameters() {
|
||||
$capability = 'moodle/course:viewparticipants';
|
||||
$data = $this->get_enrolled_users_with_capability_setup($capability);
|
||||
|
||||
$result = core_enrol_external::get_enrolled_users_with_capability(
|
||||
array(
|
||||
'coursecapabilities' => array(
|
||||
'courseid' => $data->course->id,
|
||||
'capabilities' => array(
|
||||
$capability,
|
||||
),
|
||||
),
|
||||
),
|
||||
array()
|
||||
);
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
|
||||
|
||||
// Check an array containing the expected user for the course capability is returned.
|
||||
$expecteduserlist = $result[0];
|
||||
$this->assertEquals($data->course->id, $expecteduserlist['courseid']);
|
||||
$this->assertEquals($capability, $expecteduserlist['capability']);
|
||||
$this->assertEquals(2, count($expecteduserlist['users']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_enrolled_users_with_capability
|
||||
*/
|
||||
public function test_get_enrolled_users_with_capability_with_parameters () {
|
||||
$capability = 'moodle/course:viewparticipants';
|
||||
$data = $this->get_enrolled_users_with_capability_setup($capability);
|
||||
|
||||
$result = core_enrol_external::get_enrolled_users_with_capability(
|
||||
array(
|
||||
'coursecapabilities' => array(
|
||||
'courseid' => $data->course->id,
|
||||
'capabilities' => array(
|
||||
$capability,
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
array('name' => 'limitfrom', 'value' => 1),
|
||||
array('name' => 'limitnumber', 'value' => 1),
|
||||
array('name' => 'userfields', 'value' => 'id')
|
||||
)
|
||||
);
|
||||
|
||||
// We need to execute the return values cleaning process to simulate the web service server.
|
||||
$result = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_with_capability_returns(), $result);
|
||||
|
||||
// Check an array containing the expected user for the course capability is returned.
|
||||
$expecteduserlist = $result[0]['users'];
|
||||
$expecteduser = reset($expecteduserlist);
|
||||
$this->assertEquals(1, count($expecteduserlist));
|
||||
$this->assertEquals($data->student1->id, $expecteduser['id']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ information provided here is intended especially for developers.
|
||||
enrol_instance_deleted . Always trigger them when changing records in the
|
||||
DB table 'enrol'.
|
||||
* Constant CACHE_COURSE_CONTACTS_TTL was deleted.
|
||||
* External function core_enrol_external::get_enrolled_users now returns only the viewable participants if the course has groups enabled and no groupid is passed.
|
||||
|
||||
=== 2.9 ===
|
||||
|
||||
|
||||
@@ -3142,9 +3142,6 @@ abstract class grade_helper {
|
||||
JOIN {user_info_category} c ON f.categoryid=c.id
|
||||
WHERE f.shortname $wherefields
|
||||
ORDER BY c.sortorder ASC, f.sortorder ASC", $whereparams);
|
||||
if (!is_array($customfields)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($customfields as $field) {
|
||||
// Make sure we can display this custom field
|
||||
|
||||
@@ -1109,6 +1109,14 @@ class grade_report_grader extends grade_report {
|
||||
}
|
||||
}
|
||||
|
||||
// Enable keyboard navigation if the grade is editable (not locked, not in a unoverridable category, etc).
|
||||
if ($enableajax && $grade->is_editable()) {
|
||||
// If a grade item is type text, and we don't have show quick feedback on, it can't be edited.
|
||||
if ($item->gradetype != GRADE_TYPE_TEXT || $showquickfeedback) {
|
||||
$itemcell->attributes['class'] .= ' gbnavigable';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->gradeserror[$item->id][$userid])) {
|
||||
$itemcell->text .= $this->gradeserror[$item->id][$userid];
|
||||
}
|
||||
|
||||
@@ -251,6 +251,7 @@ M.gradereport_grader.classes.ajax.prototype.keypress_enter = function(e) {
|
||||
* @param {Bool} ignoreshift If true and shift is pressed then don't exec
|
||||
*/
|
||||
M.gradereport_grader.classes.ajax.prototype.keypress_tab = function(e, ignoreshift) {
|
||||
e.preventDefault();
|
||||
var next = null;
|
||||
if (e.shiftKey) {
|
||||
if (ignoreshift) {
|
||||
@@ -328,8 +329,8 @@ M.gradereport_grader.classes.ajax.prototype.get_next_cell = function(cell) {
|
||||
if (!next) {
|
||||
return this.current.node;
|
||||
}
|
||||
// Continue on until we find a clickable cell
|
||||
if (!next.hasClass('clickable')) {
|
||||
// Continue on until we find a navigable cell
|
||||
if (!next.hasClass('gbnavigable')) {
|
||||
return this.get_next_cell(next);
|
||||
}
|
||||
return next;
|
||||
@@ -352,8 +353,8 @@ M.gradereport_grader.classes.ajax.prototype.get_prev_cell = function(cell) {
|
||||
if (!next) {
|
||||
return this.current.node;
|
||||
}
|
||||
// Continue on until we find a clickable cell
|
||||
if (!next.hasClass('clickable')) {
|
||||
// Continue on until we find a navigable cell
|
||||
if (!next.hasClass('gbnavigable')) {
|
||||
return this.get_prev_cell(next);
|
||||
}
|
||||
return next;
|
||||
@@ -380,8 +381,8 @@ M.gradereport_grader.classes.ajax.prototype.get_above_cell = function(cell) {
|
||||
if (!next) {
|
||||
return this.current.node;
|
||||
}
|
||||
// Continue on until we find a clickable cell
|
||||
if (!next.hasClass('clickable')) {
|
||||
// Continue on until we find a navigable cell
|
||||
if (!next.hasClass('gbnavigable')) {
|
||||
return this.get_above_cell(next);
|
||||
}
|
||||
return next;
|
||||
@@ -408,8 +409,8 @@ M.gradereport_grader.classes.ajax.prototype.get_below_cell = function(cell) {
|
||||
if (!next) {
|
||||
return this.current.node;
|
||||
}
|
||||
// Continue on until we find a clickable cell
|
||||
if (!next.hasClass('clickable')) {
|
||||
// Continue on until we find a navigable cell
|
||||
if (!next.hasClass('gbnavigable')) {
|
||||
return this.get_below_cell(next);
|
||||
}
|
||||
return next;
|
||||
@@ -700,13 +701,13 @@ M.gradereport_grader.classes.existingfield = function(ajax, userid, itemid) {
|
||||
}
|
||||
} else if (this.grade) {
|
||||
// Handle Tab and Shift+Tab.
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'press:9', this));
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'down:9', this));
|
||||
}
|
||||
if (this.grade) {
|
||||
// Handle the Enter key being pressed.
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.grade, 'press:13', this));
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.grade, 'up:13', this));
|
||||
// Handle CTRL + arrow keys.
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.grade, 'press:37,38,39,40+ctrl', this));
|
||||
this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.grade, 'down:37,38,39,40+ctrl', this));
|
||||
}
|
||||
};
|
||||
/**
|
||||
@@ -759,6 +760,7 @@ M.gradereport_grader.classes.existingfield.prototype.keypress_tab = function(e,
|
||||
* @param {Event} e
|
||||
*/
|
||||
M.gradereport_grader.classes.existingfield.prototype.keypress_arrows = function(e) {
|
||||
e.preventDefault();
|
||||
var next = null;
|
||||
switch (e.keyCode) {
|
||||
case 37: // Left
|
||||
@@ -785,6 +787,7 @@ M.gradereport_grader.classes.existingfield.prototype.keypress_arrows = function(
|
||||
M.gradereport_grader.classes.existingfield.prototype.move_focus = function(node) {
|
||||
if (node) {
|
||||
var properties = this.report.get_cell_info(node);
|
||||
this.report.ajax.current = node;
|
||||
switch(properties.itemtype) {
|
||||
case 'scale':
|
||||
properties.cell.one('select.select').focus();
|
||||
@@ -1078,16 +1081,16 @@ M.gradereport_grader.classes.textfield.prototype.attach_key_events = function()
|
||||
if (this.editfeedback) {
|
||||
if (this.grade) {
|
||||
// Handle Shift+Tab.
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'press:9+shift', a));
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'down:9+shift', a));
|
||||
}
|
||||
// Handle Tab.
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.feedback, 'press:9', a, true));
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.feedback, 'down:9', a, true));
|
||||
// Handle the Enter key being pressed.
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.feedback, 'press:13', a));
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.feedback, 'up:13', a));
|
||||
} else {
|
||||
if (this.grade) {
|
||||
// Handle Tab and Shift+Tab.
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'press:9', a));
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'down:9', a));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1097,7 +1100,7 @@ M.gradereport_grader.classes.textfield.prototype.attach_key_events = function()
|
||||
|
||||
if (this.grade) {
|
||||
// Handle the Enter key being pressed.
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.grade, 'press:13', a));
|
||||
this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.grade, 'up:13', a));
|
||||
// Prevent the default key action on all fields for arrow keys on all key events!
|
||||
// Note: this still does not work in FF!!!!!
|
||||
this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'down:37,38,39,40+ctrl'));
|
||||
|
||||
@@ -333,19 +333,13 @@ class grade extends tablelike implements selectable_items, filterable_items {
|
||||
|
||||
$data->$field = empty($grade) ? $null : $grade->finalgrade;
|
||||
$data->{"old$field"} = $data->$field;
|
||||
|
||||
preg_match('/_(\d+)_(\d+)/', $field, $oldoverride);
|
||||
$oldoverride = 'oldoverride' . $oldoverride[0];
|
||||
if (empty($data->$oldoverride)) {
|
||||
$data->$field = (!isset($grade->rawgrade)) ? $null : $grade->rawgrade;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data as $varname => $value) {
|
||||
if (preg_match('/^oldoverride_(\d+)_(\d+)/', $varname, $matches)) {
|
||||
// If we've selected override or overriding all grades.
|
||||
if (!empty($data->$matches[0]) || $filter == 'all') {
|
||||
// If we've selected overriding all grades.
|
||||
if ($filter == 'all') {
|
||||
$override = "override_{$matches[1]}_{$matches[2]}";
|
||||
$data->$override = '1';
|
||||
}
|
||||
|
||||
@@ -336,29 +336,32 @@ class user extends tablelike implements selectable_items {
|
||||
}
|
||||
|
||||
$oldfinalgradefield = "oldfinalgrade_{$gradeitem->id}_{$this->itemid}";
|
||||
if (!empty($data->$oldfinalgradefield)) {
|
||||
// Bulk grade changes for all grades need to be processed and shouldn't be skipped if they had a previous grade.
|
||||
if ($gradeitem->is_course_item() || ($filter != 'all' && !empty($data->$oldfinalgradefield))) {
|
||||
if ($gradeitem->is_course_item()) {
|
||||
// The course total should not be overridden.
|
||||
unset($data->$field);
|
||||
unset($data->oldfinalgradefield);
|
||||
$oldoverride = "oldoverride_{$gradeitem->id}_{$this->itemid}";
|
||||
unset($data->$oldoverride);
|
||||
$oldfeedback = "oldfeedback_{$gradeitem->id}_{$this->itemid}";
|
||||
unset($data->$oldfeedback);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$grade = grade_grade::fetch(array(
|
||||
'itemid' => $this->itemid,
|
||||
'itemid' => $gradeitemid,
|
||||
'userid' => $userid
|
||||
));
|
||||
|
||||
$data->$field = empty($grade) ? $null : $grade->finalgrade;
|
||||
$data->{"old$field"} = $data->$field;
|
||||
|
||||
preg_match('/_(\d+)_(\d+)/', $field, $oldoverride);
|
||||
$oldoverride = 'oldoverride' . $oldoverride[0];
|
||||
if (empty($data->$oldoverride)) {
|
||||
$data->$field = (!isset($grade->rawgrade)) ? $null : $grade->rawgrade;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foreach ($data as $varname => $value) {
|
||||
if (preg_match('/^oldoverride_(\d+)_(\d+)/', $varname, $matches)) {
|
||||
// If we've selected override or overriding all grades.
|
||||
if (!empty($data->$matches[0]) || $filter == 'all') {
|
||||
// If we've selected overriding all grades.
|
||||
if ($filter == 'all') {
|
||||
$override = "override_{$matches[1]}_{$matches[2]}";
|
||||
$data->$override = '1';
|
||||
}
|
||||
|
||||
@@ -47,14 +47,7 @@ class finalgrade extends grade_attribute_format implements unique_value, be_disa
|
||||
public function get_value() {
|
||||
$this->label = $this->grade->grade_item->itemname;
|
||||
|
||||
$isoverridden = $this->grade->is_overridden();
|
||||
// If the grade is overridden or the grade type is not an activity then use finalgrade.
|
||||
if (!empty($isoverridden) || $this->grade->grade_item->itemtype != 'mod') {
|
||||
$val = $this->grade->finalgrade;
|
||||
} else {
|
||||
$val = $this->grade->rawgrade;
|
||||
}
|
||||
|
||||
$val = $this->grade->finalgrade;
|
||||
if ($this->grade->grade_item->scaleid) {
|
||||
return $val ? (int)$val : -1;
|
||||
} else {
|
||||
|
||||
@@ -105,7 +105,7 @@ Feature: We can use Single view
|
||||
And I set the field "Insert value" to "1.0"
|
||||
And I click on "Perform bulk insert" "checkbox"
|
||||
And I press "Save"
|
||||
Then I should see "Grades were set for 9 items"
|
||||
Then I should see "Grades were set for 8 items"
|
||||
|
||||
Scenario: Navigation works in the Single view.
|
||||
Given I follow "Single view for Student 1"
|
||||
|
||||
@@ -90,5 +90,5 @@ $string['welcomep50'] = 'L\'ús de totes les aplicacions d\'aquest paquet és go
|
||||
<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['welcomep70'] = 'Feu clic en el botó "Següent" per continuar la configuració de <strong>Moodle</strong>.';
|
||||
$string['welcomep70'] = 'Feu clic al botó «Següent» per continuar amb la configuració de <strong>Moodle</strong>.';
|
||||
$string['wwwroot'] = 'Adreça web';
|
||||
|
||||
@@ -42,3 +42,4 @@ Použijte volbu --help pro získání nápovědy';
|
||||
$string['cliyesnoprompt'] = 'zadejte a (pro "ano") nebo n (pro "ne")';
|
||||
$string['environmentrequireinstall'] = 'musí být nainstalováno a povoleno';
|
||||
$string['environmentrequireversion'] = 'vyžadována je verze {$a->needed}, nyní používáte verzi {$a->current}';
|
||||
$string['upgradekeyset'] = 'Klíč pro upgrade (ponechte prázdné, nechcete-li nenastavil)';
|
||||
|
||||
@@ -42,3 +42,4 @@ $string['cliunknowoption'] = 'Opzioni non riconosciute:
|
||||
$string['cliyesnoprompt'] = 'inserisci s (per Si) o n (per No)';
|
||||
$string['environmentrequireinstall'] = 'E\' necessaria l\'installazione e attivazione';
|
||||
$string['environmentrequireversion'] = 'Versione minima necessaria: {$a->needed}. Versione in uso: {$a->current}';
|
||||
$string['upgradekeyset'] = 'Chiave per l\'aggiornamento (lasciare vuoto se non impostata)';
|
||||
|
||||
@@ -42,3 +42,4 @@ Vennligst bruk Hjelp.';
|
||||
$string['cliyesnoprompt'] = 'Tast j (betyr JA) eller n (betyr NEI)';
|
||||
$string['environmentrequireinstall'] = 'er påkrevd at du installerer/tillater';
|
||||
$string['environmentrequireversion'] = 'Du må ha versjon {$a->needed}. Du kjører {$a->current}';
|
||||
$string['upgradekeyset'] = 'Oppgraderingsnøkkel (la være tom for å kreve noen)';
|
||||
|
||||
@@ -27,7 +27,7 @@ $string['addrestriction'] = 'Add restriction...';
|
||||
$string['and'] = 'and';
|
||||
$string['condition_group'] = 'Restriction set';
|
||||
$string['condition_group_info'] = 'Add a set of nested restrictions to apply complex logic.';
|
||||
$string['enableavailability'] = 'Enable conditional access';
|
||||
$string['enableavailability'] = 'Enable restricted access';
|
||||
$string['enableavailability_desc'] = 'When enabled, this lets you set conditions (based on date, grade, or completion) that control whether an activity or resource can be accessed.';
|
||||
$string['error_list_nochildren'] = 'Restriction sets should contain at least one condition.';
|
||||
$string['hidden_marker'] = '(hidden otherwise)';
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@ $string['badgeprivacysetting_help'] = 'Badges you earn can be displayed on your
|
||||
|
||||
You can still control individual badge privacy settings on your badges page.';
|
||||
$string['badgeprivacysetting_str'] = 'Automatically show badges I earn on my profile page';
|
||||
$string['badgesalt'] = 'Salt for hashing the recepient\'s email address';
|
||||
$string['badgesalt'] = 'Salt for hashing the recipient\'s email address';
|
||||
$string['badgesalt_desc'] = 'Using a hash allows backpack services to confirm the badge earner without having to expose their email address. This setting should only use numbers and letters.
|
||||
|
||||
Note: For recipient verification purposes, please avoid changing this setting once you start issuing badges.';
|
||||
|
||||
@@ -41,7 +41,7 @@ $string['completed'] = 'Completed';
|
||||
$string['completedunlocked'] = 'Completion options unlocked';
|
||||
$string['completedunlockedtext'] = 'When you save changes, completion state for all students will be erased. If you change your mind about this, do not save the form.';
|
||||
$string['completedwarning'] = 'Completion options locked';
|
||||
$string['completedwarningtext'] = 'One or more students ({$a}) has already marked this activity as completed. Changing completion options will erase their completion state and may cause confusion. Thus the options have been locked and should not be unlocked unless absolutely necessary.';
|
||||
$string['completedwarningtext'] = 'This activity has already been marked as completed for {$a} participant(s). Changing completion options will erase their completion state and may cause confusion. Thus the options have been locked and should not be unlocked unless absolutely necessary.';
|
||||
$string['completion'] = 'Completion tracking';
|
||||
$string['completion-alt-auto-enabled'] = 'The system marks this item complete according to conditions: {$a}';
|
||||
$string['completion-alt-auto-fail'] = 'Completed: {$a} (did not achieve pass grade)';
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ $string['migratetomanual'] = 'Migrate to manual enrolments';
|
||||
$string['nochange'] = 'No change';
|
||||
$string['noexistingparticipants'] = 'No existing participants';
|
||||
$string['nogroup'] = 'No group';
|
||||
$string['noguestaccess'] = 'Guests can not access this course, please try to log in.';
|
||||
$string['noguestaccess'] = 'Guests cannot access this course. Please log in.';
|
||||
$string['none'] = 'None';
|
||||
$string['notenrollable'] = 'You can not enrol yourself in this course.';
|
||||
$string['notenrolledusers'] = 'Other users';
|
||||
|
||||
+2
-2
@@ -304,7 +304,7 @@ $string['invalidcourselevel'] = 'Incorrect context level';
|
||||
$string['invalidcoursemodule'] = 'Invalid course module ID';
|
||||
$string['invalidcoursenameshort'] = 'Invalid short course name';
|
||||
$string['invaliddata'] = 'Data submitted is invalid';
|
||||
$string['invaliddatarootpermissions'] = 'Invalid permissions detected in $CFG->dataroot directory, administrator has to fix permissions.';
|
||||
$string['invaliddatarootpermissions'] = 'Invalid permissions detected when trying to create a directory. Turn debugging on for further details.';
|
||||
$string['invaliddevicetype'] = 'Invalid device type';
|
||||
$string['invalidelementid'] = 'Incorrect element id!';
|
||||
$string['invalidentry'] = 'This is not valid entry!';
|
||||
@@ -388,7 +388,7 @@ $string['movecatcontentstoroot'] = 'Moving the category content to root is not a
|
||||
$string['movecategorynotpossible'] = 'You cannot move category \'{$a}\' into the selected category.';
|
||||
$string['movecategoryownparent'] = 'You cannot make category \'{$a}\' a parent of itself.';
|
||||
$string['movecategoryparentconflict'] = 'You cannot make category \'{$a}\' a subcategory of one of its own subcategories.';
|
||||
$string['mssqlrcsmodemissing'] = 'The database is not using the expected READ_COMMITTED_SNAPSHOT mode which can lead to wrong results, especially under high concurrency scenarios. Please enable it for correct behaviour. You can find more information in the <a href="https://docs.moodle.org/en/Installing_MSSQL_for_PHP#Configuration">Moodle Docs</a>.';
|
||||
$string['mssqlrcsmodemissing'] = 'The database is not using the expected READ_COMMITTED_SNAPSHOT mode which can lead to wrong results, especially under high concurrency scenarios. Please enable it for correct behaviour. For further information, see the documentation <a href="https://docs.moodle.org/en/Installing_MSSQL_for_PHP">Installing MSSQL for PHP</a>.';
|
||||
$string['multiplerecordsfound'] = 'Multiple records found, only one record expected.';
|
||||
$string['multiplerestorenotallow'] = 'Multiple restore execution not allowed!';
|
||||
$string['mustbeloggedin'] = 'You must be logged in to do this';
|
||||
|
||||
@@ -50,6 +50,7 @@ $string['mustbeoverriden'] = 'Abstract form_definition() method in class {$a} mu
|
||||
$string['nomethodforaddinghelpbutton'] = 'There is no method for adding a help button to form element {$a->name} (class {$a->classname})';
|
||||
$string['nonexistentformelements'] = 'Trying to add help buttons to non-existent form elements : {$a}';
|
||||
$string['noselection'] = 'No selection';
|
||||
$string['nosuggestions'] = 'No suggestions';
|
||||
$string['optional'] = 'Optional';
|
||||
$string['othersettings'] = 'Other settings';
|
||||
$string['requiredelement'] = 'Required field';
|
||||
|
||||
+3
-1
@@ -240,7 +240,9 @@ $string['gradeanalysis'] = 'Grade analysis';
|
||||
$string['gradebook'] = 'Gradebook';
|
||||
$string['gradebookcalculationsuptodate'] = 'The calculations in the gradebook are up to date. You may need to reload this page to see changes.';
|
||||
$string['gradebookcalculationsfixbutton'] = 'Accept grade changes and fix calculation errors';
|
||||
$string['gradebookcalculationswarning'] = 'Note: Some errors have been detected in calculating the grades displayed in the gradebook. It is recommended that the errors are fixed by clicking the button below, though this will result in some grades being changed. For details, see the changes between versions {$a->gradebookversion} and {$a->currentversion} in <a href="{$a->url}">Gradebook calculation changes</a>.';
|
||||
$string['gradebookcalculationswarning'] = 'Note: Some errors have been detected in calculating the grades displayed in the gradebook. If your course has not started or is in progress, it is recommended that the errors are fixed by clicking the button below, though this will result in some grades being changed. If your course has already ended and grades have been submitted, you probably do not want to fix this issue.
|
||||
|
||||
The newer version is {$a->currentversion}; you are using gradebook version {$a->gradebookversion}. You can see a list of changes in <a href="{$a->url}">Gradebook calculation changes</a>.';
|
||||
$string['gradebookhiddenerror'] = 'The gradebook is currently set to hide everything from students.';
|
||||
$string['gradebookhistories'] = 'Grade histories';
|
||||
$string['gradebooksetup'] = 'Gradebook setup';
|
||||
|
||||
@@ -143,6 +143,7 @@ $string['thisconversation'] = 'this conversation';
|
||||
$string['timenosee'] = 'Minutes since I was last seen online';
|
||||
$string['timesent'] = 'Time sent';
|
||||
$string['touserdoesntexist'] = 'You can not send a message to a user id ({$a}) that doesn\'t exist';
|
||||
$string['unabletomessageuser'] = 'You are not permitted to send a message to that user';
|
||||
$string['unblockcontact'] = 'Unblock contact';
|
||||
$string['unreadmessages'] = 'Unread messages ({$a})';
|
||||
$string['unreadnewmessages'] = 'New messages ({$a})';
|
||||
|
||||
+1
-1
@@ -1002,7 +1002,7 @@ $string['institution'] = 'Institution';
|
||||
$string['instudentview'] = 'in student view';
|
||||
$string['interests'] = 'Interests';
|
||||
$string['interestslist'] = 'List of interests';
|
||||
$string['interestslist_help'] = 'Enter your interests. Your interests will be displayed on your profile page as tags.';
|
||||
$string['interestslist_help'] = 'Enter your interests, one by one, which will then be displayed on your profile page as tags.';
|
||||
$string['invalidemail'] = 'Invalid email address';
|
||||
$string['invalidlogin'] = 'Invalid login, please try again';
|
||||
$string['invalidusername'] = 'The username can only contain alphanumeric lowercase characters, underscore (_), hyphen (-), period (.) or at symbol (@)';
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@ $string['availability'] = 'Availability';
|
||||
$string['cancelinstallall'] = 'Cancel new installations ({$a})';
|
||||
$string['cancelinstallone'] = 'Cancel this installation';
|
||||
$string['cancelinstallhead'] = 'Cancelling installation of plugins';
|
||||
$string['cancelinstallinfo'] = 'Following plugins are not fully installed yet and their installation can be cancelled. To do so, the plugin folder must be removed from your server now. Make sure that is really what you want to prevent accidental data loss (such as your own code modifications).';
|
||||
$string['cancelinstallinfo'] = 'The following plugins are not fully installed yet so their installation may be cancelled. To do so, the plugin folder must be removed from the server now. Make sure this is really what you want to prevent accidental data loss (such as your own code modifications).';
|
||||
$string['cancelinstallinfodir'] = 'Folder to be deleted: {$a}';
|
||||
$string['cancelupgradeall'] = 'Cancel upgrades ({$a})';
|
||||
$string['cancelupgradehead'] = 'Restoring previous version of plugins';
|
||||
@@ -67,7 +67,7 @@ $string['notdownloadable'] = 'Can not download the package';
|
||||
$string['notdownloadable_help'] = 'ZIP package with the update can not be downloaded automatically. Please refer to the documentation page for more help.';
|
||||
$string['notdownloadable_link'] = 'admin/mdeploy/notdownloadable';
|
||||
$string['notwritable'] = 'Plugin files not writable';
|
||||
$string['notwritable_help'] = 'Plugin files are not writable by the web server. The web server process has to have write access to the plugin folder and all its contents. Write access to the root folder of the given plugin type may be required, too.';
|
||||
$string['notwritable_help'] = 'Plugin files are not writable by the web server. The web server process must have write access to the plugin folder and all its contents. Write access to the root folder of the given plugin type may also be required.';
|
||||
$string['otherplugin'] = '{$a->component}';
|
||||
$string['otherpluginversion'] = '{$a->component} ({$a->version})';
|
||||
$string['overviewall'] = 'All plugins';
|
||||
|
||||
@@ -117,7 +117,7 @@ $string['iprestriction'] = 'IP restriction';
|
||||
$string['iprestriction_help'] = 'The user will need to call the web service from the listed IPs (separated by commas).';
|
||||
$string['key'] = 'Key';
|
||||
$string['keyshelp'] = 'The keys are used to access your Moodle account from external applications.';
|
||||
$string['loginrequired'] = 'Restricted to logged in users';
|
||||
$string['loginrequired'] = 'Restricted to logged-in users';
|
||||
$string['manageprotocols'] = 'Manage protocols';
|
||||
$string['managetokens'] = 'Manage tokens';
|
||||
$string['missingcaps'] = 'Missing capabilities';
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+236
-210
@@ -46,11 +46,11 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
* @method activateSelection
|
||||
* @private
|
||||
* @param {Number} index The index in the current (visible) list of selection.
|
||||
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
|
||||
* @param {Object} state State variables for this autocomplete element.
|
||||
*/
|
||||
var activateSelection = function(index, selectionId) {
|
||||
var activateSelection = function(index, state) {
|
||||
// Find the elements in the DOM.
|
||||
var selectionElement = $(document.getElementById(selectionId));
|
||||
var selectionElement = $(document.getElementById(state.selectionId));
|
||||
|
||||
// Count the visible items.
|
||||
var length = selectionElement.children('[aria-selected=true]').length;
|
||||
@@ -62,7 +62,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
// Find the specified element.
|
||||
var element = $(selectionElement.children('[aria-selected=true]').get(index));
|
||||
// Create an id we can assign to this element.
|
||||
var itemId = selectionId + '-' + index;
|
||||
var itemId = state.selectionId + '-' + index;
|
||||
|
||||
// Deselect all the selections.
|
||||
selectionElement.children().attr('data-active-selection', false).attr('id', '');
|
||||
@@ -77,26 +77,29 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
*
|
||||
* @method deselectItem
|
||||
* @private
|
||||
* @param {String} inputId The id of the input element for this instance of the autocomplete.
|
||||
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
|
||||
* @param {Object} options Original options for this autocomplete element.
|
||||
* @param {Object} state State variables for this autocomplete element.
|
||||
* @param {Element} The item to be deselected.
|
||||
* @param {Element} originalSelect The original select list.
|
||||
* @param {Boolean} multiple Is this a multi select.
|
||||
*/
|
||||
var deselectItem = function(inputId, selectionId, item, originalSelect, multiple) {
|
||||
var deselectItem = function(options, state, item, originalSelect) {
|
||||
var selectedItemValue = $(item).attr('data-value');
|
||||
|
||||
// We can only deselect items if this is a multi-select field.
|
||||
if (multiple) {
|
||||
if (options.multiple) {
|
||||
// Look for a match, and toggle the selected property if there is a match.
|
||||
originalSelect.children('option').each(function(index, ele) {
|
||||
if ($(ele).attr('value') == selectedItemValue) {
|
||||
$(ele).prop('selected', false);
|
||||
// We remove newly created custom tags from the suggestions list when they are deselected.
|
||||
if ($(ele).attr('data-iscustom')) {
|
||||
$(ele).remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// Rerender the selection list.
|
||||
updateSelectionList(selectionId, inputId, originalSelect, multiple);
|
||||
updateSelectionList(options, state, originalSelect);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -105,13 +108,12 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
* @method activateItem
|
||||
* @private
|
||||
* @param {Number} index The index in the current (visible) list of suggestions.
|
||||
* @param {String} inputId The id of the input element for this instance of the autocomplete.
|
||||
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
|
||||
* @param {Object} state State variables for this instance of autocomplete.
|
||||
*/
|
||||
var activateItem = function(index, inputId, suggestionsId) {
|
||||
var activateItem = function(index, state) {
|
||||
// Find the elements in the DOM.
|
||||
var inputElement = $(document.getElementById(inputId));
|
||||
var suggestionsElement = $(document.getElementById(suggestionsId));
|
||||
var inputElement = $(document.getElementById(state.inputId));
|
||||
var suggestionsElement = $(document.getElementById(state.suggestionsId));
|
||||
|
||||
// Count the visible items.
|
||||
var length = suggestionsElement.children('[aria-hidden=false]').length;
|
||||
@@ -125,7 +127,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
// Find the index of this item in the full list of suggestions (including hidden).
|
||||
var globalIndex = $(suggestionsElement.children('[role=option]')).index(element);
|
||||
// Create an id we can assign to this element.
|
||||
var itemId = suggestionsId + '-' + globalIndex;
|
||||
var itemId = state.suggestionsId + '-' + globalIndex;
|
||||
|
||||
// Deselect all the suggestions.
|
||||
suggestionsElement.children().attr('aria-selected', false).attr('id', '');
|
||||
@@ -133,6 +135,15 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
element.attr('aria-selected', true).attr('id', itemId);
|
||||
// Tell the input field it has a new active descendant so the item is announced.
|
||||
inputElement.attr('aria-activedescendant', itemId);
|
||||
|
||||
// Scroll it into view.
|
||||
var scrollPos = element.offset().top
|
||||
- suggestionsElement.offset().top
|
||||
+ suggestionsElement.scrollTop()
|
||||
- (suggestionsElement.height() / 2);
|
||||
suggestionsElement.animate({
|
||||
scrollTop: scrollPos
|
||||
}, 100);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -140,18 +151,17 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
*
|
||||
* @method activateNextItem
|
||||
* @private
|
||||
* @param {String} inputId The id of the input element for this instance of the autocomplete.
|
||||
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
|
||||
* @param {Object} state State variable for this auto complete element.
|
||||
*/
|
||||
var activateNextItem = function(inputId, suggestionsId) {
|
||||
var activateNextItem = function(state) {
|
||||
// Find the list of suggestions.
|
||||
var suggestionsElement = $(document.getElementById(suggestionsId));
|
||||
var suggestionsElement = $(document.getElementById(state.suggestionsId));
|
||||
// Find the active one.
|
||||
var element = suggestionsElement.children('[aria-selected=true]');
|
||||
// Find it's index.
|
||||
var current = suggestionsElement.children('[aria-hidden=false]').index(element);
|
||||
// Activate the next one.
|
||||
activateItem(current+1, inputId, suggestionsId);
|
||||
activateItem(current+1, state);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -159,42 +169,42 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
*
|
||||
* @method activatePreviousSelection
|
||||
* @private
|
||||
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
|
||||
* @param {Object} state State variables for this instance of autocomplete.
|
||||
*/
|
||||
var activatePreviousSelection = function(selectionId) {
|
||||
var activatePreviousSelection = function(state) {
|
||||
// Find the list of selections.
|
||||
var selectionsElement = $(document.getElementById(selectionId));
|
||||
var selectionsElement = $(document.getElementById(state.selectionId));
|
||||
// Find the active one.
|
||||
var element = selectionsElement.children('[data-active-selection=true]');
|
||||
if (!element) {
|
||||
activateSelection(0, selectionId);
|
||||
activateSelection(0, state);
|
||||
return;
|
||||
}
|
||||
// Find it's index.
|
||||
var current = selectionsElement.children('[aria-selected=true]').index(element);
|
||||
// Activate the next one.
|
||||
activateSelection(current-1, selectionId);
|
||||
activateSelection(current-1, state);
|
||||
};
|
||||
/**
|
||||
* Find the index of the current active selection, and activate the next one.
|
||||
*
|
||||
* @method activateNextSelection
|
||||
* @private
|
||||
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
|
||||
* @param {Object} state State variables for this instance of autocomplete.
|
||||
*/
|
||||
var activateNextSelection = function(selectionId) {
|
||||
var activateNextSelection = function(state) {
|
||||
// Find the list of selections.
|
||||
var selectionsElement = $(document.getElementById(selectionId));
|
||||
var selectionsElement = $(document.getElementById(state.selectionId));
|
||||
// Find the active one.
|
||||
var element = selectionsElement.children('[data-active-selection=true]');
|
||||
if (!element) {
|
||||
activateSelection(0, selectionId);
|
||||
activateSelection(0, state);
|
||||
return;
|
||||
}
|
||||
// Find it's index.
|
||||
var current = selectionsElement.children('[aria-selected=true]').index(element);
|
||||
// Activate the next one.
|
||||
activateSelection(current+1, selectionId);
|
||||
activateSelection(current+1, state);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -202,18 +212,17 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
*
|
||||
* @method activatePreviousItem
|
||||
* @private
|
||||
* @param {String} inputId The id of the input element for this instance of the autocomplete.
|
||||
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
|
||||
* @param {Object} state State variables for this autocomplete element.
|
||||
*/
|
||||
var activatePreviousItem = function(inputId, suggestionsId) {
|
||||
var activatePreviousItem = function(state) {
|
||||
// Find the list of suggestions.
|
||||
var suggestionsElement = $(document.getElementById(suggestionsId));
|
||||
var suggestionsElement = $(document.getElementById(state.suggestionsId));
|
||||
// Find the active one.
|
||||
var element = suggestionsElement.children('[aria-selected=true]');
|
||||
// Find it's index.
|
||||
var current = suggestionsElement.children('[aria-hidden=false]').index(element);
|
||||
// Activate the next one.
|
||||
activateItem(current-1, inputId, suggestionsId);
|
||||
activateItem(current-1, state);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -221,16 +230,15 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
*
|
||||
* @method closeSuggestions
|
||||
* @private
|
||||
* @param {String} inputId The id of the input element for this instance of the autocomplete.
|
||||
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
|
||||
* @param {Object} state State variables for this autocomplete element.
|
||||
*/
|
||||
var closeSuggestions = function(inputId, suggestionsId, selectionId) {
|
||||
var closeSuggestions = function(state) {
|
||||
// Find the elements in the DOM.
|
||||
var inputElement = $(document.getElementById(inputId));
|
||||
var suggestionsElement = $(document.getElementById(suggestionsId));
|
||||
var inputElement = $(document.getElementById(state.inputId));
|
||||
var suggestionsElement = $(document.getElementById(state.suggestionsId));
|
||||
|
||||
// Announce the list of suggestions was closed, and read the current list of selections.
|
||||
inputElement.attr('aria-expanded', false).attr('aria-activedescendant', selectionId);
|
||||
inputElement.attr('aria-expanded', false).attr('aria-activedescendant', state.selectionId);
|
||||
// Hide the suggestions list (from screen readers too).
|
||||
suggestionsElement.hide().attr('aria-hidden', true);
|
||||
};
|
||||
@@ -240,43 +248,44 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
*
|
||||
* @method updateSuggestions
|
||||
* @private
|
||||
* @param {String} query The current query typed in the input field.
|
||||
* @param {String} inputId The id of the input element for this instance of the autocomplete.
|
||||
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
|
||||
* @param {Object} options The original options for this autocomplete.
|
||||
* @param {Object} state The state variables for this autocomplete.
|
||||
* @param {String} query The current text for the search string.
|
||||
* @param {JQuery} originalSelect The JQuery object matching the hidden select list.
|
||||
* @param {Boolean} multiple Are multiple items allowed to be selected?
|
||||
* @param {Boolean} tags Are we allowed to create new items on the fly?
|
||||
*/
|
||||
var updateSuggestions = function(query, inputId, suggestionsId, originalSelect, multiple, tags) {
|
||||
var updateSuggestions = function(options, state, query, originalSelect) {
|
||||
// Find the elements in the DOM.
|
||||
var inputElement = $(document.getElementById(inputId));
|
||||
var suggestionsElement = $(document.getElementById(suggestionsId));
|
||||
var inputElement = $(document.getElementById(state.inputId));
|
||||
var suggestionsElement = $(document.getElementById(state.suggestionsId));
|
||||
|
||||
// Used to track if we found any visible suggestions.
|
||||
var matchingElements = false;
|
||||
// Options is used by the context when rendering the suggestions from a template.
|
||||
var options = [];
|
||||
var suggestions = [];
|
||||
originalSelect.children('option').each(function(index, option) {
|
||||
if ($(option).prop('selected') !== true) {
|
||||
options[options.length] = { label: option.innerHTML, value: $(option).attr('value') };
|
||||
suggestions[suggestions.length] = { label: option.innerHTML, value: $(option).attr('value') };
|
||||
}
|
||||
});
|
||||
|
||||
// Re-render the list of suggestions.
|
||||
var searchquery = state.caseSensitive ? query : query.toLocaleLowerCase();
|
||||
var context = $.extend({ options: suggestions}, options, state);
|
||||
templates.render(
|
||||
'core/form_autocomplete_suggestions',
|
||||
{ inputId: inputId, suggestionsId: suggestionsId, options: options, multiple: multiple}
|
||||
context
|
||||
).done(function(newHTML) {
|
||||
// We have the new template, insert it in the page.
|
||||
suggestionsElement.replaceWith(newHTML);
|
||||
// Get the element again.
|
||||
suggestionsElement = $(document.getElementById(suggestionsId));
|
||||
suggestionsElement = $(document.getElementById(state.suggestionsId));
|
||||
// Show it if it is hidden.
|
||||
suggestionsElement.show().attr('aria-hidden', false);
|
||||
// For each option in the list, hide it if it doesn't match the query.
|
||||
suggestionsElement.children().each(function(index, node) {
|
||||
node = $(node);
|
||||
if (node.text().indexOf(query) > -1) {
|
||||
if ((options.caseSensitive && node.text().indexOf(searchquery) > -1) ||
|
||||
(!options.caseSensitive && node.text().toLocaleLowerCase().indexOf(searchquery) > -1)) {
|
||||
node.show().attr('aria-hidden', false);
|
||||
matchingElements = true;
|
||||
} else {
|
||||
@@ -284,19 +293,19 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
}
|
||||
});
|
||||
// If we found any matches, show the list.
|
||||
inputElement.attr('aria-expanded', true);
|
||||
if (matchingElements) {
|
||||
inputElement.attr('aria-expanded', true);
|
||||
// We only activate the first item in the list if tags is false,
|
||||
// because otherwise "Enter" would select the first item, instead of
|
||||
// creating a new tag.
|
||||
if (!tags) {
|
||||
activateItem(0, inputId, suggestionsId);
|
||||
if (!options.tags) {
|
||||
activateItem(0, state);
|
||||
}
|
||||
} else {
|
||||
// Abort - nothing matches. Hide the suggestions properly.
|
||||
suggestionsElement.hide();
|
||||
suggestionsElement.attr('aria-hidden', true);
|
||||
inputElement.attr('aria-expanded', false);
|
||||
// Nothing matches. Tell them that.
|
||||
str.get_string('nosuggestions', 'form').done(function(nosuggestionsstr) {
|
||||
suggestionsElement.html(nosuggestionsstr);
|
||||
});
|
||||
}
|
||||
}).fail(notification.exception);
|
||||
|
||||
@@ -307,14 +316,13 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
*
|
||||
* @method createItem
|
||||
* @private
|
||||
* @param {String} inputId The id of the input element for this instance of the autocomplete.
|
||||
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
|
||||
* @param {Boolean} multiple Are multiple items allowed to be selected?
|
||||
* @param {Object} options The original options for the autocomplete.
|
||||
* @param {Object} state State variables for the autocomplete.
|
||||
* @param {JQuery} originalSelect The JQuery object matching the hidden select list.
|
||||
*/
|
||||
var createItem = function(inputId, suggestionsId, selectionId, multiple, originalSelect) {
|
||||
var createItem = function(options, state, originalSelect) {
|
||||
// Find the element in the DOM.
|
||||
var inputElement = $(document.getElementById(inputId));
|
||||
var inputElement = $(document.getElementById(state.inputId));
|
||||
// Get the current text in the input field.
|
||||
var query = inputElement.val();
|
||||
var tags = query.split(',');
|
||||
@@ -324,7 +332,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
// If we can only select one at a time, deselect any current value.
|
||||
tag = tag.trim();
|
||||
if (tag !== '') {
|
||||
if (!multiple) {
|
||||
if (!options.multiple) {
|
||||
originalSelect.children('option').prop('selected', false);
|
||||
}
|
||||
// Look for an existing option in the select list that matches this new tag.
|
||||
@@ -341,34 +349,18 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
option.attr('value', tag);
|
||||
originalSelect.append(option);
|
||||
option.prop('selected', true);
|
||||
// We mark newly created custom options as we handle them differently if they are "deselected".
|
||||
option.attr('data-iscustom', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Get the selection element.
|
||||
var newSelection = $(document.getElementById(selectionId));
|
||||
// Build up a valid context to re-render the selection.
|
||||
var items = [];
|
||||
originalSelect.children('option').each(function(index, ele) {
|
||||
if ($(ele).prop('selected')) {
|
||||
items.push( { label: $(ele).html(), value: $(ele).attr('value') } );
|
||||
}
|
||||
});
|
||||
var context = {
|
||||
selectionId: selectionId,
|
||||
items: items,
|
||||
multiple: multiple
|
||||
};
|
||||
// Re-render the selection.
|
||||
templates.render('core/form_autocomplete_selection', context).done(function(newHTML) {
|
||||
// Update the page.
|
||||
newSelection.empty().append($(newHTML).html());
|
||||
}).fail(notification.exception);
|
||||
|
||||
updateSelectionList(options, state, originalSelect);
|
||||
|
||||
// Clear the input field.
|
||||
inputElement.val('');
|
||||
// Close the suggestions list.
|
||||
closeSuggestions(inputId, suggestionsId, selectionId);
|
||||
// Trigger a change event so that the mforms javascript can check for required fields etc.
|
||||
originalSelect.change();
|
||||
closeSuggestions(state);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -376,29 +368,40 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
*
|
||||
* @method updateSelectionList
|
||||
* @private
|
||||
* @param {String} selectionId The id of the selections element for this instance of the autocomplete.
|
||||
* @param {String} inputId The id of the input element for this instance of the autocomplete.
|
||||
* @param {Object} options Original options for this autocomplete element.
|
||||
* @param {Object} state State variables for this autocomplete element.
|
||||
* @param {JQuery} originalSelect The JQuery object matching the hidden select list.
|
||||
* @param {Boolean} multiple Does this element support multiple selections.
|
||||
*/
|
||||
var updateSelectionList = function(selectionId, inputId, originalSelect, multiple) {
|
||||
var updateSelectionList = function(options, state, originalSelect) {
|
||||
// Build up a valid context to re-render the template.
|
||||
var items = [];
|
||||
var newSelection = $(document.getElementById(selectionId));
|
||||
var newSelection = $(document.getElementById(state.selectionId));
|
||||
var activeId = newSelection.attr('aria-activedescendant');
|
||||
var activeValue = false;
|
||||
|
||||
if (activeId) {
|
||||
activeValue = $(document.getElementById(activeId)).attr('data-value');
|
||||
}
|
||||
originalSelect.children('option').each(function(index, ele) {
|
||||
if ($(ele).prop('selected')) {
|
||||
items.push( { label: $(ele).html(), value: $(ele).attr('value') } );
|
||||
}
|
||||
});
|
||||
var context = {
|
||||
selectionId: selectionId,
|
||||
items: items,
|
||||
multiple: multiple
|
||||
};
|
||||
var context = $.extend({ items: items }, options, state);
|
||||
|
||||
// Render the template.
|
||||
templates.render('core/form_autocomplete_selection', context).done(function(newHTML) {
|
||||
// Add it to the page.
|
||||
newSelection.empty().append($(newHTML).html());
|
||||
|
||||
if (activeValue !== false) {
|
||||
// Reselect any previously selected item.
|
||||
newSelection.children('[aria-selected=true]').each(function(index, ele) {
|
||||
if ($(ele).attr('data-value') === activeValue) {
|
||||
activateSelection(index, state);
|
||||
}
|
||||
});
|
||||
}
|
||||
}).fail(notification.exception);
|
||||
// Because this function get's called after changing the selection, this is a good place
|
||||
// to trigger a change notification.
|
||||
@@ -410,16 +413,14 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
*
|
||||
* @method selectCurrentItem
|
||||
* @private
|
||||
* @param {String} inputId The id of the input element for this instance of the autocomplete.
|
||||
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
|
||||
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
|
||||
* @param {Boolean} multiple Are multiple items allowed to be selected?
|
||||
* @param {Object} options The original options for the autocomplete.
|
||||
* @param {Object} state State variables for the autocomplete.
|
||||
* @param {JQuery} originalSelect The JQuery object matching the hidden select list.
|
||||
*/
|
||||
var selectCurrentItem = function(inputId, suggestionsId, selectionId, multiple, originalSelect) {
|
||||
var selectCurrentItem = function(options, state, originalSelect) {
|
||||
// Find the elements in the page.
|
||||
var inputElement = $(document.getElementById(inputId));
|
||||
var suggestionsElement = $(document.getElementById(suggestionsId));
|
||||
var inputElement = $(document.getElementById(state.inputId));
|
||||
var suggestionsElement = $(document.getElementById(state.suggestionsId));
|
||||
// Here loop through suggestions and set val to join of all selected items.
|
||||
|
||||
var selectedItemValue = suggestionsElement.children('[aria-selected=true]').attr('data-value');
|
||||
@@ -427,7 +428,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
// select one or more items correctly.
|
||||
// Take care to use 'prop' and not 'attr' for selected properties.
|
||||
// If only one can be selected at a time, start by deselecting everything.
|
||||
if (!multiple) {
|
||||
if (!options.multiple) {
|
||||
originalSelect.children('option').prop('selected', false);
|
||||
}
|
||||
// Look for a match, and toggle the selected property if there is a match.
|
||||
@@ -437,11 +438,11 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
}
|
||||
});
|
||||
// Rerender the selection list.
|
||||
updateSelectionList(selectionId, inputId, originalSelect, multiple);
|
||||
updateSelectionList(options, state, originalSelect);
|
||||
// Clear the input element.
|
||||
inputElement.val('');
|
||||
// Close the list of suggestions.
|
||||
closeSuggestions(inputId, suggestionsId, selectionId);
|
||||
closeSuggestions(state);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -450,21 +451,18 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
* @method updateAjax
|
||||
* @private
|
||||
* @param {Event} e The event that triggered this update.
|
||||
* @param {String} selector The selector pointing to the original select.
|
||||
* @param {String} inputId The id of the input element for this instance of the autocomplete.
|
||||
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
|
||||
* @param {Object} options The original options for the autocomplete.
|
||||
* @param {Object} state The state variables for the autocomplete.
|
||||
* @param {JQuery} originalSelect The JQuery object matching the hidden select list.
|
||||
* @param {Boolean} multiple Are multiple items allowed to be selected?
|
||||
* @param {Boolean} tags Are we allowed to create new items on the fly?
|
||||
* @param {Object} ajaxHandler This is a module that does the ajax fetch and translates the results.
|
||||
*/
|
||||
var updateAjax = function(e, selector, inputId, suggestionsId, originalSelect, multiple, tags, ajaxHandler) {
|
||||
var updateAjax = function(e, options, state, originalSelect, ajaxHandler) {
|
||||
// Get the query to pass to the ajax function.
|
||||
var query = $(e.currentTarget).val();
|
||||
// Call the transport function to do the ajax (name taken from Select2).
|
||||
ajaxHandler.transport(selector, query, function(results) {
|
||||
ajaxHandler.transport(options.selector, query, function(results) {
|
||||
// We got a result - pass it through the translator before using it.
|
||||
var processedResults = ajaxHandler.processResults(selector, results);
|
||||
var processedResults = ajaxHandler.processResults(options.selector, results);
|
||||
var existingValues = [];
|
||||
|
||||
// Now destroy all options that are not currently selected.
|
||||
@@ -486,7 +484,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
}
|
||||
});
|
||||
// Update the list of suggestions now from the new values in the select list.
|
||||
updateSuggestions('', inputId, suggestionsId, originalSelect, multiple, tags);
|
||||
updateSuggestions(options, state, '', originalSelect);
|
||||
}, notification.exception);
|
||||
};
|
||||
|
||||
@@ -495,54 +493,60 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
*
|
||||
* @method addNavigation
|
||||
* @private
|
||||
* @param {String} inputId The id of the input element for this instance of the autocomplete.
|
||||
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
|
||||
* @param {String} downArrowId The id of arrow to open the suggestions list.
|
||||
* @param {String} selectionId The id of element that shows the current selections.
|
||||
* @param {Object} options The options used to create this autocomplete element.
|
||||
* @param {Object} state State variables for this autocomplete element.
|
||||
* @param {JQuery} originalSelect The JQuery object matching the hidden select list.
|
||||
* @param {Boolean} multiple Are multiple items allowed to be selected?
|
||||
* @param {Boolean} tags Are we allowed to create new items on the fly?
|
||||
*/
|
||||
var addNavigation = function(inputId, suggestionsId, downArrowId, selectionId, originalSelect, multiple, tags) {
|
||||
var addNavigation = function(options, state, originalSelect) {
|
||||
// Start with the input element.
|
||||
var inputElement = $(document.getElementById(inputId));
|
||||
var inputElement = $(document.getElementById(state.inputId));
|
||||
// Add keyboard nav with keydown.
|
||||
inputElement.on('keydown', function(e) {
|
||||
switch (e.keyCode) {
|
||||
case KEYS.DOWN:
|
||||
// If the suggestion list is open, move to the next item.
|
||||
if (inputElement.attr('aria-expanded') === "true") {
|
||||
activateNextItem(inputId, suggestionsId);
|
||||
if (!options.showSuggestions) {
|
||||
// Do not consume this event.
|
||||
return true;
|
||||
} else if (inputElement.attr('aria-expanded') === "true") {
|
||||
activateNextItem(state);
|
||||
} else {
|
||||
// Else - open the suggestions list.
|
||||
updateSuggestions(inputElement.val(), inputId, suggestionsId, originalSelect, multiple, tags);
|
||||
// Handle ajax population of suggestions.
|
||||
if (!inputElement.val() && options.ajax) {
|
||||
require([options.ajax], function(ajaxHandler) {
|
||||
updateAjax(e, options, state, originalSelect, ajaxHandler);
|
||||
});
|
||||
} else {
|
||||
// Else - open the suggestions list.
|
||||
updateSuggestions(options, state, inputElement.val(), originalSelect);
|
||||
}
|
||||
}
|
||||
// We handled this event, so prevent it.
|
||||
e.preventDefault();
|
||||
return false;
|
||||
case KEYS.COMMA:
|
||||
if (tags) {
|
||||
if (options.tags) {
|
||||
// If we are allowing tags, comma should create a tag (or enter).
|
||||
createItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
|
||||
createItem(options, state, originalSelect);
|
||||
}
|
||||
// We handled this event, so prevent it.
|
||||
e.preventDefault();
|
||||
return false;
|
||||
case KEYS.UP:
|
||||
// Choose the previous active item.
|
||||
activatePreviousItem(inputId, suggestionsId);
|
||||
activatePreviousItem(state);
|
||||
// We handled this event, so prevent it.
|
||||
e.preventDefault();
|
||||
return false;
|
||||
case KEYS.ENTER:
|
||||
var suggestionsElement = $(document.getElementById(suggestionsId));
|
||||
var suggestionsElement = $(document.getElementById(state.suggestionsId));
|
||||
if ((inputElement.attr('aria-expanded') === "true") &&
|
||||
(suggestionsElement.children('[aria-selected=true]').length > 0)) {
|
||||
// If the suggestion list has an active item, select it.
|
||||
selectCurrentItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
|
||||
} else if (tags) {
|
||||
selectCurrentItem(options, state, originalSelect);
|
||||
} else if (options.tags) {
|
||||
// If tags are enabled, create a tag.
|
||||
createItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
|
||||
createItem(options, state, originalSelect);
|
||||
}
|
||||
// We handled this event, so prevent it.
|
||||
e.preventDefault();
|
||||
@@ -550,7 +554,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
case KEYS.ESCAPE:
|
||||
if (inputElement.attr('aria-expanded') === "true") {
|
||||
// If the suggestion list is open, close it.
|
||||
closeSuggestions(inputId, suggestionsId, selectionId);
|
||||
closeSuggestions(state);
|
||||
}
|
||||
// We handled this event, so prevent it.
|
||||
e.preventDefault();
|
||||
@@ -560,8 +564,8 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
});
|
||||
// Handler used to force set the value from behat.
|
||||
inputElement.on('behat:set-value', function() {
|
||||
if (tags) {
|
||||
createItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
|
||||
if (options.tags) {
|
||||
createItem(options, state, originalSelect);
|
||||
}
|
||||
});
|
||||
inputElement.on('blur focus', function(e) {
|
||||
@@ -572,65 +576,69 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
window.clearTimeout(closeSuggestionsTimer);
|
||||
}
|
||||
closeSuggestionsTimer = window.setTimeout(function() {
|
||||
if ((e.type == 'blur') && tags) {
|
||||
createItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
|
||||
if (e.type == 'blur') {
|
||||
if (options.tags) {
|
||||
createItem(options, state, originalSelect);
|
||||
}
|
||||
closeSuggestions(state);
|
||||
}
|
||||
closeSuggestions(inputId, suggestionsId, selectionId);
|
||||
}, 500);
|
||||
});
|
||||
var arrowElement = $(document.getElementById(downArrowId));
|
||||
arrowElement.on('click', function() {
|
||||
// Prevent the close timer, or we will open, then close the suggestions.
|
||||
inputElement.focus();
|
||||
if (closeSuggestionsTimer) {
|
||||
window.clearTimeout(closeSuggestionsTimer);
|
||||
}
|
||||
// Show the suggestions list.
|
||||
updateSuggestions(inputElement.val(), inputId, suggestionsId, originalSelect, multiple, tags);
|
||||
});
|
||||
if (options.showSuggestions) {
|
||||
var arrowElement = $(document.getElementById(state.downArrowId));
|
||||
arrowElement.on('click', function() {
|
||||
// Prevent the close timer, or we will open, then close the suggestions.
|
||||
inputElement.focus();
|
||||
if (closeSuggestionsTimer) {
|
||||
window.clearTimeout(closeSuggestionsTimer);
|
||||
}
|
||||
// Show the suggestions list.
|
||||
updateSuggestions(options, state, inputElement.val(), originalSelect);
|
||||
});
|
||||
}
|
||||
|
||||
var suggestionsElement = $(document.getElementById(suggestionsId));
|
||||
var suggestionsElement = $(document.getElementById(state.suggestionsId));
|
||||
suggestionsElement.parent().on('click', '[role=option]', function(e) {
|
||||
// Handle clicks on suggestions.
|
||||
var element = $(e.currentTarget).closest('[role=option]');
|
||||
var suggestionsElement = $(document.getElementById(suggestionsId));
|
||||
var suggestionsElement = $(document.getElementById(state.suggestionsId));
|
||||
// Find the index of the clicked on suggestion.
|
||||
var current = suggestionsElement.children('[aria-hidden=false]').index(element);
|
||||
// Activate it.
|
||||
activateItem(current, inputId, suggestionsId);
|
||||
activateItem(current, state);
|
||||
// And select it.
|
||||
selectCurrentItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
|
||||
selectCurrentItem(options, state, originalSelect);
|
||||
});
|
||||
var selectionElement = $(document.getElementById(selectionId));
|
||||
var selectionElement = $(document.getElementById(state.selectionId));
|
||||
// Handle clicks on the selected items (will unselect an item).
|
||||
selectionElement.parent().on('click', '[role=listitem]', function(e) {
|
||||
selectionElement.on('click', '[role=listitem]', function(e) {
|
||||
// Get the item that was clicked.
|
||||
var item = $(e.currentTarget);
|
||||
// Remove it from the selection.
|
||||
deselectItem(inputId, selectionId, item, originalSelect, multiple);
|
||||
deselectItem(options, state, item, originalSelect);
|
||||
});
|
||||
// Keyboard navigation for the selection list.
|
||||
selectionElement.parent().on('keydown', function(e) {
|
||||
selectionElement.on('keydown', function(e) {
|
||||
switch (e.keyCode) {
|
||||
case KEYS.DOWN:
|
||||
// Choose the next selection item.
|
||||
activateNextSelection(selectionId);
|
||||
activateNextSelection(state);
|
||||
// We handled this event, so prevent it.
|
||||
e.preventDefault();
|
||||
return false;
|
||||
case KEYS.UP:
|
||||
// Choose the previous selection item.
|
||||
activatePreviousSelection(selectionId);
|
||||
activatePreviousSelection(state);
|
||||
// We handled this event, so prevent it.
|
||||
e.preventDefault();
|
||||
return false;
|
||||
case KEYS.SPACE:
|
||||
case KEYS.ENTER:
|
||||
// Get the item that is currently selected.
|
||||
var selectedItem = $(document.getElementById(selectionId)).children('[data-active-selection=true]');
|
||||
var selectedItem = $(document.getElementById(state.selectionId)).children('[data-active-selection=true]');
|
||||
if (selectedItem) {
|
||||
// Unselect this item.
|
||||
deselectItem(inputId, selectionId, selectedItem, originalSelect, multiple);
|
||||
deselectItem(options, state, selectedItem, originalSelect);
|
||||
// We handled this event, so prevent it.
|
||||
e.preventDefault();
|
||||
}
|
||||
@@ -639,10 +647,21 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
return true;
|
||||
});
|
||||
// Whenever the input field changes, update the suggestion list.
|
||||
inputElement.on('input', function(e) {
|
||||
var query = $(e.currentTarget).val();
|
||||
updateSuggestions(query, inputId, suggestionsId, originalSelect, multiple, tags);
|
||||
});
|
||||
if (options.showSuggestions) {
|
||||
inputElement.on('input', function(e) {
|
||||
var query = $(e.currentTarget).val();
|
||||
var last = $(e.currentTarget).data('last-value');
|
||||
// IE11 fires many more input events than required - even when the value has not changed.
|
||||
// We need to only do this for real value changed events or the suggestions will be
|
||||
// unclickable on IE11 (because they will be rebuilt before the click event fires).
|
||||
// Note - because of this we cannot close the list when the query is empty or it will break
|
||||
// on IE11.
|
||||
if (last !== query) {
|
||||
updateSuggestions(options, state, query, originalSelect);
|
||||
}
|
||||
$(e.currentTarget).data('last-value', query);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return /** @alias module:core/form-autocomplete */ {
|
||||
@@ -657,14 +676,29 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
* module must expose 2 functions "transport" and "processResults".
|
||||
* These are modeled on Select2 see: https://select2.github.io/options.html#ajax
|
||||
* @param {String} placeholder - The text to display before a selection is made.
|
||||
* @param {Boolean} caseSensitive - If search has to be made case sensitive.
|
||||
*/
|
||||
enhance: function(selector, tags, ajax, placeholder) {
|
||||
enhance: function(selector, tags, ajax, placeholder, caseSensitive, showSuggestions) {
|
||||
// Set some default values.
|
||||
if (typeof tags === "undefined") {
|
||||
tags = false;
|
||||
var options = {
|
||||
selector: selector,
|
||||
tags: false,
|
||||
ajax: false,
|
||||
placeholder: placeholder,
|
||||
caseSensitive: false,
|
||||
showSuggestions: true
|
||||
};
|
||||
if (typeof tags !== "undefined") {
|
||||
options.tags = tags;
|
||||
}
|
||||
if (typeof ajax === "undefined") {
|
||||
ajax = false;
|
||||
if (typeof ajax !== "undefined") {
|
||||
options.ajax = ajax;
|
||||
}
|
||||
if (typeof caseSensitive !== "undefined") {
|
||||
options.caseSensitive = caseSensitive;
|
||||
}
|
||||
if (typeof showSuggestions !== "undefined") {
|
||||
options.showSuggestions = showSuggestions;
|
||||
}
|
||||
|
||||
// Look for the select element.
|
||||
@@ -678,38 +712,30 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
originalSelect.hide().attr('aria-hidden', true);
|
||||
|
||||
// Find or generate some ids.
|
||||
var selectId = originalSelect.attr('id');
|
||||
var multiple = originalSelect.attr('multiple');
|
||||
var inputId = 'form_autocomplete_input-' + $.now();
|
||||
var suggestionsId = 'form_autocomplete_suggestions-' + $.now();
|
||||
var selectionId = 'form_autocomplete_selection-' + $.now();
|
||||
var downArrowId = 'form_autocomplete_downarrow-' + $.now();
|
||||
var state = {
|
||||
selectId: originalSelect.attr('id'),
|
||||
inputId: 'form_autocomplete_input-' + $.now(),
|
||||
suggestionsId: 'form_autocomplete_suggestions-' + $.now(),
|
||||
selectionId: 'form_autocomplete_selection-' + $.now(),
|
||||
downArrowId: 'form_autocomplete_downarrow-' + $.now()
|
||||
};
|
||||
options.multiple = originalSelect.attr('multiple');
|
||||
|
||||
var originalLabel = $('[for=' + selectId + ']');
|
||||
var originalLabel = $('[for=' + state.selectId + ']');
|
||||
// Create the new markup and insert it after the select.
|
||||
var options = [];
|
||||
var suggestions = [];
|
||||
originalSelect.children('option').each(function(index, option) {
|
||||
options[index] = { label: option.innerHTML, value: $(option).attr('value') };
|
||||
suggestions[index] = { label: option.innerHTML, value: $(option).attr('value') };
|
||||
});
|
||||
|
||||
// Render all the parts of our UI.
|
||||
var renderInput = templates.render(
|
||||
'core/form_autocomplete_input',
|
||||
{ downArrowId: downArrowId,
|
||||
inputId: inputId,
|
||||
suggestionsId: suggestionsId,
|
||||
selectionId: selectionId,
|
||||
placeholder: placeholder,
|
||||
multiple: multiple }
|
||||
);
|
||||
var renderDatalist = templates.render(
|
||||
'core/form_autocomplete_suggestions',
|
||||
{ inputId: inputId, suggestionsId: suggestionsId, options: options, multiple: multiple}
|
||||
);
|
||||
var renderSelection = templates.render(
|
||||
'core/form_autocomplete_selection',
|
||||
{ selectionId: selectionId, items: [], multiple: multiple}
|
||||
);
|
||||
var context = $.extend({}, options, state);
|
||||
context.options = suggestions;
|
||||
context.items = [];
|
||||
|
||||
var renderInput = templates.render('core/form_autocomplete_input', context);
|
||||
var renderDatalist = templates.render('core/form_autocomplete_suggestions', context);
|
||||
var renderSelection = templates.render('core/form_autocomplete_selection', context);
|
||||
|
||||
$.when(renderInput, renderDatalist, renderSelection).done(function(input, suggestions, selection) {
|
||||
// Add our new UI elements to the page.
|
||||
@@ -717,29 +743,29 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
|
||||
originalSelect.after(input);
|
||||
originalSelect.after(selection);
|
||||
// Update the form label to point to the text input.
|
||||
originalLabel.attr('for', inputId);
|
||||
originalLabel.attr('for', state.inputId);
|
||||
// Add the event handlers.
|
||||
addNavigation(inputId, suggestionsId, downArrowId, selectionId, originalSelect, multiple, tags);
|
||||
addNavigation(options, state, originalSelect);
|
||||
|
||||
var inputElement = $(document.getElementById(inputId));
|
||||
var suggestionsElement = $(document.getElementById(suggestionsId));
|
||||
var inputElement = $(document.getElementById(state.inputId));
|
||||
var suggestionsElement = $(document.getElementById(state.suggestionsId));
|
||||
// Hide the suggestions by default.
|
||||
suggestionsElement.hide().attr('aria-hidden', true);
|
||||
|
||||
// If this field uses ajax, set it up.
|
||||
if (ajax) {
|
||||
require([ajax], function(ajaxHandler) {
|
||||
if (options.ajax) {
|
||||
require([options.ajax], function(ajaxHandler) {
|
||||
var handler = function(e) {
|
||||
updateAjax(e, selector, inputId, suggestionsId, originalSelect, multiple, tags, ajaxHandler);
|
||||
updateAjax(e, options, state, originalSelect, ajaxHandler);
|
||||
};
|
||||
// Trigger an ajax update after the text field value changes.
|
||||
inputElement.on("input keypress", handler);
|
||||
var arrowElement = $(document.getElementById(downArrowId));
|
||||
var arrowElement = $(document.getElementById(state.downArrowId));
|
||||
arrowElement.on("click", handler);
|
||||
});
|
||||
}
|
||||
// Show the current values in the selection list.
|
||||
updateSelectionList(selectionId, inputId, originalSelect, multiple);
|
||||
updateSelectionList(options, state, originalSelect);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -86,7 +86,12 @@ class behat_field_manager {
|
||||
|
||||
// Get the field type if is part of a moodleform.
|
||||
if (self::is_moodleform_field($fieldnode)) {
|
||||
$type = self::get_field_node_type($fieldnode, $session);
|
||||
// This might go out of scope, finding element beyond the dom and fail. So fallback to guessing type.
|
||||
try {
|
||||
$type = self::get_field_node_type($fieldnode, $session);
|
||||
} catch (WebDriver\Exception\InvalidSelector $e) {
|
||||
$type = 'field';
|
||||
}
|
||||
}
|
||||
|
||||
// If is not a moodleforms field use the base field type.
|
||||
|
||||
@@ -106,7 +106,9 @@ class behat_form_field {
|
||||
// using the generic behat_form_field is because we are
|
||||
// dealing with a fgroup element.
|
||||
$instance = $this->guess_type();
|
||||
return $instance->field->keyPress($char, $modifier);
|
||||
$instance->field->keyDown($char, $modifier);
|
||||
$instance->field->keyPress($char, $modifier);
|
||||
$instance->field->keyUp($char, $modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -537,7 +537,6 @@ class core_date {
|
||||
self::$badzones['5.5'] = 'Asia/Kolkata';
|
||||
self::$badzones['6.5'] = 'Asia/Rangoon';
|
||||
self::$badzones['9.5'] = 'Australia/Darwin';
|
||||
self::$badzones['11.5'] = 'Pacific/Norfolk';
|
||||
|
||||
// Remove bad zones that are elsewhere.
|
||||
foreach (self::$bczones as $zone => $unused) {
|
||||
|
||||
@@ -491,6 +491,12 @@ class manager {
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the task data is unchanged.
|
||||
if (!$DB->record_exists('task_scheduled', (array) $record)) {
|
||||
$lock->release();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$task->is_blocking()) {
|
||||
$cronlock->release();
|
||||
} else {
|
||||
|
||||
@@ -180,13 +180,13 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
return $engine;
|
||||
}
|
||||
|
||||
// get the default database engine
|
||||
$sql = "SELECT @@storage_engine";
|
||||
// Get the default database engine.
|
||||
$sql = "SELECT @@default_storage_engine engine";
|
||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
if ($rec = $result->fetch_assoc()) {
|
||||
$engine = $rec['@@storage_engine'];
|
||||
$engine = $rec['engine'];
|
||||
}
|
||||
$result->close();
|
||||
|
||||
|
||||
@@ -44,6 +44,14 @@ $PAGE->set_context($context);
|
||||
require_login($course, false, $cm);
|
||||
require_sesskey();
|
||||
|
||||
if (isguestuser()) {
|
||||
print_error('accessdenied', 'admin');
|
||||
}
|
||||
|
||||
if (!in_array('atto', explode(',', get_config('core', 'texteditors')))) {
|
||||
print_error('accessdenied', 'admin');
|
||||
}
|
||||
|
||||
$action = required_param('action', PARAM_ALPHA);
|
||||
|
||||
$response = array();
|
||||
|
||||
@@ -166,6 +166,10 @@ class atto_texteditor extends texteditor {
|
||||
}
|
||||
$contentcss = $PAGE->theme->editor_css_url()->out(false);
|
||||
|
||||
// Autosave disabled for guests.
|
||||
if (isguestuser()) {
|
||||
$autosave = false;
|
||||
}
|
||||
// Note <> is a safe separator, because it will not appear in the output of s().
|
||||
$pagehash = sha1($PAGE->url . '<>' . s($this->get_text()));
|
||||
$params = array(
|
||||
|
||||
Vendored
+1
-1
@@ -333,8 +333,8 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
};
|
||||
xhr.open("POST", M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload', true);
|
||||
xhr.send(formData);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -333,8 +333,8 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
};
|
||||
xhr.open("POST", M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload', true);
|
||||
xhr.send(formData);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
|
||||
@@ -331,8 +331,8 @@ Y.namespace('M.atto_image').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
};
|
||||
xhr.open("POST", M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload', true);
|
||||
xhr.send(formData);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
|
||||
@@ -28,13 +28,13 @@ $string['all'] = 'Around each cell';
|
||||
$string['allowbackgroundcolour'] = 'Allow background colour';
|
||||
$string['allowbackgroundcolour_desc'] = 'Allow users to set the background colour for the table';
|
||||
$string['allowborder'] = 'Allow borders';
|
||||
$string['allowborder_desc'] = 'If checked, users will be able to customize borders when creating and editing a table';
|
||||
$string['allowborder_desc'] = 'If enabled, table and cell borders can be customised. The default border is a solid, 1px, black line.';
|
||||
$string['allowborderstyle'] = 'Allow border styling';
|
||||
$string['allowborderstyle_desc'] = 'Allow users to style borders on tables. If this is not checked, but allowborder is checked, the default style is solid.<br>NOTE that this setting will not have an impact unless allowborder is checked';
|
||||
$string['allowborderstyle_desc'] = 'This setting has no effect unless \'Allow borders\' is enabled.';
|
||||
$string['allowbordersize'] = 'Allow border size control';
|
||||
$string['allowbordersize_desc'] = 'Allow users to set the size of borders on tables. If this is not checked, but allowborder is checked, the default size is 1px.<br>NOTE that this setting will not have an impact unless allowborder is checked';
|
||||
$string['allowbordersize_desc'] = 'This setting has no effect unless \'Allow borders\' is enabled.';
|
||||
$string['allowbordercolour'] = 'Allow border colour';
|
||||
$string['allowbordercolour_desc'] = 'Allow users to set the colour of the borders on tables. If this is not checked, but allowborder is checked, the default colour is black.<br>NOTE that this setting will not have an impact unless allowborder is checked';
|
||||
$string['allowbordercolour_desc'] = 'This setting has no effect unless \'Allow borders\' is enabled.';
|
||||
$string['allowwidth'] = 'Allow width';
|
||||
$string['allowwidth_desc'] = 'Allow users to set the width of the tables.';
|
||||
$string['appearance'] = 'Appearance';
|
||||
@@ -45,15 +45,22 @@ $string['borders'] = 'Borders';
|
||||
$string['bordersize'] = 'Size of borders';
|
||||
$string['borderstyles'] = 'Style of borders';
|
||||
$string['borderstyles_desc'] = 'Allowed border styles, separated by commas';
|
||||
$string['borderstyles_default'] = 'solid,dashed,dotted';
|
||||
$string['caption'] = 'Caption';
|
||||
$string['captionposition'] = 'Caption position';
|
||||
$string['columns'] = 'Columns';
|
||||
$string['createtable'] = 'Create table';
|
||||
$string['deletecolumn'] = 'Delete column';
|
||||
$string['deleterow'] = 'Delete row';
|
||||
$string['dashed'] = 'Dashed';
|
||||
$string['dotted'] = 'Dotted';
|
||||
$string['double'] = 'Double';
|
||||
$string['edittable'] = 'Edit table';
|
||||
$string['groove'] = 'Groove';
|
||||
$string['headers'] = 'Define headers on';
|
||||
$string['hidden'] = 'Hidden';
|
||||
$string['inherit'] = 'Inherit';
|
||||
$string['initial'] = 'Initial';
|
||||
$string['inset'] = 'Inset';
|
||||
$string['movecolumnleft'] = 'Move column left';
|
||||
$string['movecolumnright'] = 'Move column right';
|
||||
$string['moverowdown'] = 'Move row down';
|
||||
@@ -63,10 +70,14 @@ $string['none'] = 'None';
|
||||
$string['numberofcolumns'] = 'Number of columns';
|
||||
$string['numberofrows'] = 'Number of rows';
|
||||
$string['outer'] = 'Around table';
|
||||
$string['outset'] = 'Outset';
|
||||
$string['pluginname'] = 'Table';
|
||||
$string['ridge'] = 'Ridge';
|
||||
$string['rows'] = 'Rows';
|
||||
$string['settings'] = 'Table settings';
|
||||
$string['updatetable'] = 'Update table';
|
||||
$string['width'] = 'Table width (in %)';
|
||||
$string['solid'] = 'Solid';
|
||||
$string['themedefault'] = 'Theme default';
|
||||
$string['transparent'] = 'Transparent';
|
||||
$string['unset'] = 'Unset';
|
||||
$string['updatetable'] = 'Update table';
|
||||
$string['width'] = 'Table width (in %)';
|
||||
|
||||
@@ -60,8 +60,19 @@ function atto_table_strings_for_js() {
|
||||
'width',
|
||||
'outer',
|
||||
'noborder',
|
||||
'transparent',
|
||||
'themedefault'),
|
||||
'inherit',
|
||||
'themedefault',
|
||||
'initial',
|
||||
'unset',
|
||||
'hidden',
|
||||
'dotted',
|
||||
'dashed',
|
||||
'solid',
|
||||
'double',
|
||||
'groove',
|
||||
'ridge',
|
||||
'inset',
|
||||
'outset'),
|
||||
'atto_table');
|
||||
|
||||
$PAGE->requires->strings_for_js(array('top',
|
||||
|
||||
@@ -50,16 +50,20 @@ if ($ADMIN->fulltree) {
|
||||
|
||||
$name = new lang_string('borderstyles', 'atto_table');
|
||||
$desc = new lang_string('borderstyles_desc', 'atto_table');
|
||||
$default = ['none' => 'none',
|
||||
'hidden' => 'hidden',
|
||||
'dotted' => 'dotted',
|
||||
'dashed' => 'dashed',
|
||||
'solid' => 'solid',
|
||||
'double' => 'double',
|
||||
'groove' => 'groove',
|
||||
'ridge' => 'ridge',
|
||||
'inset' => 'inset',
|
||||
'outset' => 'outset'];
|
||||
$default = array(
|
||||
'initial' => new lang_string('initial', 'atto_table'),
|
||||
'unset' => new lang_string('unset', 'atto_table'),
|
||||
'none' => new lang_string('none', 'atto_table'),
|
||||
'hidden' => new lang_string('hidden', 'atto_table'),
|
||||
'dotted' => new lang_string('dotted', 'atto_table'),
|
||||
'dashed' => new lang_string('dashed', 'atto_table'),
|
||||
'solid' => new lang_string('solid', 'atto_table'),
|
||||
'double' => new lang_string('double', 'atto_table'),
|
||||
'groove' => new lang_string('groove', 'atto_table'),
|
||||
'ridge' => new lang_string('ridge', 'atto_table'),
|
||||
'inset' => new lang_string('inset', 'atto_table'),
|
||||
'outset' => new lang_string('outset', 'atto_table'),
|
||||
);
|
||||
|
||||
$setting = new admin_setting_configmultiselect('atto_table/borderstyles',
|
||||
$name,
|
||||
|
||||
@@ -34,3 +34,6 @@ div.availablecolors label input[type="radio"] {
|
||||
margin-left: -15px;
|
||||
}
|
||||
|
||||
input[name="bordersize"], input[name="width"] {
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,71 @@ Feature: Atto tables
|
||||
And I press "Save changes"
|
||||
Then ".blog_entry table caption" "css_element" should be visible
|
||||
|
||||
@javascript
|
||||
Scenario: Check table setting defaults on new table
|
||||
Given the following config values are set as admin:
|
||||
| config | value | plugin |
|
||||
| allowborders | 1 | atto_table |
|
||||
| allowborderstyles | 1 | atto_table |
|
||||
| allowbordersize | 1 | atto_table |
|
||||
| allowbordercolour | 1 | atto_table |
|
||||
| allowbackgroundcolour | 1 | atto_table |
|
||||
| allowwidth | 1 | atto_table |
|
||||
And I log in as "admin"
|
||||
And I follow "Profile" in the user menu
|
||||
And I follow "Blog entries"
|
||||
And I follow "Add a new entry"
|
||||
And I set the field "Entry title" to "How to make a table"
|
||||
And I set the field "Blog entry body" to "Table test"
|
||||
And I select the text in the "Blog entry body" Atto editor
|
||||
And I click on "Show more buttons" "button"
|
||||
When I click on "Table" "button"
|
||||
Then the ".moodle-dialogue-base .atto_form .borderstyle" "css_element" should be disabled
|
||||
And the ".moodle-dialogue-base .atto_form .bordersize" "css_element" should be disabled
|
||||
And the "borderColour" "radio" should be disabled
|
||||
And the field "borders" matches value "default"
|
||||
And the field "Theme default" matches value "1"
|
||||
And the field "Table width (in %)" matches value ""
|
||||
|
||||
@javascript
|
||||
Scenario: Edit a table restores settings
|
||||
Given the following config values are set as admin:
|
||||
| config | value | plugin |
|
||||
| allowborders | 1 | atto_table |
|
||||
| allowborderstyles | 1 | atto_table |
|
||||
| allowbordersize | 1 | atto_table |
|
||||
| allowbordercolour | 1 | atto_table |
|
||||
| allowbackgroundcolour | 1 | atto_table |
|
||||
| allowwidth | 1 | atto_table |
|
||||
And I log in as "admin"
|
||||
And I follow "Profile" in the user menu
|
||||
And I follow "Blog entries"
|
||||
And I follow "Add a new entry"
|
||||
And I set the field "Entry title" to "How to make a table"
|
||||
And I set the field "Blog entry body" to "<table><tr><td>Cell</td></tr></table>"
|
||||
And I select the text in the "Blog entry body" Atto editor
|
||||
And I click on "Show more buttons" "button"
|
||||
And I click on "Table" "button"
|
||||
And I click on "Edit table" "link"
|
||||
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
|
||||
And I set the field "Table width (in %)" to "100"
|
||||
And I set the field "Borders" to "Around table"
|
||||
And I set the field "Style of borders" to "Dashed"
|
||||
And I set the field "Size of borders" to "2"
|
||||
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .bordercolour" "css_element"
|
||||
And I press "Update table"
|
||||
And I select the text in the "Blog entry body" Atto editor
|
||||
And I click on "Table" "button"
|
||||
When I click on "Edit table" "link"
|
||||
# Check that the background colour is set correctly.
|
||||
Then the field with xpath "//label/input[@name='backgroundColour' and @value='#FFFFFF']" matches value "1"
|
||||
And the field "Table width (in %)" matches value "100"
|
||||
And the field "Borders" matches value "Around table"
|
||||
And the field "Style of borders" matches value "dashed"
|
||||
And the field "Size of borders" matches value "2"
|
||||
# Check that the border colour is set correctly.
|
||||
And the field with xpath "//label/input[@name='borderColour' and @value='#FFFFFF']" matches value "1"
|
||||
|
||||
@javascript
|
||||
Scenario: Create a table with background colour and width with border settings off
|
||||
Given the following config values are set as admin:
|
||||
@@ -67,7 +132,8 @@ Feature: Atto tables
|
||||
And I follow "Edit"
|
||||
And I click on "Show more buttons" "button"
|
||||
And I click on "HTML" "button"
|
||||
Then I should see "style=\"background-color:#FFFFFF;width:100%;\""
|
||||
Then I should see "background-color:rgb(255,255,255);"
|
||||
And I should see "width:100%;"
|
||||
|
||||
@javascript
|
||||
Scenario: Edit a table with background colour and width with border settings off
|
||||
@@ -102,7 +168,8 @@ Feature: Atto tables
|
||||
And I follow "Edit"
|
||||
And I click on "Show more buttons" "button"
|
||||
And I click on "HTML" "button"
|
||||
Then I should see "style=\"width:100%;background-color:rgb(255,255,255);\""
|
||||
Then I should see "background-color:rgb(255,255,255);"
|
||||
And I should see "width:100%;"
|
||||
|
||||
@javascript
|
||||
Scenario: Create a table with background colour and width with borders on
|
||||
@@ -137,7 +204,8 @@ Feature: Atto tables
|
||||
And I follow "Edit"
|
||||
And I click on "Show more buttons" "button"
|
||||
And I click on "HTML" "button"
|
||||
Then I should see "style=\"border:1px solid #FFFFFF;background-color:#FFFFFF;width:100%;\""
|
||||
Then I should see "background-color:rgb(255,255,255);"
|
||||
And I should see "width:100%;"
|
||||
|
||||
@javascript
|
||||
Scenario: Edit a table with background colour and width with borders on
|
||||
@@ -167,13 +235,14 @@ Feature: Atto tables
|
||||
Then ".moodle-dialogue-base .atto_form .customwidth" "css_element" should exist
|
||||
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
|
||||
And I set the field "Table width (in %)" to "100"
|
||||
And I set the field "Borders" to "Around table"
|
||||
And I set the field "Borders" to "Around each cell"
|
||||
And I press "Update table"
|
||||
And I press "Save changes"
|
||||
And I follow "Edit"
|
||||
And I click on "Show more buttons" "button"
|
||||
And I click on "HTML" "button"
|
||||
Then I should see "style=\"border:1px solid rgb(255,255,255);width:100%;background-color:rgb(255,255,255);\""
|
||||
Then I should see "background-color:rgb(255,255,255);"
|
||||
And I should see "width:100%;"
|
||||
|
||||
@javascript
|
||||
Scenario: Create a table with background colour and width with borders and border styling on
|
||||
@@ -203,13 +272,15 @@ Feature: Atto tables
|
||||
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
|
||||
And I set the field "Table width (in %)" to "100"
|
||||
And I set the field "Borders" to "Around table"
|
||||
And I set the field "Style of borders" to "dashed"
|
||||
And I set the field "Style of borders" to "Dashed"
|
||||
And I press "Create table"
|
||||
And I press "Save changes"
|
||||
And I follow "Edit"
|
||||
And I click on "Show more buttons" "button"
|
||||
And I click on "HTML" "button"
|
||||
Then I should see "style=\"border:1px dashed #FFFFFF;background-color:#FFFFFF;width:100%;\""
|
||||
Then I should see "background-color:rgb(255,255,255);"
|
||||
And I should see "width:100%;"
|
||||
And I should see "border-style:dashed;"
|
||||
|
||||
@javascript
|
||||
Scenario: Edit a table with background colour and width with borders and border styling on
|
||||
@@ -240,13 +311,15 @@ Feature: Atto tables
|
||||
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
|
||||
And I set the field "Table width (in %)" to "100"
|
||||
And I set the field "Borders" to "Around table"
|
||||
And I set the field "Style of borders" to "dashed"
|
||||
And I set the field "Style of borders" to "Dashed"
|
||||
And I press "Update table"
|
||||
And I press "Save changes"
|
||||
And I follow "Edit"
|
||||
And I click on "Show more buttons" "button"
|
||||
And I click on "HTML" "button"
|
||||
Then I should see "style=\"border:1px dashed rgb(255,255,255);width:100%;background-color:rgb(255,255,255);\""
|
||||
Then I should see "background-color:rgb(255,255,255);"
|
||||
And I should see "width:100%;"
|
||||
And I should see "border-style:dashed;"
|
||||
|
||||
@javascript
|
||||
Scenario: Create a table with background colour and width with borders, border styling, and border size on
|
||||
@@ -276,14 +349,17 @@ Feature: Atto tables
|
||||
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
|
||||
And I set the field "Table width (in %)" to "100"
|
||||
And I set the field "Borders" to "Around table"
|
||||
And I set the field "Style of borders" to "dashed"
|
||||
And I set the field "Style of borders" to "Dashed"
|
||||
And I set the field "Size of borders" to "2"
|
||||
And I press "Create table"
|
||||
And I press "Save changes"
|
||||
And I follow "Edit"
|
||||
And I click on "Show more buttons" "button"
|
||||
And I click on "HTML" "button"
|
||||
Then I should see "style=\"border:2px dashed #FFFFFF;background-color:#FFFFFF;width:100%;\""
|
||||
Then I should see "background-color:rgb(255,255,255);"
|
||||
And I should see "width:100%;"
|
||||
And I should see "border-style:dashed;"
|
||||
And I should see "border-width:2px;"
|
||||
|
||||
@javascript
|
||||
Scenario: Edit a table with background colour and width with borders, border styling, and border size on
|
||||
@@ -314,14 +390,17 @@ Feature: Atto tables
|
||||
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
|
||||
And I set the field "Table width (in %)" to "100"
|
||||
And I set the field "Borders" to "Around table"
|
||||
And I set the field "Style of borders" to "dashed"
|
||||
And I set the field "Style of borders" to "Dashed"
|
||||
And I set the field "Size of borders" to "2"
|
||||
And I press "Update table"
|
||||
And I press "Save changes"
|
||||
And I follow "Edit"
|
||||
And I click on "Show more buttons" "button"
|
||||
And I click on "HTML" "button"
|
||||
Then I should see "style=\"border:2px dashed rgb(255,255,255);width:100%;background-color:rgb(255,255,255);\""
|
||||
Then I should see "background-color:rgb(255,255,255);"
|
||||
And I should see "width:100%;"
|
||||
And I should see "border-style:dashed;"
|
||||
And I should see "border-width:2px;"
|
||||
|
||||
@javascript
|
||||
Scenario: Create a table with all settings on
|
||||
@@ -351,7 +430,7 @@ Feature: Atto tables
|
||||
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
|
||||
And I set the field "Table width (in %)" to "100"
|
||||
And I set the field "Borders" to "Around table"
|
||||
And I set the field "Style of borders" to "dashed"
|
||||
And I set the field "Style of borders" to "Dashed"
|
||||
And I set the field "Size of borders" to "2"
|
||||
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .bordercolour" "css_element"
|
||||
And I press "Create table"
|
||||
@@ -359,7 +438,11 @@ Feature: Atto tables
|
||||
And I follow "Edit"
|
||||
And I click on "Show more buttons" "button"
|
||||
And I click on "HTML" "button"
|
||||
Then I should see "style=\"border:2px dashed #FFFFFF;background-color:#FFFFFF;width:100%;\""
|
||||
Then I should see "background-color:rgb(255,255,255);"
|
||||
And I should see "width:100%;"
|
||||
And I should see "dashed"
|
||||
And I should see "2px"
|
||||
And I should see "rgb(255,255,255)"
|
||||
|
||||
@javascript
|
||||
Scenario: Edit a table with background colour and width with borders, border styling, and border size on
|
||||
@@ -387,15 +470,19 @@ Feature: Atto tables
|
||||
Then ".moodle-dialogue-base .atto_form .bordercolour" "css_element" should exist
|
||||
Then ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element" should exist
|
||||
Then ".moodle-dialogue-base .atto_form .customwidth" "css_element" should exist
|
||||
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
|
||||
And I click on "FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .backgroundcolour" "css_element"
|
||||
And I set the field "Table width (in %)" to "100"
|
||||
And I set the field "Borders" to "Around table"
|
||||
And I set the field "Style of borders" to "dashed"
|
||||
And I set the field "Style of borders" to "Dashed"
|
||||
And I set the field "Size of borders" to "2"
|
||||
And I click on "#FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .bordercolour" "css_element"
|
||||
And I click on "FFFFFF" "radio" in the ".moodle-dialogue-base .atto_form .bordercolour" "css_element"
|
||||
And I press "Update table"
|
||||
And I press "Save changes"
|
||||
And I follow "Edit"
|
||||
And I click on "Show more buttons" "button"
|
||||
And I click on "HTML" "button"
|
||||
Then I should see "style=\"border:2px dashed rgb(255,255,255);width:100%;background-color:rgb(255,255,255);\""
|
||||
Then I should see "background-color:rgb(255,255,255);"
|
||||
And I should see "width:100%;"
|
||||
And I should see "dashed"
|
||||
And I should see "2px"
|
||||
And I should see "rgb(255,255,255)"
|
||||
|
||||
Vendored
+158
-123
@@ -35,11 +35,11 @@ YUI.add('moodle-atto_table-button', function (Y, NAME) {
|
||||
|
||||
var COMPONENT = 'atto_table',
|
||||
DEFAULT = {
|
||||
BORDERSTYLE: 'solid',
|
||||
BORDERSTYLE: 'inherit',
|
||||
BORDERWIDTH: '1'
|
||||
},
|
||||
DIALOGUE = {
|
||||
WIDTH: '450px'
|
||||
WIDTH: '480px'
|
||||
},
|
||||
TEMPLATE = '' +
|
||||
'<form class="{{CSS.FORM}}">' +
|
||||
@@ -78,9 +78,8 @@ var COMPONENT = 'atto_table',
|
||||
'<legend class="mdl-align">{{get_string "appearance" component}}</legend>' +
|
||||
'{{#if allowBorders}}' +
|
||||
'<label for="{{elementid}}_atto_table_borders" class="sameline">{{get_string "borders" component}}</label>' +
|
||||
'<select class="{{CSS.BORDERS}}" id="{{elementid}}_atto_table_borders">' +
|
||||
'<select name="borders" class="{{CSS.BORDERS}}" id="{{elementid}}_atto_table_borders">' +
|
||||
'<option value="default">{{get_string "themedefault" component}}' + '</option>' +
|
||||
'<option value="none">{{get_string "noborder" component}}' + '</option>' +
|
||||
'<option value="outer">{{get_string "outer" component}}' + '</option>' +
|
||||
'<option value="all">{{get_string "all" component}}' + '</option>' +
|
||||
'</select>' +
|
||||
@@ -88,9 +87,10 @@ var COMPONENT = 'atto_table',
|
||||
'{{#if allowBorderStyles}}' +
|
||||
'<label for="{{elementid}}_atto_table_borderstyle" class="sameline">' +
|
||||
'{{get_string "borderstyles" component}}</label>' +
|
||||
'<select class="{{CSS.BORDERSTYLE}}" id="{{elementid}}_atto_table_borderstyle">' +
|
||||
'<select name="borderstyles" class="{{CSS.BORDERSTYLE}}" id="{{elementid}}_atto_table_borderstyle">' +
|
||||
'<option value="inherit">{{get_string "inherit" component}}</option>' +
|
||||
'{{#each borderStyles}}' +
|
||||
'<option value="' + '{{this}}' + '">' + '{{this}}' + '</option>' +
|
||||
'<option value="' + '{{this}}' + '">' + '{{get_string this ../component}}' + '</option>' +
|
||||
'{{/each}}' +
|
||||
'</select>' +
|
||||
'<br>' +
|
||||
@@ -98,7 +98,7 @@ var COMPONENT = 'atto_table',
|
||||
'{{#if allowBorderSize}}' +
|
||||
'<label for="{{elementid}}_atto_table_bordersize" class="sameline">' +
|
||||
'{{get_string "bordersize" component}}</label>' +
|
||||
'<input id="{{elementid}}_atto_table_bordersize" class="{{CSS.BORDERSIZE}}"' +
|
||||
'<input name="bordersize" id="{{elementid}}_atto_table_bordersize" class="{{CSS.BORDERSIZE}}"' +
|
||||
'type="number" value="1" size="8" min="1" max="50"/>' +
|
||||
'<label style="display: inline-block;">{{CSS.BORDERSIZEUNIT}}</label>' +
|
||||
'<br>' +
|
||||
@@ -112,10 +112,10 @@ var COMPONENT = 'atto_table',
|
||||
'style="background-color:transparent;color:transparent">' +
|
||||
|
||||
'<input id="{{../elementid}}_atto_table_bordercolour_-1"' +
|
||||
'type="radio" name="borderColour" value="transparent" checked="checked"'+
|
||||
'title="{{get_string "transparent" component}}"></input>' +
|
||||
'type="radio" name="borderColour" value="none" checked="checked"'+
|
||||
'title="{{get_string "themedefault" component}}"></input>' +
|
||||
|
||||
'{{get_string "transparent" component}}' +
|
||||
'{{get_string "themedefault" component}}' +
|
||||
'</label>' +
|
||||
'{{#each availableColours}}' +
|
||||
'<label for="{{../elementid}}_atto_table_bordercolour_{{@index}}"' +
|
||||
@@ -140,10 +140,10 @@ var COMPONENT = 'atto_table',
|
||||
'style="background-color:transparent;color:transparent">' +
|
||||
|
||||
'<input id="{{../elementid}}_atto_table_backgroundcolour_-1"' +
|
||||
'type="radio" name="backgroundColour" value="transparent" checked="checked"'+
|
||||
'title="{{get_string "transparent" component}}"></input>' +
|
||||
'type="radio" name="backgroundColour" value="none" checked="checked"'+
|
||||
'title="{{get_string "themedefault" component}}"></input>' +
|
||||
|
||||
'{{get_string "transparent" component}}' +
|
||||
'{{get_string "themedefault" component}}' +
|
||||
'</label>' +
|
||||
|
||||
'{{#each availableColours}}' +
|
||||
@@ -162,7 +162,7 @@ var COMPONENT = 'atto_table',
|
||||
'{{#if allowWidth}}' +
|
||||
'<label for="{{elementid}}_atto_table_width" class="sameline">' +
|
||||
'{{get_string "width" component}}</label>' +
|
||||
'<input id="{{elementid}}_atto_table_width" class="{{CSS.WIDTH}}" size="8" type="number" min="0" max="100"/>' +
|
||||
'<input name="width" id="{{elementid}}_atto_table_width" class="{{CSS.WIDTH}}" size="8" type="number" min="0" max="100"/>' +
|
||||
'<label style="display: inline-block;">{{CSS.WIDTHUNIT}}</label>' +
|
||||
'<br>' +
|
||||
'{{/if}}' +
|
||||
@@ -289,6 +289,8 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
// Set the dialogue content, and then show the dialogue.
|
||||
dialogue.set('bodyContent', this._getDialogueContent(false))
|
||||
.show();
|
||||
|
||||
this._updateAvailableSettings();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -334,7 +336,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
*/
|
||||
_getDialogueContent: function(edit) {
|
||||
var template = Y.Handlebars.compile(TEMPLATE);
|
||||
var availableColours = this.get('availableColors');
|
||||
var allowBorders = this.get('allowBorders');
|
||||
|
||||
this._content = Y.Node.create(template({
|
||||
CSS: CSS,
|
||||
@@ -343,13 +345,13 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
edit: edit,
|
||||
nonedit: !edit,
|
||||
allowStyling: this.get('allowStyling'),
|
||||
allowBorders: this.get('allowBorders'),
|
||||
allowBorders: allowBorders,
|
||||
allowBorderStyles: this.get('allowBorderStyles'),
|
||||
borderStyles: this.get('borderStyles'),
|
||||
allowBorderSize: this.get('allowBorderSize'),
|
||||
allowBorderColour: this.get('allowBorderColour'),
|
||||
allowBackgroundColour: this.get('allowBackgroundColour'),
|
||||
availableColours: availableColours,
|
||||
availableColours: this.get('availableColors'),
|
||||
allowWidth: this.get('allowWidth')
|
||||
}));
|
||||
|
||||
@@ -360,9 +362,48 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
this._content.one('.submit').on('click', this._setTable, this);
|
||||
}
|
||||
|
||||
if (allowBorders) {
|
||||
this._content.one('[name="borders"]').on('change', this._updateAvailableSettings, this);
|
||||
}
|
||||
|
||||
return this._content;
|
||||
},
|
||||
|
||||
/**
|
||||
* Disables options within the dialogue if they shouldn't be available.
|
||||
* E.g.
|
||||
* If borders are set to "Theme default" then the border size, style and
|
||||
* colour options are disabled.
|
||||
*
|
||||
* @method _updateAvailableSettings
|
||||
* @private
|
||||
*/
|
||||
_updateAvailableSettings: function() {
|
||||
var tableForm = this._content,
|
||||
enableBorders = tableForm.one('[name="borders"]'),
|
||||
borderStyle = tableForm.one('[name="borderstyles"]'),
|
||||
borderSize = tableForm.one('[name="bordersize"]'),
|
||||
borderColour = tableForm.all('[name="borderColour"]'),
|
||||
disabledValue = 'removeAttribute';
|
||||
|
||||
if (enableBorders.get('value') === 'default') {
|
||||
disabledValue = 'setAttribute';
|
||||
}
|
||||
|
||||
if (borderStyle) {
|
||||
borderStyle[disabledValue]('disabled');
|
||||
}
|
||||
|
||||
if (borderSize) {
|
||||
borderSize[disabledValue]('disabled');
|
||||
}
|
||||
|
||||
if (borderColour) {
|
||||
borderColour[disabledValue]('disabled');
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Given the current selection, return a table cell suitable for table editing
|
||||
* purposes, i.e. the first table cell selected, or the first cell in the table
|
||||
@@ -436,10 +477,6 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
borderstyle,
|
||||
bordercolour,
|
||||
backgroundcolour,
|
||||
backgroundcolourvalue = '#FFFFFF',
|
||||
borderSizeValue = '1',
|
||||
borderStyleValue = 'solid',
|
||||
borderhex = '#FFFFFF',
|
||||
table,
|
||||
width,
|
||||
captionnode;
|
||||
@@ -461,51 +498,16 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
backgroundcolour = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.SELECTEDBACKGROUNDCOLOUR);
|
||||
width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
|
||||
|
||||
if (bordercolour) {
|
||||
borderhex = bordercolour.get('value');
|
||||
}
|
||||
|
||||
if (bordersize) {
|
||||
borderSizeValue = bordersize.get('value');
|
||||
}
|
||||
|
||||
if (borderstyle) {
|
||||
borderStyleValue = borderstyle.get('value');
|
||||
}
|
||||
|
||||
if (backgroundcolour) {
|
||||
backgroundcolourvalue = backgroundcolour.get('value');
|
||||
}
|
||||
|
||||
table = this._lastTarget.ancestor('table');
|
||||
// Clear the inline border styling
|
||||
table.removeAttribute('style');
|
||||
table.all('td, th').each(function() {
|
||||
this.removeAttribute('style');
|
||||
this._setAppearance(table, {
|
||||
width: width,
|
||||
borders: borders,
|
||||
borderColour: bordercolour,
|
||||
borderSize: bordersize,
|
||||
borderStyle: borderstyle,
|
||||
backgroundColour: backgroundcolour
|
||||
});
|
||||
|
||||
if (borders) {
|
||||
if (borders.get('value') === 'outer') {
|
||||
table.setStyle('border', borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
|
||||
borderStyleValue + ' ' + borderhex);
|
||||
} else if (borders.get('value') === 'all') {
|
||||
table.all('td, th').each(function() {
|
||||
this.setStyle('border', borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
|
||||
borderStyleValue + ' ' + borderhex);
|
||||
});
|
||||
} else if (borders.get('value') === 'none') {
|
||||
table.setStyle('border', 'none');
|
||||
}
|
||||
}
|
||||
|
||||
if (width && width.get('value')) {
|
||||
table.setStyle('width', width.get('value') + CSS.WIDTHUNIT);
|
||||
}
|
||||
|
||||
if (backgroundcolourvalue !== '') {
|
||||
table.setStyle('background-color', backgroundcolourvalue);
|
||||
}
|
||||
|
||||
captionnode = table.one('caption');
|
||||
if (!captionnode) {
|
||||
captionnode = Y.Node.create('<caption></caption>');
|
||||
@@ -589,14 +591,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
var caption,
|
||||
captionposition,
|
||||
borders,
|
||||
bordertable = '',
|
||||
bordercell = '',
|
||||
bordersize,
|
||||
borderstyle,
|
||||
bordercolour,
|
||||
borderSizeValue = '1',
|
||||
borderStyleValue = 'solid',
|
||||
borderhex = '#FFFFFF',
|
||||
rows,
|
||||
cols,
|
||||
headers,
|
||||
@@ -624,52 +621,13 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
headers = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.HEADERS);
|
||||
width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
|
||||
|
||||
if (bordercolour) {
|
||||
borderhex = bordercolour.get('value');
|
||||
}
|
||||
|
||||
if (bordersize) {
|
||||
borderSizeValue = bordersize.get('value');
|
||||
}
|
||||
|
||||
if (borderstyle) {
|
||||
borderStyleValue = borderstyle.get('value');
|
||||
}
|
||||
|
||||
if (borders) {
|
||||
if (borders.get('value') === 'outer') {
|
||||
bordertable = ' style="border: ' + borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
|
||||
borderStyleValue + ' ' + borderhex + ';"';
|
||||
} else if (borders.get('value') === 'all') {
|
||||
bordercell = ' style="border: ' + borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
|
||||
borderStyleValue + ' ' + borderhex + ';"';
|
||||
}
|
||||
}
|
||||
|
||||
if (backgroundcolour) {
|
||||
if (bordertable !== '') {
|
||||
bordertable = bordertable.substring(0, bordertable.length-1) +
|
||||
'background-color: ' + backgroundcolour.get('value') + ';"';
|
||||
} else {
|
||||
bordertable = ' style="background-color: ' + backgroundcolour.get('value') + ';"';
|
||||
}
|
||||
}
|
||||
|
||||
if (width && width.get('value')) {
|
||||
if (bordertable !== '') {
|
||||
bordertable = bordertable.substring(0, bordertable.length-1) + 'width: ' +
|
||||
width.get('value') + CSS.WIDTHUNIT + ';"';
|
||||
} else {
|
||||
bordertable = ' style="width: ' + width.get('value') + CSS.WIDTHUNIT + ';"';
|
||||
}
|
||||
}
|
||||
|
||||
// Set the selection.
|
||||
this.get('host').setSelection(this._currentSelection);
|
||||
|
||||
// Note there are some spaces inserted in the cells and before and after, so that users have somewhere to click.
|
||||
var nl = "\n";
|
||||
tablehtml = '<br/>' + nl + '<table' + bordertable + '>' + nl;
|
||||
var tableId = Y.guid();
|
||||
tablehtml = '<br/>' + nl + '<table id="' + tableId + '">' + nl;
|
||||
|
||||
var captionstyle = '';
|
||||
if (captionposition.get('value')) {
|
||||
@@ -681,7 +639,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
i = 1;
|
||||
tablehtml += '<thead>' + nl + '<tr>' + nl;
|
||||
for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
|
||||
tablehtml += '<th scope="col"' + bordercell + '></th>' + nl;
|
||||
tablehtml += '<th scope="col"></th>' + nl;
|
||||
}
|
||||
tablehtml += '</tr>' + nl + '</thead>' + nl;
|
||||
}
|
||||
@@ -690,9 +648,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
tablehtml += '<tr>' + nl;
|
||||
for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
|
||||
if (j === 0 && (headers.get('value') === 'rows' || headers.get('value') === 'both')) {
|
||||
tablehtml += '<th scope="row"' + bordercell + '></th>' + nl;
|
||||
tablehtml += '<th scope="row"></th>' + nl;
|
||||
} else {
|
||||
tablehtml += '<td ' + bordercell + '></td>' + nl;
|
||||
tablehtml += '<td ></td>' + nl;
|
||||
}
|
||||
}
|
||||
tablehtml += '</tr>' + nl;
|
||||
@@ -702,6 +660,17 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
|
||||
this.get('host').insertContentAtFocusPoint(tablehtml);
|
||||
|
||||
var tableNode = Y.one('#' + tableId);
|
||||
this._setAppearance(tableNode, {
|
||||
width: width,
|
||||
borders: borders,
|
||||
borderColour: bordercolour,
|
||||
borderSize: bordersize,
|
||||
borderStyle: borderstyle,
|
||||
backgroundColour: backgroundcolour
|
||||
});
|
||||
tableNode.removeAttribute('id');
|
||||
|
||||
// Mark the content as updated.
|
||||
this.markUpdated();
|
||||
},
|
||||
@@ -1148,6 +1117,73 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the appropriate styles on the given table node according to
|
||||
* the provided configuration.
|
||||
*
|
||||
* @method _setAppearance
|
||||
* @param {Node} The table node to be modified.
|
||||
* @param {Object} Configuration object (associative array) containing the form nodes for
|
||||
* border styling.
|
||||
* @private
|
||||
*/
|
||||
_setAppearance: function(tableNode, configuration) {
|
||||
var borderhex,
|
||||
borderSizeValue,
|
||||
borderStyleValue,
|
||||
backgroundcolourvalue;
|
||||
|
||||
if (configuration.borderColour) {
|
||||
borderhex = configuration.borderColour.get('value');
|
||||
}
|
||||
|
||||
if (configuration.borderSize) {
|
||||
borderSizeValue = configuration.borderSize.get('value');
|
||||
}
|
||||
|
||||
if (configuration.borderStyle) {
|
||||
borderStyleValue = configuration.borderStyle.get('value');
|
||||
}
|
||||
|
||||
if (configuration.backgroundColour) {
|
||||
backgroundcolourvalue = configuration.backgroundColour.get('value');
|
||||
}
|
||||
|
||||
// Clear the inline border styling
|
||||
tableNode.removeAttribute('style');
|
||||
tableNode.all('td, th').each(function(cell) {
|
||||
cell.removeAttribute('style');
|
||||
}, this);
|
||||
|
||||
if (configuration.borders) {
|
||||
if (configuration.borders.get('value') === 'outer') {
|
||||
tableNode.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
|
||||
tableNode.setStyle('borderStyle', borderStyleValue);
|
||||
|
||||
if (borderhex !== 'none') {
|
||||
tableNode.setStyle('borderColor', borderhex);
|
||||
}
|
||||
} else if (configuration.borders.get('value') === 'all') {
|
||||
tableNode.all('td, th').each(function(cell) {
|
||||
cell.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
|
||||
cell.setStyle('borderStyle', borderStyleValue);
|
||||
|
||||
if (borderhex !== 'none') {
|
||||
cell.setStyle('borderColor', borderhex);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
if (backgroundcolourvalue !== 'none') {
|
||||
tableNode.setStyle('backgroundColor', backgroundcolourvalue);
|
||||
}
|
||||
|
||||
if (configuration.width && configuration.width.get('value')) {
|
||||
tableNode.setStyle('width', configuration.width.get('value') + CSS.WIDTHUNIT);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit table (show the dialogue).
|
||||
*
|
||||
@@ -1174,7 +1210,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
backgroundcolours = node.all(SELECTORS.BACKGROUNDCOLOURS),
|
||||
width = node.one(SELECTORS.WIDTH),
|
||||
table = this._lastTarget.ancestor('table'),
|
||||
captionnode = table.one('caption');
|
||||
captionnode = table.one('caption'),
|
||||
hexColour,
|
||||
matchedInput;
|
||||
|
||||
if (captionnode) {
|
||||
captioninput.set('value', captionnode.getHTML());
|
||||
@@ -1193,9 +1231,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
captionpositioninput.set('value', '');
|
||||
}
|
||||
|
||||
if (table.getStyle('background-color') !== 'transparent' && this.get('allowBackgroundColour')) {
|
||||
var hexColour = Y.Color.toHex(table.getStyle('background-color'));
|
||||
var matchedInput = backgroundcolours.filter('[value="' + hexColour + '"]');
|
||||
if (table.getStyle('backgroundColor') && this.get('allowBackgroundColour')) {
|
||||
hexColour = Y.Color.toHex(table.getStyle('backgroundColor'));
|
||||
matchedInput = backgroundcolours.filter('[value="' + hexColour + '"]');
|
||||
|
||||
if (matchedInput) {
|
||||
matchedInput.set("checked", true);
|
||||
@@ -1207,11 +1245,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
borderConfiguration = this._getBorderConfiguration(table);
|
||||
|
||||
if (borderConfiguration) {
|
||||
if (borderConfiguration.borderStyle && borderConfiguration.borderStyle === 'none') {
|
||||
borderValue = 'none';
|
||||
} else {
|
||||
borderValue = 'outer';
|
||||
}
|
||||
borderValue = 'outer';
|
||||
} else {
|
||||
borderConfiguration = this._getBorderConfiguration(table.one('td'));
|
||||
if (borderConfiguration) {
|
||||
@@ -1226,8 +1260,8 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
bordersize.set('value', borderSize);
|
||||
borderinput.set('value', borderValue);
|
||||
|
||||
var hexColour = borderConfiguration.borderColor;
|
||||
var matchedInput = bordercolours.filter('[value="' + hexColour + '"]');
|
||||
hexColour = borderConfiguration.borderColor;
|
||||
matchedInput = bordercolours.filter('[value="' + hexColour + '"]');
|
||||
|
||||
if (matchedInput) {
|
||||
matchedInput.set("checked", true);
|
||||
@@ -1244,6 +1278,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
}
|
||||
headersinput.set('value', headersvalue);
|
||||
dialogue.set('bodyContent', node).show();
|
||||
this._updateAvailableSettings();
|
||||
},
|
||||
|
||||
|
||||
|
||||
Vendored
+4
-4
File diff suppressed because one or more lines are too long
Vendored
+158
-123
@@ -35,11 +35,11 @@ YUI.add('moodle-atto_table-button', function (Y, NAME) {
|
||||
|
||||
var COMPONENT = 'atto_table',
|
||||
DEFAULT = {
|
||||
BORDERSTYLE: 'solid',
|
||||
BORDERSTYLE: 'inherit',
|
||||
BORDERWIDTH: '1'
|
||||
},
|
||||
DIALOGUE = {
|
||||
WIDTH: '450px'
|
||||
WIDTH: '480px'
|
||||
},
|
||||
TEMPLATE = '' +
|
||||
'<form class="{{CSS.FORM}}">' +
|
||||
@@ -78,9 +78,8 @@ var COMPONENT = 'atto_table',
|
||||
'<legend class="mdl-align">{{get_string "appearance" component}}</legend>' +
|
||||
'{{#if allowBorders}}' +
|
||||
'<label for="{{elementid}}_atto_table_borders" class="sameline">{{get_string "borders" component}}</label>' +
|
||||
'<select class="{{CSS.BORDERS}}" id="{{elementid}}_atto_table_borders">' +
|
||||
'<select name="borders" class="{{CSS.BORDERS}}" id="{{elementid}}_atto_table_borders">' +
|
||||
'<option value="default">{{get_string "themedefault" component}}' + '</option>' +
|
||||
'<option value="none">{{get_string "noborder" component}}' + '</option>' +
|
||||
'<option value="outer">{{get_string "outer" component}}' + '</option>' +
|
||||
'<option value="all">{{get_string "all" component}}' + '</option>' +
|
||||
'</select>' +
|
||||
@@ -88,9 +87,10 @@ var COMPONENT = 'atto_table',
|
||||
'{{#if allowBorderStyles}}' +
|
||||
'<label for="{{elementid}}_atto_table_borderstyle" class="sameline">' +
|
||||
'{{get_string "borderstyles" component}}</label>' +
|
||||
'<select class="{{CSS.BORDERSTYLE}}" id="{{elementid}}_atto_table_borderstyle">' +
|
||||
'<select name="borderstyles" class="{{CSS.BORDERSTYLE}}" id="{{elementid}}_atto_table_borderstyle">' +
|
||||
'<option value="inherit">{{get_string "inherit" component}}</option>' +
|
||||
'{{#each borderStyles}}' +
|
||||
'<option value="' + '{{this}}' + '">' + '{{this}}' + '</option>' +
|
||||
'<option value="' + '{{this}}' + '">' + '{{get_string this ../component}}' + '</option>' +
|
||||
'{{/each}}' +
|
||||
'</select>' +
|
||||
'<br>' +
|
||||
@@ -98,7 +98,7 @@ var COMPONENT = 'atto_table',
|
||||
'{{#if allowBorderSize}}' +
|
||||
'<label for="{{elementid}}_atto_table_bordersize" class="sameline">' +
|
||||
'{{get_string "bordersize" component}}</label>' +
|
||||
'<input id="{{elementid}}_atto_table_bordersize" class="{{CSS.BORDERSIZE}}"' +
|
||||
'<input name="bordersize" id="{{elementid}}_atto_table_bordersize" class="{{CSS.BORDERSIZE}}"' +
|
||||
'type="number" value="1" size="8" min="1" max="50"/>' +
|
||||
'<label style="display: inline-block;">{{CSS.BORDERSIZEUNIT}}</label>' +
|
||||
'<br>' +
|
||||
@@ -112,10 +112,10 @@ var COMPONENT = 'atto_table',
|
||||
'style="background-color:transparent;color:transparent">' +
|
||||
|
||||
'<input id="{{../elementid}}_atto_table_bordercolour_-1"' +
|
||||
'type="radio" name="borderColour" value="transparent" checked="checked"'+
|
||||
'title="{{get_string "transparent" component}}"></input>' +
|
||||
'type="radio" name="borderColour" value="none" checked="checked"'+
|
||||
'title="{{get_string "themedefault" component}}"></input>' +
|
||||
|
||||
'{{get_string "transparent" component}}' +
|
||||
'{{get_string "themedefault" component}}' +
|
||||
'</label>' +
|
||||
'{{#each availableColours}}' +
|
||||
'<label for="{{../elementid}}_atto_table_bordercolour_{{@index}}"' +
|
||||
@@ -140,10 +140,10 @@ var COMPONENT = 'atto_table',
|
||||
'style="background-color:transparent;color:transparent">' +
|
||||
|
||||
'<input id="{{../elementid}}_atto_table_backgroundcolour_-1"' +
|
||||
'type="radio" name="backgroundColour" value="transparent" checked="checked"'+
|
||||
'title="{{get_string "transparent" component}}"></input>' +
|
||||
'type="radio" name="backgroundColour" value="none" checked="checked"'+
|
||||
'title="{{get_string "themedefault" component}}"></input>' +
|
||||
|
||||
'{{get_string "transparent" component}}' +
|
||||
'{{get_string "themedefault" component}}' +
|
||||
'</label>' +
|
||||
|
||||
'{{#each availableColours}}' +
|
||||
@@ -162,7 +162,7 @@ var COMPONENT = 'atto_table',
|
||||
'{{#if allowWidth}}' +
|
||||
'<label for="{{elementid}}_atto_table_width" class="sameline">' +
|
||||
'{{get_string "width" component}}</label>' +
|
||||
'<input id="{{elementid}}_atto_table_width" class="{{CSS.WIDTH}}" size="8" type="number" min="0" max="100"/>' +
|
||||
'<input name="width" id="{{elementid}}_atto_table_width" class="{{CSS.WIDTH}}" size="8" type="number" min="0" max="100"/>' +
|
||||
'<label style="display: inline-block;">{{CSS.WIDTHUNIT}}</label>' +
|
||||
'<br>' +
|
||||
'{{/if}}' +
|
||||
@@ -289,6 +289,8 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
// Set the dialogue content, and then show the dialogue.
|
||||
dialogue.set('bodyContent', this._getDialogueContent(false))
|
||||
.show();
|
||||
|
||||
this._updateAvailableSettings();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -334,7 +336,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
*/
|
||||
_getDialogueContent: function(edit) {
|
||||
var template = Y.Handlebars.compile(TEMPLATE);
|
||||
var availableColours = this.get('availableColors');
|
||||
var allowBorders = this.get('allowBorders');
|
||||
|
||||
this._content = Y.Node.create(template({
|
||||
CSS: CSS,
|
||||
@@ -343,13 +345,13 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
edit: edit,
|
||||
nonedit: !edit,
|
||||
allowStyling: this.get('allowStyling'),
|
||||
allowBorders: this.get('allowBorders'),
|
||||
allowBorders: allowBorders,
|
||||
allowBorderStyles: this.get('allowBorderStyles'),
|
||||
borderStyles: this.get('borderStyles'),
|
||||
allowBorderSize: this.get('allowBorderSize'),
|
||||
allowBorderColour: this.get('allowBorderColour'),
|
||||
allowBackgroundColour: this.get('allowBackgroundColour'),
|
||||
availableColours: availableColours,
|
||||
availableColours: this.get('availableColors'),
|
||||
allowWidth: this.get('allowWidth')
|
||||
}));
|
||||
|
||||
@@ -360,9 +362,48 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
this._content.one('.submit').on('click', this._setTable, this);
|
||||
}
|
||||
|
||||
if (allowBorders) {
|
||||
this._content.one('[name="borders"]').on('change', this._updateAvailableSettings, this);
|
||||
}
|
||||
|
||||
return this._content;
|
||||
},
|
||||
|
||||
/**
|
||||
* Disables options within the dialogue if they shouldn't be available.
|
||||
* E.g.
|
||||
* If borders are set to "Theme default" then the border size, style and
|
||||
* colour options are disabled.
|
||||
*
|
||||
* @method _updateAvailableSettings
|
||||
* @private
|
||||
*/
|
||||
_updateAvailableSettings: function() {
|
||||
var tableForm = this._content,
|
||||
enableBorders = tableForm.one('[name="borders"]'),
|
||||
borderStyle = tableForm.one('[name="borderstyles"]'),
|
||||
borderSize = tableForm.one('[name="bordersize"]'),
|
||||
borderColour = tableForm.all('[name="borderColour"]'),
|
||||
disabledValue = 'removeAttribute';
|
||||
|
||||
if (enableBorders.get('value') === 'default') {
|
||||
disabledValue = 'setAttribute';
|
||||
}
|
||||
|
||||
if (borderStyle) {
|
||||
borderStyle[disabledValue]('disabled');
|
||||
}
|
||||
|
||||
if (borderSize) {
|
||||
borderSize[disabledValue]('disabled');
|
||||
}
|
||||
|
||||
if (borderColour) {
|
||||
borderColour[disabledValue]('disabled');
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Given the current selection, return a table cell suitable for table editing
|
||||
* purposes, i.e. the first table cell selected, or the first cell in the table
|
||||
@@ -436,10 +477,6 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
borderstyle,
|
||||
bordercolour,
|
||||
backgroundcolour,
|
||||
backgroundcolourvalue = '#FFFFFF',
|
||||
borderSizeValue = '1',
|
||||
borderStyleValue = 'solid',
|
||||
borderhex = '#FFFFFF',
|
||||
table,
|
||||
width,
|
||||
captionnode;
|
||||
@@ -461,51 +498,16 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
backgroundcolour = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.SELECTEDBACKGROUNDCOLOUR);
|
||||
width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
|
||||
|
||||
if (bordercolour) {
|
||||
borderhex = bordercolour.get('value');
|
||||
}
|
||||
|
||||
if (bordersize) {
|
||||
borderSizeValue = bordersize.get('value');
|
||||
}
|
||||
|
||||
if (borderstyle) {
|
||||
borderStyleValue = borderstyle.get('value');
|
||||
}
|
||||
|
||||
if (backgroundcolour) {
|
||||
backgroundcolourvalue = backgroundcolour.get('value');
|
||||
}
|
||||
|
||||
table = this._lastTarget.ancestor('table');
|
||||
// Clear the inline border styling
|
||||
table.removeAttribute('style');
|
||||
table.all('td, th').each(function() {
|
||||
this.removeAttribute('style');
|
||||
this._setAppearance(table, {
|
||||
width: width,
|
||||
borders: borders,
|
||||
borderColour: bordercolour,
|
||||
borderSize: bordersize,
|
||||
borderStyle: borderstyle,
|
||||
backgroundColour: backgroundcolour
|
||||
});
|
||||
|
||||
if (borders) {
|
||||
if (borders.get('value') === 'outer') {
|
||||
table.setStyle('border', borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
|
||||
borderStyleValue + ' ' + borderhex);
|
||||
} else if (borders.get('value') === 'all') {
|
||||
table.all('td, th').each(function() {
|
||||
this.setStyle('border', borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
|
||||
borderStyleValue + ' ' + borderhex);
|
||||
});
|
||||
} else if (borders.get('value') === 'none') {
|
||||
table.setStyle('border', 'none');
|
||||
}
|
||||
}
|
||||
|
||||
if (width && width.get('value')) {
|
||||
table.setStyle('width', width.get('value') + CSS.WIDTHUNIT);
|
||||
}
|
||||
|
||||
if (backgroundcolourvalue !== '') {
|
||||
table.setStyle('background-color', backgroundcolourvalue);
|
||||
}
|
||||
|
||||
captionnode = table.one('caption');
|
||||
if (!captionnode) {
|
||||
captionnode = Y.Node.create('<caption></caption>');
|
||||
@@ -589,14 +591,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
var caption,
|
||||
captionposition,
|
||||
borders,
|
||||
bordertable = '',
|
||||
bordercell = '',
|
||||
bordersize,
|
||||
borderstyle,
|
||||
bordercolour,
|
||||
borderSizeValue = '1',
|
||||
borderStyleValue = 'solid',
|
||||
borderhex = '#FFFFFF',
|
||||
rows,
|
||||
cols,
|
||||
headers,
|
||||
@@ -624,52 +621,13 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
headers = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.HEADERS);
|
||||
width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
|
||||
|
||||
if (bordercolour) {
|
||||
borderhex = bordercolour.get('value');
|
||||
}
|
||||
|
||||
if (bordersize) {
|
||||
borderSizeValue = bordersize.get('value');
|
||||
}
|
||||
|
||||
if (borderstyle) {
|
||||
borderStyleValue = borderstyle.get('value');
|
||||
}
|
||||
|
||||
if (borders) {
|
||||
if (borders.get('value') === 'outer') {
|
||||
bordertable = ' style="border: ' + borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
|
||||
borderStyleValue + ' ' + borderhex + ';"';
|
||||
} else if (borders.get('value') === 'all') {
|
||||
bordercell = ' style="border: ' + borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
|
||||
borderStyleValue + ' ' + borderhex + ';"';
|
||||
}
|
||||
}
|
||||
|
||||
if (backgroundcolour) {
|
||||
if (bordertable !== '') {
|
||||
bordertable = bordertable.substring(0, bordertable.length-1) +
|
||||
'background-color: ' + backgroundcolour.get('value') + ';"';
|
||||
} else {
|
||||
bordertable = ' style="background-color: ' + backgroundcolour.get('value') + ';"';
|
||||
}
|
||||
}
|
||||
|
||||
if (width && width.get('value')) {
|
||||
if (bordertable !== '') {
|
||||
bordertable = bordertable.substring(0, bordertable.length-1) + 'width: ' +
|
||||
width.get('value') + CSS.WIDTHUNIT + ';"';
|
||||
} else {
|
||||
bordertable = ' style="width: ' + width.get('value') + CSS.WIDTHUNIT + ';"';
|
||||
}
|
||||
}
|
||||
|
||||
// Set the selection.
|
||||
this.get('host').setSelection(this._currentSelection);
|
||||
|
||||
// Note there are some spaces inserted in the cells and before and after, so that users have somewhere to click.
|
||||
var nl = "\n";
|
||||
tablehtml = '<br/>' + nl + '<table' + bordertable + '>' + nl;
|
||||
var tableId = Y.guid();
|
||||
tablehtml = '<br/>' + nl + '<table id="' + tableId + '">' + nl;
|
||||
|
||||
var captionstyle = '';
|
||||
if (captionposition.get('value')) {
|
||||
@@ -681,7 +639,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
i = 1;
|
||||
tablehtml += '<thead>' + nl + '<tr>' + nl;
|
||||
for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
|
||||
tablehtml += '<th scope="col"' + bordercell + '></th>' + nl;
|
||||
tablehtml += '<th scope="col"></th>' + nl;
|
||||
}
|
||||
tablehtml += '</tr>' + nl + '</thead>' + nl;
|
||||
}
|
||||
@@ -690,9 +648,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
tablehtml += '<tr>' + nl;
|
||||
for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
|
||||
if (j === 0 && (headers.get('value') === 'rows' || headers.get('value') === 'both')) {
|
||||
tablehtml += '<th scope="row"' + bordercell + '></th>' + nl;
|
||||
tablehtml += '<th scope="row"></th>' + nl;
|
||||
} else {
|
||||
tablehtml += '<td ' + bordercell + '></td>' + nl;
|
||||
tablehtml += '<td ></td>' + nl;
|
||||
}
|
||||
}
|
||||
tablehtml += '</tr>' + nl;
|
||||
@@ -702,6 +660,17 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
|
||||
this.get('host').insertContentAtFocusPoint(tablehtml);
|
||||
|
||||
var tableNode = Y.one('#' + tableId);
|
||||
this._setAppearance(tableNode, {
|
||||
width: width,
|
||||
borders: borders,
|
||||
borderColour: bordercolour,
|
||||
borderSize: bordersize,
|
||||
borderStyle: borderstyle,
|
||||
backgroundColour: backgroundcolour
|
||||
});
|
||||
tableNode.removeAttribute('id');
|
||||
|
||||
// Mark the content as updated.
|
||||
this.markUpdated();
|
||||
},
|
||||
@@ -1148,6 +1117,73 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the appropriate styles on the given table node according to
|
||||
* the provided configuration.
|
||||
*
|
||||
* @method _setAppearance
|
||||
* @param {Node} The table node to be modified.
|
||||
* @param {Object} Configuration object (associative array) containing the form nodes for
|
||||
* border styling.
|
||||
* @private
|
||||
*/
|
||||
_setAppearance: function(tableNode, configuration) {
|
||||
var borderhex,
|
||||
borderSizeValue,
|
||||
borderStyleValue,
|
||||
backgroundcolourvalue;
|
||||
|
||||
if (configuration.borderColour) {
|
||||
borderhex = configuration.borderColour.get('value');
|
||||
}
|
||||
|
||||
if (configuration.borderSize) {
|
||||
borderSizeValue = configuration.borderSize.get('value');
|
||||
}
|
||||
|
||||
if (configuration.borderStyle) {
|
||||
borderStyleValue = configuration.borderStyle.get('value');
|
||||
}
|
||||
|
||||
if (configuration.backgroundColour) {
|
||||
backgroundcolourvalue = configuration.backgroundColour.get('value');
|
||||
}
|
||||
|
||||
// Clear the inline border styling
|
||||
tableNode.removeAttribute('style');
|
||||
tableNode.all('td, th').each(function(cell) {
|
||||
cell.removeAttribute('style');
|
||||
}, this);
|
||||
|
||||
if (configuration.borders) {
|
||||
if (configuration.borders.get('value') === 'outer') {
|
||||
tableNode.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
|
||||
tableNode.setStyle('borderStyle', borderStyleValue);
|
||||
|
||||
if (borderhex !== 'none') {
|
||||
tableNode.setStyle('borderColor', borderhex);
|
||||
}
|
||||
} else if (configuration.borders.get('value') === 'all') {
|
||||
tableNode.all('td, th').each(function(cell) {
|
||||
cell.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
|
||||
cell.setStyle('borderStyle', borderStyleValue);
|
||||
|
||||
if (borderhex !== 'none') {
|
||||
cell.setStyle('borderColor', borderhex);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
if (backgroundcolourvalue !== 'none') {
|
||||
tableNode.setStyle('backgroundColor', backgroundcolourvalue);
|
||||
}
|
||||
|
||||
if (configuration.width && configuration.width.get('value')) {
|
||||
tableNode.setStyle('width', configuration.width.get('value') + CSS.WIDTHUNIT);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit table (show the dialogue).
|
||||
*
|
||||
@@ -1174,7 +1210,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
backgroundcolours = node.all(SELECTORS.BACKGROUNDCOLOURS),
|
||||
width = node.one(SELECTORS.WIDTH),
|
||||
table = this._lastTarget.ancestor('table'),
|
||||
captionnode = table.one('caption');
|
||||
captionnode = table.one('caption'),
|
||||
hexColour,
|
||||
matchedInput;
|
||||
|
||||
if (captionnode) {
|
||||
captioninput.set('value', captionnode.getHTML());
|
||||
@@ -1193,9 +1231,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
captionpositioninput.set('value', '');
|
||||
}
|
||||
|
||||
if (table.getStyle('background-color') !== 'transparent' && this.get('allowBackgroundColour')) {
|
||||
var hexColour = Y.Color.toHex(table.getStyle('background-color'));
|
||||
var matchedInput = backgroundcolours.filter('[value="' + hexColour + '"]');
|
||||
if (table.getStyle('backgroundColor') && this.get('allowBackgroundColour')) {
|
||||
hexColour = Y.Color.toHex(table.getStyle('backgroundColor'));
|
||||
matchedInput = backgroundcolours.filter('[value="' + hexColour + '"]');
|
||||
|
||||
if (matchedInput) {
|
||||
matchedInput.set("checked", true);
|
||||
@@ -1207,11 +1245,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
borderConfiguration = this._getBorderConfiguration(table);
|
||||
|
||||
if (borderConfiguration) {
|
||||
if (borderConfiguration.borderStyle && borderConfiguration.borderStyle === 'none') {
|
||||
borderValue = 'none';
|
||||
} else {
|
||||
borderValue = 'outer';
|
||||
}
|
||||
borderValue = 'outer';
|
||||
} else {
|
||||
borderConfiguration = this._getBorderConfiguration(table.one('td'));
|
||||
if (borderConfiguration) {
|
||||
@@ -1226,8 +1260,8 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
bordersize.set('value', borderSize);
|
||||
borderinput.set('value', borderValue);
|
||||
|
||||
var hexColour = borderConfiguration.borderColor;
|
||||
var matchedInput = bordercolours.filter('[value="' + hexColour + '"]');
|
||||
hexColour = borderConfiguration.borderColor;
|
||||
matchedInput = bordercolours.filter('[value="' + hexColour + '"]');
|
||||
|
||||
if (matchedInput) {
|
||||
matchedInput.set("checked", true);
|
||||
@@ -1244,6 +1278,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
}
|
||||
headersinput.set('value', headersvalue);
|
||||
dialogue.set('bodyContent', node).show();
|
||||
this._updateAvailableSettings();
|
||||
},
|
||||
|
||||
|
||||
|
||||
+158
-123
@@ -33,11 +33,11 @@
|
||||
|
||||
var COMPONENT = 'atto_table',
|
||||
DEFAULT = {
|
||||
BORDERSTYLE: 'solid',
|
||||
BORDERSTYLE: 'inherit',
|
||||
BORDERWIDTH: '1'
|
||||
},
|
||||
DIALOGUE = {
|
||||
WIDTH: '450px'
|
||||
WIDTH: '480px'
|
||||
},
|
||||
TEMPLATE = '' +
|
||||
'<form class="{{CSS.FORM}}">' +
|
||||
@@ -76,9 +76,8 @@ var COMPONENT = 'atto_table',
|
||||
'<legend class="mdl-align">{{get_string "appearance" component}}</legend>' +
|
||||
'{{#if allowBorders}}' +
|
||||
'<label for="{{elementid}}_atto_table_borders" class="sameline">{{get_string "borders" component}}</label>' +
|
||||
'<select class="{{CSS.BORDERS}}" id="{{elementid}}_atto_table_borders">' +
|
||||
'<select name="borders" class="{{CSS.BORDERS}}" id="{{elementid}}_atto_table_borders">' +
|
||||
'<option value="default">{{get_string "themedefault" component}}' + '</option>' +
|
||||
'<option value="none">{{get_string "noborder" component}}' + '</option>' +
|
||||
'<option value="outer">{{get_string "outer" component}}' + '</option>' +
|
||||
'<option value="all">{{get_string "all" component}}' + '</option>' +
|
||||
'</select>' +
|
||||
@@ -86,9 +85,10 @@ var COMPONENT = 'atto_table',
|
||||
'{{#if allowBorderStyles}}' +
|
||||
'<label for="{{elementid}}_atto_table_borderstyle" class="sameline">' +
|
||||
'{{get_string "borderstyles" component}}</label>' +
|
||||
'<select class="{{CSS.BORDERSTYLE}}" id="{{elementid}}_atto_table_borderstyle">' +
|
||||
'<select name="borderstyles" class="{{CSS.BORDERSTYLE}}" id="{{elementid}}_atto_table_borderstyle">' +
|
||||
'<option value="inherit">{{get_string "inherit" component}}</option>' +
|
||||
'{{#each borderStyles}}' +
|
||||
'<option value="' + '{{this}}' + '">' + '{{this}}' + '</option>' +
|
||||
'<option value="' + '{{this}}' + '">' + '{{get_string this ../component}}' + '</option>' +
|
||||
'{{/each}}' +
|
||||
'</select>' +
|
||||
'<br>' +
|
||||
@@ -96,7 +96,7 @@ var COMPONENT = 'atto_table',
|
||||
'{{#if allowBorderSize}}' +
|
||||
'<label for="{{elementid}}_atto_table_bordersize" class="sameline">' +
|
||||
'{{get_string "bordersize" component}}</label>' +
|
||||
'<input id="{{elementid}}_atto_table_bordersize" class="{{CSS.BORDERSIZE}}"' +
|
||||
'<input name="bordersize" id="{{elementid}}_atto_table_bordersize" class="{{CSS.BORDERSIZE}}"' +
|
||||
'type="number" value="1" size="8" min="1" max="50"/>' +
|
||||
'<label style="display: inline-block;">{{CSS.BORDERSIZEUNIT}}</label>' +
|
||||
'<br>' +
|
||||
@@ -110,10 +110,10 @@ var COMPONENT = 'atto_table',
|
||||
'style="background-color:transparent;color:transparent">' +
|
||||
|
||||
'<input id="{{../elementid}}_atto_table_bordercolour_-1"' +
|
||||
'type="radio" name="borderColour" value="transparent" checked="checked"'+
|
||||
'title="{{get_string "transparent" component}}"></input>' +
|
||||
'type="radio" name="borderColour" value="none" checked="checked"'+
|
||||
'title="{{get_string "themedefault" component}}"></input>' +
|
||||
|
||||
'{{get_string "transparent" component}}' +
|
||||
'{{get_string "themedefault" component}}' +
|
||||
'</label>' +
|
||||
'{{#each availableColours}}' +
|
||||
'<label for="{{../elementid}}_atto_table_bordercolour_{{@index}}"' +
|
||||
@@ -138,10 +138,10 @@ var COMPONENT = 'atto_table',
|
||||
'style="background-color:transparent;color:transparent">' +
|
||||
|
||||
'<input id="{{../elementid}}_atto_table_backgroundcolour_-1"' +
|
||||
'type="radio" name="backgroundColour" value="transparent" checked="checked"'+
|
||||
'title="{{get_string "transparent" component}}"></input>' +
|
||||
'type="radio" name="backgroundColour" value="none" checked="checked"'+
|
||||
'title="{{get_string "themedefault" component}}"></input>' +
|
||||
|
||||
'{{get_string "transparent" component}}' +
|
||||
'{{get_string "themedefault" component}}' +
|
||||
'</label>' +
|
||||
|
||||
'{{#each availableColours}}' +
|
||||
@@ -160,7 +160,7 @@ var COMPONENT = 'atto_table',
|
||||
'{{#if allowWidth}}' +
|
||||
'<label for="{{elementid}}_atto_table_width" class="sameline">' +
|
||||
'{{get_string "width" component}}</label>' +
|
||||
'<input id="{{elementid}}_atto_table_width" class="{{CSS.WIDTH}}" size="8" type="number" min="0" max="100"/>' +
|
||||
'<input name="width" id="{{elementid}}_atto_table_width" class="{{CSS.WIDTH}}" size="8" type="number" min="0" max="100"/>' +
|
||||
'<label style="display: inline-block;">{{CSS.WIDTHUNIT}}</label>' +
|
||||
'<br>' +
|
||||
'{{/if}}' +
|
||||
@@ -287,6 +287,8 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
// Set the dialogue content, and then show the dialogue.
|
||||
dialogue.set('bodyContent', this._getDialogueContent(false))
|
||||
.show();
|
||||
|
||||
this._updateAvailableSettings();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -332,7 +334,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
*/
|
||||
_getDialogueContent: function(edit) {
|
||||
var template = Y.Handlebars.compile(TEMPLATE);
|
||||
var availableColours = this.get('availableColors');
|
||||
var allowBorders = this.get('allowBorders');
|
||||
|
||||
this._content = Y.Node.create(template({
|
||||
CSS: CSS,
|
||||
@@ -341,13 +343,13 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
edit: edit,
|
||||
nonedit: !edit,
|
||||
allowStyling: this.get('allowStyling'),
|
||||
allowBorders: this.get('allowBorders'),
|
||||
allowBorders: allowBorders,
|
||||
allowBorderStyles: this.get('allowBorderStyles'),
|
||||
borderStyles: this.get('borderStyles'),
|
||||
allowBorderSize: this.get('allowBorderSize'),
|
||||
allowBorderColour: this.get('allowBorderColour'),
|
||||
allowBackgroundColour: this.get('allowBackgroundColour'),
|
||||
availableColours: availableColours,
|
||||
availableColours: this.get('availableColors'),
|
||||
allowWidth: this.get('allowWidth')
|
||||
}));
|
||||
|
||||
@@ -358,9 +360,48 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
this._content.one('.submit').on('click', this._setTable, this);
|
||||
}
|
||||
|
||||
if (allowBorders) {
|
||||
this._content.one('[name="borders"]').on('change', this._updateAvailableSettings, this);
|
||||
}
|
||||
|
||||
return this._content;
|
||||
},
|
||||
|
||||
/**
|
||||
* Disables options within the dialogue if they shouldn't be available.
|
||||
* E.g.
|
||||
* If borders are set to "Theme default" then the border size, style and
|
||||
* colour options are disabled.
|
||||
*
|
||||
* @method _updateAvailableSettings
|
||||
* @private
|
||||
*/
|
||||
_updateAvailableSettings: function() {
|
||||
var tableForm = this._content,
|
||||
enableBorders = tableForm.one('[name="borders"]'),
|
||||
borderStyle = tableForm.one('[name="borderstyles"]'),
|
||||
borderSize = tableForm.one('[name="bordersize"]'),
|
||||
borderColour = tableForm.all('[name="borderColour"]'),
|
||||
disabledValue = 'removeAttribute';
|
||||
|
||||
if (enableBorders.get('value') === 'default') {
|
||||
disabledValue = 'setAttribute';
|
||||
}
|
||||
|
||||
if (borderStyle) {
|
||||
borderStyle[disabledValue]('disabled');
|
||||
}
|
||||
|
||||
if (borderSize) {
|
||||
borderSize[disabledValue]('disabled');
|
||||
}
|
||||
|
||||
if (borderColour) {
|
||||
borderColour[disabledValue]('disabled');
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Given the current selection, return a table cell suitable for table editing
|
||||
* purposes, i.e. the first table cell selected, or the first cell in the table
|
||||
@@ -434,10 +475,6 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
borderstyle,
|
||||
bordercolour,
|
||||
backgroundcolour,
|
||||
backgroundcolourvalue = '#FFFFFF',
|
||||
borderSizeValue = '1',
|
||||
borderStyleValue = 'solid',
|
||||
borderhex = '#FFFFFF',
|
||||
table,
|
||||
width,
|
||||
captionnode;
|
||||
@@ -459,51 +496,16 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
backgroundcolour = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.SELECTEDBACKGROUNDCOLOUR);
|
||||
width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
|
||||
|
||||
if (bordercolour) {
|
||||
borderhex = bordercolour.get('value');
|
||||
}
|
||||
|
||||
if (bordersize) {
|
||||
borderSizeValue = bordersize.get('value');
|
||||
}
|
||||
|
||||
if (borderstyle) {
|
||||
borderStyleValue = borderstyle.get('value');
|
||||
}
|
||||
|
||||
if (backgroundcolour) {
|
||||
backgroundcolourvalue = backgroundcolour.get('value');
|
||||
}
|
||||
|
||||
table = this._lastTarget.ancestor('table');
|
||||
// Clear the inline border styling
|
||||
table.removeAttribute('style');
|
||||
table.all('td, th').each(function() {
|
||||
this.removeAttribute('style');
|
||||
this._setAppearance(table, {
|
||||
width: width,
|
||||
borders: borders,
|
||||
borderColour: bordercolour,
|
||||
borderSize: bordersize,
|
||||
borderStyle: borderstyle,
|
||||
backgroundColour: backgroundcolour
|
||||
});
|
||||
|
||||
if (borders) {
|
||||
if (borders.get('value') === 'outer') {
|
||||
table.setStyle('border', borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
|
||||
borderStyleValue + ' ' + borderhex);
|
||||
} else if (borders.get('value') === 'all') {
|
||||
table.all('td, th').each(function() {
|
||||
this.setStyle('border', borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
|
||||
borderStyleValue + ' ' + borderhex);
|
||||
});
|
||||
} else if (borders.get('value') === 'none') {
|
||||
table.setStyle('border', 'none');
|
||||
}
|
||||
}
|
||||
|
||||
if (width && width.get('value')) {
|
||||
table.setStyle('width', width.get('value') + CSS.WIDTHUNIT);
|
||||
}
|
||||
|
||||
if (backgroundcolourvalue !== '') {
|
||||
table.setStyle('background-color', backgroundcolourvalue);
|
||||
}
|
||||
|
||||
captionnode = table.one('caption');
|
||||
if (!captionnode) {
|
||||
captionnode = Y.Node.create('<caption></caption>');
|
||||
@@ -587,14 +589,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
var caption,
|
||||
captionposition,
|
||||
borders,
|
||||
bordertable = '',
|
||||
bordercell = '',
|
||||
bordersize,
|
||||
borderstyle,
|
||||
bordercolour,
|
||||
borderSizeValue = '1',
|
||||
borderStyleValue = 'solid',
|
||||
borderhex = '#FFFFFF',
|
||||
rows,
|
||||
cols,
|
||||
headers,
|
||||
@@ -622,52 +619,13 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
headers = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.HEADERS);
|
||||
width = e.currentTarget.ancestor(SELECTORS.FORM).one(SELECTORS.WIDTH);
|
||||
|
||||
if (bordercolour) {
|
||||
borderhex = bordercolour.get('value');
|
||||
}
|
||||
|
||||
if (bordersize) {
|
||||
borderSizeValue = bordersize.get('value');
|
||||
}
|
||||
|
||||
if (borderstyle) {
|
||||
borderStyleValue = borderstyle.get('value');
|
||||
}
|
||||
|
||||
if (borders) {
|
||||
if (borders.get('value') === 'outer') {
|
||||
bordertable = ' style="border: ' + borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
|
||||
borderStyleValue + ' ' + borderhex + ';"';
|
||||
} else if (borders.get('value') === 'all') {
|
||||
bordercell = ' style="border: ' + borderSizeValue + CSS.BORDERSIZEUNIT + ' ' +
|
||||
borderStyleValue + ' ' + borderhex + ';"';
|
||||
}
|
||||
}
|
||||
|
||||
if (backgroundcolour) {
|
||||
if (bordertable !== '') {
|
||||
bordertable = bordertable.substring(0, bordertable.length-1) +
|
||||
'background-color: ' + backgroundcolour.get('value') + ';"';
|
||||
} else {
|
||||
bordertable = ' style="background-color: ' + backgroundcolour.get('value') + ';"';
|
||||
}
|
||||
}
|
||||
|
||||
if (width && width.get('value')) {
|
||||
if (bordertable !== '') {
|
||||
bordertable = bordertable.substring(0, bordertable.length-1) + 'width: ' +
|
||||
width.get('value') + CSS.WIDTHUNIT + ';"';
|
||||
} else {
|
||||
bordertable = ' style="width: ' + width.get('value') + CSS.WIDTHUNIT + ';"';
|
||||
}
|
||||
}
|
||||
|
||||
// Set the selection.
|
||||
this.get('host').setSelection(this._currentSelection);
|
||||
|
||||
// Note there are some spaces inserted in the cells and before and after, so that users have somewhere to click.
|
||||
var nl = "\n";
|
||||
tablehtml = '<br/>' + nl + '<table' + bordertable + '>' + nl;
|
||||
var tableId = Y.guid();
|
||||
tablehtml = '<br/>' + nl + '<table id="' + tableId + '">' + nl;
|
||||
|
||||
var captionstyle = '';
|
||||
if (captionposition.get('value')) {
|
||||
@@ -679,7 +637,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
i = 1;
|
||||
tablehtml += '<thead>' + nl + '<tr>' + nl;
|
||||
for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
|
||||
tablehtml += '<th scope="col"' + bordercell + '></th>' + nl;
|
||||
tablehtml += '<th scope="col"></th>' + nl;
|
||||
}
|
||||
tablehtml += '</tr>' + nl + '</thead>' + nl;
|
||||
}
|
||||
@@ -688,9 +646,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
tablehtml += '<tr>' + nl;
|
||||
for (j = 0; j < parseInt(cols.get('value'), 10); j++) {
|
||||
if (j === 0 && (headers.get('value') === 'rows' || headers.get('value') === 'both')) {
|
||||
tablehtml += '<th scope="row"' + bordercell + '></th>' + nl;
|
||||
tablehtml += '<th scope="row"></th>' + nl;
|
||||
} else {
|
||||
tablehtml += '<td ' + bordercell + '></td>' + nl;
|
||||
tablehtml += '<td ></td>' + nl;
|
||||
}
|
||||
}
|
||||
tablehtml += '</tr>' + nl;
|
||||
@@ -700,6 +658,17 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
|
||||
this.get('host').insertContentAtFocusPoint(tablehtml);
|
||||
|
||||
var tableNode = Y.one('#' + tableId);
|
||||
this._setAppearance(tableNode, {
|
||||
width: width,
|
||||
borders: borders,
|
||||
borderColour: bordercolour,
|
||||
borderSize: bordersize,
|
||||
borderStyle: borderstyle,
|
||||
backgroundColour: backgroundcolour
|
||||
});
|
||||
tableNode.removeAttribute('id');
|
||||
|
||||
// Mark the content as updated.
|
||||
this.markUpdated();
|
||||
},
|
||||
@@ -1146,6 +1115,73 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the appropriate styles on the given table node according to
|
||||
* the provided configuration.
|
||||
*
|
||||
* @method _setAppearance
|
||||
* @param {Node} The table node to be modified.
|
||||
* @param {Object} Configuration object (associative array) containing the form nodes for
|
||||
* border styling.
|
||||
* @private
|
||||
*/
|
||||
_setAppearance: function(tableNode, configuration) {
|
||||
var borderhex,
|
||||
borderSizeValue,
|
||||
borderStyleValue,
|
||||
backgroundcolourvalue;
|
||||
|
||||
if (configuration.borderColour) {
|
||||
borderhex = configuration.borderColour.get('value');
|
||||
}
|
||||
|
||||
if (configuration.borderSize) {
|
||||
borderSizeValue = configuration.borderSize.get('value');
|
||||
}
|
||||
|
||||
if (configuration.borderStyle) {
|
||||
borderStyleValue = configuration.borderStyle.get('value');
|
||||
}
|
||||
|
||||
if (configuration.backgroundColour) {
|
||||
backgroundcolourvalue = configuration.backgroundColour.get('value');
|
||||
}
|
||||
|
||||
// Clear the inline border styling
|
||||
tableNode.removeAttribute('style');
|
||||
tableNode.all('td, th').each(function(cell) {
|
||||
cell.removeAttribute('style');
|
||||
}, this);
|
||||
|
||||
if (configuration.borders) {
|
||||
if (configuration.borders.get('value') === 'outer') {
|
||||
tableNode.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
|
||||
tableNode.setStyle('borderStyle', borderStyleValue);
|
||||
|
||||
if (borderhex !== 'none') {
|
||||
tableNode.setStyle('borderColor', borderhex);
|
||||
}
|
||||
} else if (configuration.borders.get('value') === 'all') {
|
||||
tableNode.all('td, th').each(function(cell) {
|
||||
cell.setStyle('borderWidth', borderSizeValue + CSS.BORDERSIZEUNIT);
|
||||
cell.setStyle('borderStyle', borderStyleValue);
|
||||
|
||||
if (borderhex !== 'none') {
|
||||
cell.setStyle('borderColor', borderhex);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
if (backgroundcolourvalue !== 'none') {
|
||||
tableNode.setStyle('backgroundColor', backgroundcolourvalue);
|
||||
}
|
||||
|
||||
if (configuration.width && configuration.width.get('value')) {
|
||||
tableNode.setStyle('width', configuration.width.get('value') + CSS.WIDTHUNIT);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit table (show the dialogue).
|
||||
*
|
||||
@@ -1172,7 +1208,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
backgroundcolours = node.all(SELECTORS.BACKGROUNDCOLOURS),
|
||||
width = node.one(SELECTORS.WIDTH),
|
||||
table = this._lastTarget.ancestor('table'),
|
||||
captionnode = table.one('caption');
|
||||
captionnode = table.one('caption'),
|
||||
hexColour,
|
||||
matchedInput;
|
||||
|
||||
if (captionnode) {
|
||||
captioninput.set('value', captionnode.getHTML());
|
||||
@@ -1191,9 +1229,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
captionpositioninput.set('value', '');
|
||||
}
|
||||
|
||||
if (table.getStyle('background-color') !== 'transparent' && this.get('allowBackgroundColour')) {
|
||||
var hexColour = Y.Color.toHex(table.getStyle('background-color'));
|
||||
var matchedInput = backgroundcolours.filter('[value="' + hexColour + '"]');
|
||||
if (table.getStyle('backgroundColor') && this.get('allowBackgroundColour')) {
|
||||
hexColour = Y.Color.toHex(table.getStyle('backgroundColor'));
|
||||
matchedInput = backgroundcolours.filter('[value="' + hexColour + '"]');
|
||||
|
||||
if (matchedInput) {
|
||||
matchedInput.set("checked", true);
|
||||
@@ -1205,11 +1243,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
borderConfiguration = this._getBorderConfiguration(table);
|
||||
|
||||
if (borderConfiguration) {
|
||||
if (borderConfiguration.borderStyle && borderConfiguration.borderStyle === 'none') {
|
||||
borderValue = 'none';
|
||||
} else {
|
||||
borderValue = 'outer';
|
||||
}
|
||||
borderValue = 'outer';
|
||||
} else {
|
||||
borderConfiguration = this._getBorderConfiguration(table.one('td'));
|
||||
if (borderConfiguration) {
|
||||
@@ -1224,8 +1258,8 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
bordersize.set('value', borderSize);
|
||||
borderinput.set('value', borderValue);
|
||||
|
||||
var hexColour = borderConfiguration.borderColor;
|
||||
var matchedInput = bordercolours.filter('[value="' + hexColour + '"]');
|
||||
hexColour = borderConfiguration.borderColor;
|
||||
matchedInput = bordercolours.filter('[value="' + hexColour + '"]');
|
||||
|
||||
if (matchedInput) {
|
||||
matchedInput.set("checked", true);
|
||||
@@ -1242,6 +1276,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
|
||||
}
|
||||
headersinput.set('value', headersvalue);
|
||||
dialogue.set('bodyContent', node).show();
|
||||
this._updateAvailableSettings();
|
||||
},
|
||||
|
||||
|
||||
|
||||
@@ -376,6 +376,73 @@ class core_files_zip_packer_testcase extends advanced_testcase implements file_p
|
||||
unlink($archive);
|
||||
}
|
||||
|
||||
public function test_close_archive() {
|
||||
global $CFG;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$archive = "$CFG->tempdir/archive.zip";
|
||||
$textfile = "$CFG->tempdir/textfile.txt";
|
||||
touch($textfile);
|
||||
|
||||
$this->assertFileNotExists($archive);
|
||||
$this->assertFileExists($textfile);
|
||||
|
||||
// Create archive and close it without files.
|
||||
// (returns true, without any warning).
|
||||
$zip_archive = new zip_archive();
|
||||
$result = $zip_archive->open($archive, file_archive::CREATE);
|
||||
$this->assertTrue($result);
|
||||
$result = $zip_archive->close();
|
||||
$this->assertTrue($result);
|
||||
unlink($archive);
|
||||
|
||||
// Create archive and close it with files.
|
||||
// (returns true, without any warning).
|
||||
$zip_archive = new zip_archive();
|
||||
$result = $zip_archive->open($archive, file_archive::CREATE);
|
||||
$this->assertTrue($result);
|
||||
$result = $zip_archive->add_file_from_string('test.txt', 'test');
|
||||
$this->assertTrue($result);
|
||||
$result = $zip_archive->add_file_from_pathname('test2.txt', $textfile);
|
||||
$result = $zip_archive->close();
|
||||
$this->assertTrue($result);
|
||||
unlink($archive);
|
||||
|
||||
// Create archive and close if forcing error.
|
||||
// (returns true for old PHP versions and
|
||||
// false with warnings for new PHP versions). MDL-51863.
|
||||
$zip_archive = new zip_archive();
|
||||
$result = $zip_archive->open($archive, file_archive::CREATE);
|
||||
$this->assertTrue($result);
|
||||
$result = $zip_archive->add_file_from_string('test.txt', 'test');
|
||||
$this->assertTrue($result);
|
||||
$result = $zip_archive->add_file_from_pathname('test2.txt', $textfile);
|
||||
$this->assertTrue($result);
|
||||
// Delete the file before closing does force close() to fail.
|
||||
unlink($textfile);
|
||||
// Behavior is different between old PHP versions and new ones. Let's detect it.
|
||||
$result = false;
|
||||
try {
|
||||
// Old PHP versions were not printing any warning.
|
||||
$result = $zip_archive->close();
|
||||
} catch (Exception $e) {
|
||||
// New PHP versions print PHP Warning.
|
||||
$this->assertInstanceOf('PHPUnit_Framework_Error_Warning', $e);
|
||||
$this->assertContains('ZipArchive::close', $e->getMessage());
|
||||
}
|
||||
// This is crazy, but it shows how some PHP versions do return true.
|
||||
try {
|
||||
// And some PHP versions do return correctly false (5.4.25, 5.6.14...)
|
||||
$this->assertFalse($result);
|
||||
} catch (Exception $e) {
|
||||
// But others do insist into returning true (5.6.13...). Only can accept them.
|
||||
$this->assertInstanceOf('PHPUnit_Framework_ExpectationFailedException', $e);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
$this->assertFileNotExists($archive);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends test_add_files
|
||||
*/
|
||||
|
||||
@@ -191,7 +191,7 @@ class zip_archive extends file_archive {
|
||||
}
|
||||
|
||||
if ($this->emptyziphack) {
|
||||
$this->za->close();
|
||||
@$this->za->close();
|
||||
$this->za = null;
|
||||
$this->mode = null;
|
||||
$this->namelookup = null;
|
||||
@@ -202,7 +202,7 @@ class zip_archive extends file_archive {
|
||||
|
||||
} else if ($this->za->numFiles == 0) {
|
||||
// PHP can not create empty archives, so let's fake it.
|
||||
$this->za->close();
|
||||
@$this->za->close();
|
||||
$this->za = null;
|
||||
$this->mode = null;
|
||||
$this->namelookup = null;
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,32 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Flowplayer handling file.
|
||||
*
|
||||
* @package core
|
||||
* @copyright Petr Skoda <petr.skoda@totaralms.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define('NO_DEBUG_DISPLAY', true);
|
||||
define('NO_MOODLE_COOKIES', true);
|
||||
define('NO_UPGRADE_CHECK', true);
|
||||
|
||||
require('../../config.php');
|
||||
require('../../lib/flowplayer/lib.php');
|
||||
|
||||
flowplayer_send_flash_content('flowplayer-3.2.18.swf');
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,32 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Flowplayer audio swf handling.
|
||||
*
|
||||
* @package core
|
||||
* @copyright Petr Skoda <petr.skoda@totaralms.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define('NO_DEBUG_DISPLAY', true);
|
||||
define('NO_MOODLE_COOKIES', true);
|
||||
define('NO_UPGRADE_CHECK', true);
|
||||
|
||||
require('../../config.php');
|
||||
require('../../lib/flowplayer/lib.php');
|
||||
|
||||
flowplayer_send_flash_content('flowplayer.audio-3.2.11.swf');
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,32 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Flowplayer audio controls handling.
|
||||
*
|
||||
* @package core
|
||||
* @copyright Petr Skoda <petr.skoda@totaralms.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define('NO_DEBUG_DISPLAY', true);
|
||||
define('NO_MOODLE_COOKIES', true);
|
||||
define('NO_UPGRADE_CHECK', true);
|
||||
|
||||
require('../../config.php');
|
||||
require('../../lib/flowplayer/lib.php');
|
||||
|
||||
flowplayer_send_flash_content('flowplayer.controls-3.2.16.swf');
|
||||
@@ -0,0 +1,78 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Flowplayer library.
|
||||
*
|
||||
* @package core
|
||||
* @copyright Petr Skoda <petr.skoda@totaralms.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Safer flash serving code.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return void does not return, ends with die()
|
||||
*/
|
||||
function flowplayer_send_flash_content($filename) {
|
||||
global $CFG;
|
||||
// Note: Do not use any fancy APIs here, this must work in all supported versions.
|
||||
|
||||
// No url params.
|
||||
if (!empty($_GET) or !empty($_POST) or !empty($_REQUEST)) {
|
||||
header("HTTP/1.1 404 Not Found");
|
||||
die;
|
||||
}
|
||||
|
||||
// Our referrers only, nobody else should embed these scripts.
|
||||
if (!empty($_SERVER['HTTP_REFERER'])) {
|
||||
$refhost = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST);
|
||||
$host = parse_url($CFG->wwwroot . '/', PHP_URL_HOST);
|
||||
if ($refhost and $host and strtolower($refhost) !== strtolower($host)) {
|
||||
header("HTTP/1.1 404 Not Found");
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch and decode the original content.
|
||||
$content = file_get_contents($CFG->dirroot . '/lib/flowplayer/' . $filename . '.bin');
|
||||
if (!$content) {
|
||||
header("HTTP/1.1 404 Not Found");
|
||||
die;
|
||||
}
|
||||
$content = base64_decode($content);
|
||||
|
||||
// No caching allowed.
|
||||
if (strpos($CFG->wwwroot, 'https://') === 0) {
|
||||
// HTTPS sites - watch out for IE! KB812935 and KB316431.
|
||||
header('Cache-Control: private, max-age=10, no-transform');
|
||||
header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
|
||||
header('Pragma: ');
|
||||
} else {
|
||||
header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0, no-transform');
|
||||
header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
|
||||
header('Pragma: no-cache');
|
||||
}
|
||||
|
||||
// Send the original binary code.
|
||||
header('Content-Type: application/x-shockwave-flash');
|
||||
echo $content;
|
||||
die;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
Description of FlowPlayer import
|
||||
|
||||
Our changes:
|
||||
* the handling of flash content now it's done by PHP, checking url parameter to avoid security issues.
|
||||
* we do not load the flow player if flash not present - this is much better
|
||||
for accessibility and flash incompatible devices - they may play mp3 or flv directly
|
||||
* no splashscreens
|
||||
|
||||
@@ -46,6 +46,10 @@ class MoodleQuickForm_autocomplete extends MoodleQuickForm_select {
|
||||
protected $ajax = '';
|
||||
/** @var string $placeholder Placeholder text for an empty list. */
|
||||
protected $placeholder = '';
|
||||
/** @var bool $casesensitive Whether the search has to be case-sensitive. */
|
||||
protected $casesensitive = false;
|
||||
/** @var bool $showsuggestions Show suggestions by default - but this can be turned off. */
|
||||
protected $showsuggestions = true;
|
||||
|
||||
/**
|
||||
* constructor
|
||||
@@ -54,7 +58,7 @@ class MoodleQuickForm_autocomplete extends MoodleQuickForm_select {
|
||||
* @param mixed $elementLabel Label(s) for the select
|
||||
* @param mixed $options Data to be used to populate options
|
||||
* @param mixed $attributes Either a typical HTML attribute string or an associative array. Special options
|
||||
* "tags", "placeholder", "ajax", "multiple" are supported.
|
||||
* "tags", "placeholder", "ajax", "multiple", "casesensitive" are supported.
|
||||
*/
|
||||
function MoodleQuickForm_autocomplete($elementName=null, $elementLabel=null, $options=null, $attributes=null) {
|
||||
// Even if the constructor gets called twice we do not really want 2x options (crazy forms!).
|
||||
@@ -66,6 +70,10 @@ class MoodleQuickForm_autocomplete extends MoodleQuickForm_select {
|
||||
$this->tags = $attributes['tags'];
|
||||
unset($attributes['tags']);
|
||||
}
|
||||
if (isset($attributes['showsuggestions'])) {
|
||||
$this->showsuggestions = $attributes['showsuggestions'];
|
||||
unset($attributes['showsuggestions']);
|
||||
}
|
||||
$this->placeholder = get_string('search');
|
||||
if (isset($attributes['placeholder'])) {
|
||||
$this->placeholder = $attributes['placeholder'];
|
||||
@@ -75,6 +83,10 @@ class MoodleQuickForm_autocomplete extends MoodleQuickForm_select {
|
||||
$this->ajax = $attributes['ajax'];
|
||||
unset($attributes['ajax']);
|
||||
}
|
||||
if (isset($attributes['casesensitive'])) {
|
||||
$this->casesensitive = $attributes['casesensitive'] ? true : false;
|
||||
unset($attributes['casesensitive']);
|
||||
}
|
||||
parent::HTML_QuickForm_select($elementName, $elementLabel, $options, $attributes);
|
||||
|
||||
$this->_type = 'autocomplete';
|
||||
@@ -91,7 +103,8 @@ class MoodleQuickForm_autocomplete extends MoodleQuickForm_select {
|
||||
// Enhance the select with javascript.
|
||||
$this->_generateId();
|
||||
$id = $this->getAttribute('id');
|
||||
$PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params = array('#' . $id, $this->tags, $this->ajax, $this->placeholder));
|
||||
$PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params = array('#' . $id, $this->tags, $this->ajax,
|
||||
$this->placeholder, $this->casesensitive, $this->showsuggestions));
|
||||
|
||||
return parent::toHTML();
|
||||
}
|
||||
|
||||
@@ -960,8 +960,12 @@ M.form_filemanager.init = function(Y, options) {
|
||||
for (var i in attrs) {
|
||||
if (selectnode.one('.fp-'+attrs[i])) {
|
||||
var value = (node[attrs[i]+'_f']) ? node[attrs[i]+'_f'] : (node[attrs[i]] ? node[attrs[i]] : '');
|
||||
// Escape if the attribute being evaluated is not for the list of reference files.
|
||||
if (attrs[i] !== 'reflist') {
|
||||
value = Y.Escape.html(value);
|
||||
}
|
||||
selectnode.one('.fp-'+attrs[i]).addClassIf('fp-unknown', ''+value == '')
|
||||
.one('.fp-value').setContent(Y.Escape.html(value));
|
||||
.one('.fp-value').setContent(value);
|
||||
}
|
||||
}
|
||||
// display thumbnail
|
||||
|
||||
@@ -90,6 +90,7 @@ class MoodleQuickForm_tags extends MoodleQuickForm_autocomplete {
|
||||
}
|
||||
$attributes['multiple'] = 'multiple';
|
||||
$attributes['placeholder'] = get_string('entertags', 'tag');
|
||||
$attributes['showsuggestions'] = $this->showingofficial;
|
||||
|
||||
parent::MoodleQuickForm_autocomplete($elementName, $elementLabel, $validoptions, $attributes);
|
||||
}
|
||||
|
||||
@@ -1699,6 +1699,7 @@ M.util.load_flowplayer = function() {
|
||||
loaded = true;
|
||||
|
||||
var controls = {
|
||||
url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.controls-3.2.16.swf.php',
|
||||
autoHide: true
|
||||
}
|
||||
/* TODO: add CSS color overrides for the flv flow player */
|
||||
@@ -1706,9 +1707,9 @@ M.util.load_flowplayer = function() {
|
||||
for(var i=0; i<M.util.video_players.length; i++) {
|
||||
var video = M.util.video_players[i];
|
||||
if (video.width > 0 && video.height > 0) {
|
||||
var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', width: video.width, height: video.height};
|
||||
var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf.php', width: video.width, height: video.height};
|
||||
} else {
|
||||
var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf';
|
||||
var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf.php';
|
||||
}
|
||||
flowplayer(video.id, src, {
|
||||
plugins: {controls: controls},
|
||||
@@ -1744,6 +1745,7 @@ M.util.load_flowplayer = function() {
|
||||
return;
|
||||
}
|
||||
var controls = {
|
||||
url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.controls-3.2.16.swf.php',
|
||||
autoHide: false,
|
||||
fullscreen: false,
|
||||
next: false,
|
||||
@@ -1808,8 +1810,8 @@ M.util.load_flowplayer = function() {
|
||||
controls.height = 25;
|
||||
controls.time = true;
|
||||
}
|
||||
flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', {
|
||||
plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.11.swf'}},
|
||||
flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf.php', {
|
||||
plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.11.swf.php'}},
|
||||
clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2835,7 +2835,7 @@ EOD;
|
||||
* @return string HTML fragment.
|
||||
*/
|
||||
public function notify_message($message) {
|
||||
$n = new notification($message, notification::NOTIFY_MESSAGE);
|
||||
$n = new \core\output\notification($message, \core\output\notification::NOTIFY_MESSAGE);
|
||||
return $this->render($n);
|
||||
}
|
||||
|
||||
|
||||
@@ -556,6 +556,8 @@ class page_requirements_manager {
|
||||
$path = realpath("$componentdir/jquery/$file");
|
||||
if (strpos($path, $CFG->dirroot) === 0) {
|
||||
$url = $CFG->httpswwwroot.preg_replace('/^'.preg_quote($CFG->dirroot, '/').'/', '', $path);
|
||||
// Replace all occurences of backslashes characters in url to forward slashes.
|
||||
$url = str_replace('\\', '/', $url);
|
||||
$url = new moodle_url($url);
|
||||
} else {
|
||||
// Bad luck, fix your server!
|
||||
|
||||
+43
-27
@@ -458,7 +458,9 @@ class flexible_table {
|
||||
} else if (isset($SESSION->flextable[$this->uniqueid])) {
|
||||
$this->prefs = $SESSION->flextable[$this->uniqueid];
|
||||
}
|
||||
if (!$this->prefs) {
|
||||
|
||||
// Set up default preferences if needed.
|
||||
if (!$this->prefs or optional_param($this->request[TABLE_VAR_RESET], false, PARAM_BOOL)) {
|
||||
$this->prefs = array(
|
||||
'collapse' => array(),
|
||||
'sortby' => array(),
|
||||
@@ -526,17 +528,6 @@ class flexible_table {
|
||||
$this->prefs['i_first'] = $ifirst;
|
||||
}
|
||||
|
||||
// Allow user to reset table preferences.
|
||||
if (optional_param($this->request[TABLE_VAR_RESET], 0, PARAM_BOOL) === 1) {
|
||||
$this->prefs = array(
|
||||
'collapse' => array(),
|
||||
'sortby' => array(),
|
||||
'i_first' => '',
|
||||
'i_last' => '',
|
||||
'textsort' => $this->column_textsort,
|
||||
);
|
||||
}
|
||||
|
||||
// Save user preferences if they have changed.
|
||||
if ($this->prefs != $oldprefs) {
|
||||
if ($this->persistent) {
|
||||
@@ -1416,32 +1407,57 @@ class flexible_table {
|
||||
/**
|
||||
* Generate the HTML for the table preferences reset button.
|
||||
*
|
||||
* @return string HTML fragment.
|
||||
* @return string HTML fragment, empty string if no need to reset
|
||||
*/
|
||||
private function render_reset_button() {
|
||||
$userprefs = false;
|
||||
// Loop through the user table preferences for a setting.
|
||||
foreach ($this->prefs as $preference) {
|
||||
if (!empty($preference)) {
|
||||
// We have a preference.
|
||||
$userprefs = true;
|
||||
// We only need one.
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If no table preferences have been set then don't show the reset button.
|
||||
if (!$userprefs) {
|
||||
protected function render_reset_button() {
|
||||
|
||||
if (!$this->can_be_reset()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$url = $this->baseurl->out(false, array($this->request[TABLE_VAR_RESET] => 1));
|
||||
|
||||
$html = html_writer::start_div('mdl-right');
|
||||
$html = html_writer::start_div('resettable mdl-right');
|
||||
$html .= html_writer::link($url, get_string('resettable'));
|
||||
$html .= html_writer::end_div();
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are there some table preferences that can be reset?
|
||||
*
|
||||
* If true, then the "reset table preferences" widget should be displayed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_be_reset() {
|
||||
|
||||
// Loop through preferences and make sure they are empty or set to the default value.
|
||||
foreach ($this->prefs as $prefname => $prefval) {
|
||||
|
||||
if ($prefname === 'sortby' and !empty($this->sort_default_column)) {
|
||||
// Check if the actual sorting differs from the default one.
|
||||
if (empty($prefval) or $prefval !== array($this->sort_default_column => $this->sort_default_order)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} else if ($prefname === 'collapse' and !empty($prefval)) {
|
||||
// Check if there are some collapsed columns (all are expanded by default).
|
||||
foreach ($prefval as $columnname => $iscollapsed) {
|
||||
if ($iscollapsed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!empty($prefval)) {
|
||||
// For all other cases, we just check if some preference is set.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user