Compare commits
260 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ccf7578373 | |||
| 68ba214c65 | |||
| b6319f70bd | |||
| e759c9ed83 | |||
| c9f995872f | |||
| 2e36c42625 | |||
| 7a2300fd82 | |||
| f3342e1010 | |||
| ea90412944 | |||
| 8d64443452 | |||
| d6d2cec4a0 | |||
| 57c9b7fc37 | |||
| cae37299f3 | |||
| 5243e93661 | |||
| b135c42a17 | |||
| ec8f488f30 | |||
| aa30dc8860 | |||
| 76f98c3bd5 | |||
| 7783fd0ec1 | |||
| a6943ebb59 | |||
| eaa43a132c | |||
| 81aa5e196f | |||
| b5b81de3d6 | |||
| f0fc8a194a | |||
| 03561ea899 | |||
| 6688ae2ba2 | |||
| b44bc9bf6c | |||
| 1ccc9f1953 | |||
| efe2681894 | |||
| e3a6d44845 | |||
| 8aa01fcf61 | |||
| 8072eb1384 | |||
| b9ae53f86b | |||
| 5616a31987 | |||
| 8926030555 | |||
| 13b19e40cb | |||
| 478b1d194f | |||
| 9aac9f7074 | |||
| 3a0951f027 | |||
| 7d416f9c2c | |||
| 589b602be3 | |||
| 8001199766 | |||
| a56833d49d | |||
| be88f0993d | |||
| 11e60032be | |||
| 401710883b | |||
| 40f05d2312 | |||
| 3602df58b2 | |||
| f3743b3041 | |||
| e2d635bbaf | |||
| a764855641 | |||
| 9b0f29239b | |||
| 8ce86ebba3 | |||
| 30890c2971 | |||
| 1bd70073c5 | |||
| 9e0884d410 | |||
| af25b8fc7f | |||
| ef29782f31 | |||
| 1ada616c56 | |||
| 8443b88995 | |||
| eb354bdefa | |||
| 4cd3927c4f | |||
| 6a973d261a | |||
| 796e48a58b | |||
| 496b39974f | |||
| 6586118e0f | |||
| 4a145a8ae5 | |||
| da8a0c97f0 | |||
| 20b2565c38 | |||
| 36e84e96fc | |||
| 159b4e5d8c | |||
| aa95085969 | |||
| 79ea89fea2 | |||
| 928bcb02d6 | |||
| b9fd516407 | |||
| c2502375ff | |||
| 20a5056df6 | |||
| 4071a2b6fe | |||
| 6630b38c95 | |||
| b974b5d05d | |||
| 1ab858314a | |||
| 6be1923d81 | |||
| 801a372dad | |||
| 1322351b92 | |||
| bb2071ca17 | |||
| be713b7144 | |||
| e20752c4f5 | |||
| 30ff29dc9f | |||
| 6607c5ffb6 | |||
| 7f14def230 | |||
| 39e364515f | |||
| 557d8528eb | |||
| 0bd6039f40 | |||
| 57e8c9f748 | |||
| 7b7bf31d4c | |||
| 59aff5f949 | |||
| f816ea9a67 | |||
| 6e3314f044 | |||
| 2a542d0265 | |||
| f18fb611c9 | |||
| 43caf0217f | |||
| 7b03092121 | |||
| 5dbd051502 | |||
| c39a22f600 | |||
| f0f38bf1d3 | |||
| 41080050b0 | |||
| ae348e78a0 | |||
| 71fc06aae5 | |||
| e3491a093a | |||
| a0a78199b8 | |||
| d523cbcb26 | |||
| 5c9acb8164 | |||
| 8242027da3 | |||
| fd2f1ae146 | |||
| c8419f87c0 | |||
| 4837fea0aa | |||
| 671440d708 | |||
| 6c3f463dbd | |||
| fca7e60c81 | |||
| 1e83eec993 | |||
| 216bb33940 | |||
| 58280ae0b8 | |||
| 0df5c743ad | |||
| e8da33d86e | |||
| 02331dcd48 | |||
| c8e7777766 | |||
| d25f1c3c3c | |||
| ab2095fb0f | |||
| 6b839f3222 | |||
| 66242b95e0 | |||
| 9a428ae012 | |||
| b8243e4164 | |||
| 14a1672cff | |||
| 89cf38c6d9 | |||
| 43a1e9a2a5 | |||
| 82735dec4d | |||
| 76f11421a4 | |||
| 1b0978abf3 | |||
| 098b16fb13 | |||
| 4916c9bb66 | |||
| 8fcb479246 | |||
| 73ec9b4778 | |||
| 9cca2db570 | |||
| 9daa5874a0 | |||
| 60829fbe6d | |||
| 9f33d5ac6e | |||
| 1cc7a14ce4 | |||
| 958c9c17ca | |||
| 4397da0c4d | |||
| d8966238dd | |||
| 065a754111 | |||
| 3e4de62607 | |||
| a37faf9c70 | |||
| aba60237cd | |||
| 5836baf593 | |||
| ed44df22a8 | |||
| 314b8093e7 | |||
| ade8dd5d08 | |||
| 28196427a2 | |||
| 8c4d7cee79 | |||
| 64476c520e | |||
| c851ee5ddb | |||
| 860d33c90a | |||
| eefa2c88e0 | |||
| 284c795d23 | |||
| 808b68835b | |||
| 46a3753424 | |||
| 7336f854e6 | |||
| 6065875eac | |||
| 3fdecc2841 | |||
| 8c00d189f4 | |||
| 8c7db03a56 | |||
| c550fd0e11 | |||
| 349b705d31 | |||
| 9cc55074d5 | |||
| 4fab795ac9 | |||
| 51af04ab2f | |||
| 9e6d1a3884 | |||
| 391ea5b7de | |||
| 9d0e1fca5a | |||
| 8f730872a7 | |||
| 4febb36efe | |||
| 368832d51c | |||
| cc80554892 | |||
| e905b656f5 | |||
| 526d354991 | |||
| eb3b38f04c | |||
| a1b696f943 | |||
| 4e52eb8eb0 | |||
| dae0ccfc45 | |||
| 40f1801c4b | |||
| 6078d420bf | |||
| 2479a7c446 | |||
| ef9f60a635 | |||
| db9d745c7b | |||
| 0d049757c0 | |||
| 0f56f6e6da | |||
| ff340a0afa | |||
| 62a38a4ece | |||
| a272c25649 | |||
| a9ca3fa167 | |||
| 7ec04e0e09 | |||
| 1fc642324a | |||
| 7f91fb036b | |||
| fd39db29e2 | |||
| 341505e5de | |||
| ef7df2ae0a | |||
| ed6d81c9f7 | |||
| 01ed53df0f | |||
| 4d03dbf13d | |||
| 4cc469b2cc | |||
| 1b7d75f19f | |||
| 4dc645d64d | |||
| a5327e449d | |||
| 4d4b8ae6bf | |||
| 1e67827131 | |||
| 7dfc9ea6b3 | |||
| 69e89f4a82 | |||
| b799cde890 | |||
| 9fd91f8eec | |||
| 6e186498fd | |||
| 3c2b703e00 | |||
| e6ccfad242 | |||
| 75909e3c92 | |||
| 2a1f7a4442 | |||
| 7851132177 | |||
| 4f7a80341e | |||
| 3b4e9a9fc8 | |||
| 507335e2de | |||
| 5b93dc8539 | |||
| 3a89d0b541 | |||
| cc4d7ce61b | |||
| b8bb8fe8de | |||
| a66de3ca70 | |||
| 13e7626834 | |||
| 6a633d36c2 | |||
| 7b18365be7 | |||
| 89ceede3bf | |||
| 8e85371800 | |||
| 786d9cd3a1 | |||
| 994ccb7845 | |||
| c282157fc3 | |||
| ff456b59ea | |||
| 88924ff772 | |||
| 57f4e9608d | |||
| 1003e7e57e | |||
| 429f78412d | |||
| d2065abd0b | |||
| 5fd7c11d17 | |||
| acd146fe7c | |||
| 79dff18711 | |||
| 9da0be8c46 | |||
| 80e4426aeb | |||
| 9eced5050b | |||
| 6d5f9d1218 | |||
| 71826ac268 | |||
| 21824a9325 | |||
| 9016f772ce | |||
| 84346754f6 | |||
| 5ef66eaf7e |
+5
-5
@@ -13,7 +13,7 @@ language: php
|
||||
|
||||
php:
|
||||
# We only run the highest and lowest supported versions to reduce the load on travis-ci.org.
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.0
|
||||
|
||||
addons:
|
||||
@@ -30,7 +30,7 @@ env:
|
||||
# Although we want to run these jobs and see failures as quickly as possible, we also want to get the slowest job to
|
||||
# start first so that the total run time is not too high.
|
||||
#
|
||||
# We only run MySQL on PHP 7.1, so run that first.
|
||||
# We only run MySQL on PHP 7.2, so run that first.
|
||||
# CI Tests should be second-highest in priority as these only take <= 60 seconds to run under normal circumstances.
|
||||
# Postgres is significantly is pretty reasonable in its run-time.
|
||||
|
||||
@@ -54,12 +54,12 @@ matrix:
|
||||
|
||||
include:
|
||||
# Run grunt/npm install on highest version ('node' is an alias for the latest node.js version.)
|
||||
- php: 7.1
|
||||
env: DB=none TASK=GRUNT NVM_VERSION='node'
|
||||
- php: 7.2
|
||||
env: DB=none TASK=GRUNT NVM_VERSION='8.9'
|
||||
|
||||
exclude:
|
||||
# MySQL - it's just too slow.
|
||||
# Exclude it on all versions except for 7.1
|
||||
# Exclude it on all versions except for 7.2
|
||||
|
||||
- env: DB=mysqli TASK=PHPUNIT
|
||||
php: 7.0
|
||||
|
||||
@@ -1754,6 +1754,7 @@
|
||||
<VENDOR name="oracle" version="10.2" />
|
||||
</DATABASE>
|
||||
<PHP version="5.6.5" level="required">
|
||||
<RESTRICT function="restrict_php_version_72" message="unsupportedphpversion72" />
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
@@ -1919,6 +1920,7 @@
|
||||
<VENDOR name="oracle" version="10.2" />
|
||||
</DATABASE>
|
||||
<PHP version="5.6.5" level="required">
|
||||
<RESTRICT function="restrict_php_version_72" message="unsupportedphpversion72" />
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
|
||||
+2
-1
@@ -868,6 +868,7 @@ $cachewarnings = cache_helper::warnings();
|
||||
$eventshandlers = $DB->get_records_sql('SELECT DISTINCT component FROM {events_handlers}');
|
||||
$themedesignermode = !empty($CFG->themedesignermode);
|
||||
$mobileconfigured = !empty($CFG->enablemobilewebservice);
|
||||
$invalidforgottenpasswordurl = !empty($CFG->forgottenpasswordurl) && empty(clean_param($CFG->forgottenpasswordurl, PARAM_URL));
|
||||
|
||||
// Check if a directory with development libraries exists.
|
||||
if (empty($CFG->disabledevlibdirscheck) && (is_dir($CFG->dirroot.'/vendor') || is_dir($CFG->dirroot.'/node_modules'))) {
|
||||
@@ -885,4 +886,4 @@ $output = $PAGE->get_renderer('core', 'admin');
|
||||
echo $output->admin_notifications_page($maturity, $insecuredataroot, $errorsdisplayed, $cronoverdue, $dbproblems,
|
||||
$maintenancemode, $availableupdates, $availableupdatesfetch, $buggyiconvnomb,
|
||||
$registered, $cachewarnings, $eventshandlers, $themedesignermode, $devlibdir,
|
||||
$mobileconfigured, $overridetossl);
|
||||
$mobileconfigured, $overridetossl, $invalidforgottenpasswordurl);
|
||||
|
||||
@@ -36,6 +36,6 @@ class core_register_renderer extends plugin_renderer_base {
|
||||
* @return string
|
||||
*/
|
||||
public function moodleorg_registration_message() {
|
||||
return format_text(get_string('registermoodlenet', 'admin'), FORMAT_MARKDOWN, ['noclean' => true]);
|
||||
return format_text(get_string('registermoodlenet', 'admin'), FORMAT_HTML, ['noclean' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-1
@@ -280,6 +280,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
* @param bool $devlibdir Warn about development libs directory presence.
|
||||
* @param bool $mobileconfigured Whether the mobile web services have been enabled
|
||||
* @param bool $overridetossl Whether or not ssl is being forced.
|
||||
* @param bool $invalidforgottenpasswordurl Whether the forgotten password URL does not link to a valid URL.
|
||||
*
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
@@ -287,7 +288,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$cronoverdue, $dbproblems, $maintenancemode, $availableupdates, $availableupdatesfetch,
|
||||
$buggyiconvnomb, $registered, array $cachewarnings = array(), $eventshandlers = 0,
|
||||
$themedesignermode = false, $devlibdir = false, $mobileconfigured = false,
|
||||
$overridetossl = false) {
|
||||
$overridetossl = false, $invalidforgottenpasswordurl = false) {
|
||||
global $CFG;
|
||||
$output = '';
|
||||
|
||||
@@ -308,6 +309,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$output .= $this->events_handlers($eventshandlers);
|
||||
$output .= $this->registration_warning($registered);
|
||||
$output .= $this->mobile_configuration_warning($mobileconfigured);
|
||||
$output .= $this->forgotten_password_url_warning($invalidforgottenpasswordurl);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//// IT IS ILLEGAL AND A VIOLATION OF THE GPL TO HIDE, REMOVE OR MODIFY THIS COPYRIGHT NOTICE ///
|
||||
@@ -866,6 +868,24 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a warning about the forgotten password URL not linking to a valid URL.
|
||||
*
|
||||
* @param boolean $invalidforgottenpasswordurl true if the forgotten password URL is not valid
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
protected function forgotten_password_url_warning($invalidforgottenpasswordurl) {
|
||||
$output = '';
|
||||
if ($invalidforgottenpasswordurl) {
|
||||
$settingslink = new moodle_url('/admin/settings.php', ['section' => 'manageauths']);
|
||||
$configurebutton = $this->single_button($settingslink, get_string('check', 'moodle'));
|
||||
$output .= $this->warning(get_string('invalidforgottenpasswordurl', 'admin') . ' ' . $configurebutton,
|
||||
'error alert alert-danger');
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to render the information about the available Moodle update
|
||||
*
|
||||
|
||||
@@ -102,7 +102,7 @@ if ($hassiteconfig) {
|
||||
$temp->add(new admin_setting_configtext('alternateloginurl', new lang_string('alternateloginurl', 'auth'),
|
||||
new lang_string('alternatelogin', 'auth', htmlspecialchars(get_login_url())), ''));
|
||||
$temp->add(new admin_setting_configtext('forgottenpasswordurl', new lang_string('forgottenpasswordurl', 'auth'),
|
||||
new lang_string('forgottenpassword', 'auth'), ''));
|
||||
new lang_string('forgottenpassword', 'auth'), '', PARAM_URL));
|
||||
$temp->add(new admin_setting_confightmleditor('auth_instructions', new lang_string('instructions', 'auth'),
|
||||
new lang_string('authinstructions', 'auth'), ''));
|
||||
$setting = new admin_setting_configtext('allowemailaddresses', new lang_string('allowemailaddresses', 'admin'),
|
||||
|
||||
@@ -156,11 +156,12 @@ function tool_analytics_calculate_course_dates($course, $options) {
|
||||
$format = course_get_format($course);
|
||||
$formatoptions = $format->get_format_options();
|
||||
|
||||
if ($course->format === 'weeks' && $formatoptions['automaticenddate']) {
|
||||
// Special treatment for weeks with automatic end date.
|
||||
// Change this for a course formats API level call in MDL-60702.
|
||||
if (method_exists($format, 'update_end_date') && $formatoptions['automaticenddate']) {
|
||||
// Special treatment for weeks-based formats with automatic end date.
|
||||
|
||||
if ($options['update']) {
|
||||
format_weeks::update_end_date($course->id);
|
||||
$format::update_end_date($course->id);
|
||||
$course->enddate = $DB->get_field('course', 'enddate', array('id' => $course->id));
|
||||
$notification .= PHP_EOL . ' ' . get_string('weeksenddateautomaticallyset', 'tool_analytics') . ': ' .
|
||||
userdate($course->enddate);
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
|
||||
namespace tool_httpsreplace;
|
||||
|
||||
use database_column_info;
|
||||
use progress_bar;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
@@ -60,7 +63,7 @@ class url_finder {
|
||||
* for less straightforward swaps.
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $column
|
||||
* @param database_column_info $column
|
||||
* @param string $domain
|
||||
* @param string $search search string that has prefix, protocol, domain name and one extra character,
|
||||
* example1: src="http://host.com/
|
||||
@@ -174,7 +177,7 @@ class url_finder {
|
||||
$regex = '#((src|data)\ *=\ *[\'\"])(http://)([^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))[\'\"]#i';
|
||||
preg_match_all($regex, $record->$columnname, $match);
|
||||
foreach ($match[0] as $i => $fullmatch) {
|
||||
if (strpos($fullmatch, $CFG->wwwroot) !== false) {
|
||||
if (\core_text::strpos($fullmatch, $CFG->wwwroot) !== false) {
|
||||
continue;
|
||||
}
|
||||
$prefix = $match[1][$i];
|
||||
@@ -186,7 +189,7 @@ class url_finder {
|
||||
}
|
||||
if ($replacing) {
|
||||
// For replace string use: prefix, protocol, host and one extra character.
|
||||
$found[$prefix . substr($url, 0, strlen($host) + 8)] = $host;
|
||||
$found[$prefix . \core_text::substr($url, 0, \core_text::strlen($host) + 8)] = $host;
|
||||
} else {
|
||||
$entry["table"] = $table;
|
||||
$entry["columnname"] = $columnname;
|
||||
|
||||
@@ -584,10 +584,10 @@ class manager {
|
||||
'usestream' => true,
|
||||
));
|
||||
|
||||
if ($part === $plainpartid) {
|
||||
if ($part == $plainpartid) {
|
||||
$contentplain = $this->process_message_part_body($messagedata, $partdata, $part);
|
||||
|
||||
} else if ($part === $htmlpartid) {
|
||||
} else if ($part == $htmlpartid) {
|
||||
$contenthtml = $this->process_message_part_body($messagedata, $partdata, $part);
|
||||
|
||||
} else if ($filename = $partdata->getName($part)) {
|
||||
|
||||
@@ -140,7 +140,7 @@ class api {
|
||||
'rememberusername' => $CFG->rememberusername,
|
||||
'authloginviaemail' => $CFG->authloginviaemail,
|
||||
'registerauth' => $CFG->registerauth,
|
||||
'forgottenpasswordurl' => $CFG->forgottenpasswordurl,
|
||||
'forgottenpasswordurl' => clean_param($CFG->forgottenpasswordurl, PARAM_URL), // We may expect a mailto: here.
|
||||
'authinstructions' => $authinstructions,
|
||||
'authnoneenabled' => (int) is_enabled_auth('none'),
|
||||
'enablewebservices' => $CFG->enablewebservices,
|
||||
@@ -386,7 +386,8 @@ class api {
|
||||
// Return certificate information and verify the certificate.
|
||||
$curl->setopt(array('CURLOPT_CERTINFO' => 1, 'CURLOPT_SSL_VERIFYPEER' => true));
|
||||
$httpswwwroot = str_replace('http:', 'https:', $CFG->wwwroot); // Force https url.
|
||||
$curl->head($httpswwwroot . "/login/index.php");
|
||||
// Check https using a page not redirecting or returning exceptions.
|
||||
$curl->head($httpswwwroot . "/$CFG->admin/tool/mobile/mobile.webmanifest.php");
|
||||
$info = $curl->get_info();
|
||||
|
||||
// First of all, check the server certificate (if any).
|
||||
|
||||
@@ -97,11 +97,13 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
|
||||
set_config('typeoflogin', api::LOGIN_VIA_BROWSER, 'tool_mobile');
|
||||
set_config('logo', 'mock.png', 'core_admin');
|
||||
set_config('logocompact', 'mock.png', 'core_admin');
|
||||
set_config('forgottenpasswordurl', 'mailto:fake@email.zy'); // Test old hack.
|
||||
|
||||
list($authinstructions, $notusedformat) = external_format_text($authinstructions, FORMAT_MOODLE, $context->id);
|
||||
$expected['registerauth'] = 'email';
|
||||
$expected['authinstructions'] = $authinstructions;
|
||||
$expected['typeoflogin'] = api::LOGIN_VIA_BROWSER;
|
||||
$expected['forgottenpasswordurl'] = ''; // Expect empty when it's not an URL.
|
||||
|
||||
if ($logourl = $OUTPUT->get_logo_url()) {
|
||||
$expected['logourl'] = $logourl->out(false);
|
||||
|
||||
@@ -74,7 +74,7 @@ if ($options['run']) {
|
||||
}
|
||||
}
|
||||
$_SERVER['argv'] = array_values($_SERVER['argv']);
|
||||
PHPUnit_TextUI_Command::main();
|
||||
PHPUnit\TextUI\Command::main();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,10 @@ defined('MOODLE_INTERNAL') || die();
|
||||
/**
|
||||
* Any element analysers can analyse.
|
||||
*
|
||||
* Analysers get_analysers method return all analysable elements in the site;
|
||||
* it is important that analysable elements implement lazy loading to avoid
|
||||
* big memory footprints. See \core_analytics\course example.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
|
||||
+85
-233
@@ -40,9 +40,19 @@ require_once($CFG->dirroot . '/lib/enrollib.php');
|
||||
class course implements \core_analytics\analysable {
|
||||
|
||||
/**
|
||||
* @var \core_analytics\course[] $instances
|
||||
* @var bool Has this course data been already loaded.
|
||||
*/
|
||||
protected static $instances = array();
|
||||
protected $loaded = false;
|
||||
|
||||
/**
|
||||
* @var int $cachedid self::$cachedinstance analysable id.
|
||||
*/
|
||||
protected static $cachedid = 0;
|
||||
|
||||
/**
|
||||
* @var \core_analytics\course $cachedinstance
|
||||
*/
|
||||
protected static $cachedinstance = null;
|
||||
|
||||
/**
|
||||
* Course object
|
||||
@@ -122,7 +132,7 @@ class course implements \core_analytics\analysable {
|
||||
* Use self::instance() instead to get cached copies of the course. Instances obtained
|
||||
* through this constructor will not be cached.
|
||||
*
|
||||
* Loads course students and teachers.
|
||||
* Lazy load of course data, students and teachers.
|
||||
*
|
||||
* @param int|stdClass $course Course id
|
||||
* @return void
|
||||
@@ -130,18 +140,70 @@ class course implements \core_analytics\analysable {
|
||||
public function __construct($course) {
|
||||
|
||||
if (is_scalar($course)) {
|
||||
$this->course = get_course($course);
|
||||
$this->course = new \stdClass();
|
||||
$this->course->id = $course;
|
||||
} else {
|
||||
$this->course = $course;
|
||||
}
|
||||
}
|
||||
|
||||
$this->coursecontext = \context_course::instance($this->course->id);
|
||||
/**
|
||||
* Returns an analytics course instance.
|
||||
*
|
||||
* Lazy load of course data, students and teachers.
|
||||
*
|
||||
* @param int|stdClass $course Course object or course id
|
||||
* @return \core_analytics\course
|
||||
*/
|
||||
public static function instance($course) {
|
||||
|
||||
$courseid = $course;
|
||||
if (!is_scalar($courseid)) {
|
||||
$courseid = $course->id;
|
||||
}
|
||||
|
||||
if (self::$cachedid === $courseid) {
|
||||
return self::$cachedinstance;
|
||||
}
|
||||
|
||||
$cachedinstance = new \core_analytics\course($course);
|
||||
self::$cachedinstance = $cachedinstance;
|
||||
self::$cachedid = (int)$courseid;
|
||||
return self::$cachedinstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_id
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_id() {
|
||||
return $this->course->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the analytics course object.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function load() {
|
||||
|
||||
// The instance constructor could be already loaded with the full course object. Using shortname
|
||||
// because it is a required course field.
|
||||
if (empty($this->course->shortname)) {
|
||||
$this->course = get_course($this->course->id);
|
||||
}
|
||||
|
||||
$this->coursecontext = $this->get_context();
|
||||
|
||||
$this->now = time();
|
||||
|
||||
// Get the course users, including users assigned to student and teacher roles at an higher context.
|
||||
$cache = \cache::make_from_params(\cache_store::MODE_REQUEST, 'core_analytics', 'rolearchetypes');
|
||||
|
||||
// Flag the instance as loaded.
|
||||
$this->loaded = true;
|
||||
|
||||
if (!$studentroles = $cache->get('student')) {
|
||||
$studentroles = array_keys(get_archetype_roles('student'));
|
||||
$cache->set('student', $studentroles);
|
||||
@@ -155,53 +217,13 @@ class course implements \core_analytics\analysable {
|
||||
$this->teacherids = $this->get_user_ids($teacherroles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an analytics course instance.
|
||||
*
|
||||
* @param int|stdClass $course Course id
|
||||
* @return \core_analytics\course
|
||||
*/
|
||||
public static function instance($course) {
|
||||
|
||||
$courseid = $course;
|
||||
if (!is_scalar($courseid)) {
|
||||
$courseid = $course->id;
|
||||
}
|
||||
|
||||
if (!empty(self::$instances[$courseid])) {
|
||||
return self::$instances[$courseid];
|
||||
}
|
||||
|
||||
$instance = new \core_analytics\course($course);
|
||||
self::$instances[$courseid] = $instance;
|
||||
return self::$instances[$courseid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all statically cached instances.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reset_caches() {
|
||||
self::$instances = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* get_id
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_id() {
|
||||
return $this->course->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The course short name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return format_string($this->course->shortname, true, array('context' => $this->get_context()));
|
||||
return format_string($this->get_course_data()->shortname, true, array('context' => $this->get_context()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,8 +250,8 @@ class course implements \core_analytics\analysable {
|
||||
}
|
||||
|
||||
// The field always exist but may have no valid if the course is created through a sync process.
|
||||
if (!empty($this->course->startdate)) {
|
||||
$this->starttime = (int)$this->course->startdate;
|
||||
if (!empty($this->get_course_data()->startdate)) {
|
||||
$this->starttime = (int)$this->get_course_data()->startdate;
|
||||
} else {
|
||||
$this->starttime = 0;
|
||||
}
|
||||
@@ -256,7 +278,7 @@ class course implements \core_analytics\analysable {
|
||||
|
||||
// We first try to find current course student logs.
|
||||
$firstlogs = array();
|
||||
foreach ($this->studentids as $studentid) {
|
||||
foreach ($this->get_students() as $studentid) {
|
||||
// Grrr, we are limited by logging API, we could do this easily with a
|
||||
// select min(timecreated) from xx where courseid = yy group by userid.
|
||||
|
||||
@@ -278,7 +300,7 @@ class course implements \core_analytics\analysable {
|
||||
sort($firstlogs);
|
||||
$firstlogsmedian = $this->median($firstlogs);
|
||||
|
||||
$studentenrolments = enrol_get_course_users($this->get_id(), $this->studentids);
|
||||
$studentenrolments = enrol_get_course_users($this->get_id(), $this->get_students());
|
||||
if (empty($studentenrolments)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -306,8 +328,8 @@ class course implements \core_analytics\analysable {
|
||||
}
|
||||
|
||||
// The enddate field is only available from Moodle 3.2 (MDL-22078).
|
||||
if (!empty($this->course->enddate)) {
|
||||
$this->endtime = (int)$this->course->enddate;
|
||||
if (!empty($this->get_course_data()->enddate)) {
|
||||
$this->endtime = (int)$this->get_course_data()->enddate;
|
||||
return $this->endtime;
|
||||
}
|
||||
|
||||
@@ -362,21 +384,12 @@ class course implements \core_analytics\analysable {
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function get_course_data() {
|
||||
return $this->course;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the course valid to extract indicators from it?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid() {
|
||||
|
||||
if (!$this->was_started() || !$this->is_finished()) {
|
||||
return false;
|
||||
if (!$this->loaded) {
|
||||
$this->load();
|
||||
}
|
||||
|
||||
return true;
|
||||
return $this->course;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -427,7 +440,7 @@ class course implements \core_analytics\analysable {
|
||||
public function get_user_ids($roleids) {
|
||||
|
||||
// We need to index by ra.id as a user may have more than 1 $roles role.
|
||||
$records = get_role_users($roleids, $this->coursecontext, true, 'ra.id, u.id AS userid, r.id AS roleid', 'ra.id ASC');
|
||||
$records = get_role_users($roleids, $this->get_context(), true, 'ra.id, u.id AS userid, r.id AS roleid', 'ra.id ASC');
|
||||
|
||||
// If a user have more than 1 $roles role array_combine will discard the duplicate.
|
||||
$callable = array($this, 'filter_user_id');
|
||||
@@ -441,6 +454,11 @@ class course implements \core_analytics\analysable {
|
||||
* @return stdClass[]
|
||||
*/
|
||||
public function get_students() {
|
||||
|
||||
if (!$this->loaded) {
|
||||
$this->load();
|
||||
}
|
||||
|
||||
return $this->studentids;
|
||||
}
|
||||
|
||||
@@ -453,7 +471,7 @@ class course implements \core_analytics\analysable {
|
||||
global $DB;
|
||||
|
||||
// No logs if no students.
|
||||
if (empty($this->studentids)) {
|
||||
if (empty($this->get_students())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -529,172 +547,6 @@ class course implements \core_analytics\analysable {
|
||||
return $grades;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guesses all activities that were available during a period of time.
|
||||
*
|
||||
* @param string $activitytype
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @param \stdClass $student
|
||||
* @return array
|
||||
*/
|
||||
public function get_activities($activitytype, $starttime, $endtime, $student = false) {
|
||||
|
||||
// Var $student may not be available, default to not calculating dynamic data.
|
||||
$studentid = -1;
|
||||
if ($student) {
|
||||
$studentid = $student->id;
|
||||
}
|
||||
$modinfo = get_fast_modinfo($this->get_course_data(), $studentid);
|
||||
$activities = $modinfo->get_instances_of($activitytype);
|
||||
|
||||
$timerangeactivities = array();
|
||||
foreach ($activities as $activity) {
|
||||
if (!$this->completed_by($activity, $starttime, $endtime)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$timerangeactivities[$activity->context->id] = $activity;
|
||||
}
|
||||
|
||||
return $timerangeactivities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Was the activity supposed to be completed during the provided time range?.
|
||||
*
|
||||
* @param \cm_info $activity
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return bool
|
||||
*/
|
||||
protected function completed_by(\cm_info $activity, $starttime, $endtime) {
|
||||
|
||||
// We can't check uservisible because:
|
||||
// - Any activity with available until would not be counted.
|
||||
// - Sites may block student's course view capabilities once the course is closed.
|
||||
|
||||
// Students can not view hidden activities by default, this is not reliable 100% but accurate in most of the cases.
|
||||
if ($activity->visible === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We skip activities that were not yet visible or their 'until' was not in this $starttime - $endtime range.
|
||||
if ($activity->availability) {
|
||||
$info = new \core_availability\info_module($activity);
|
||||
$activityavailability = $this->availability_completed_by($info, $starttime, $endtime);
|
||||
if ($activityavailability === false) {
|
||||
return false;
|
||||
} else if ($activityavailability === true) {
|
||||
// This activity belongs to this time range.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We skip activities in sections that were not yet visible or their 'until' was not in this $starttime - $endtime range.
|
||||
$section = $activity->get_modinfo()->get_section_info($activity->sectionnum);
|
||||
if ($section->availability) {
|
||||
$info = new \core_availability\info_section($section);
|
||||
$sectionavailability = $this->availability_completed_by($info, $starttime, $endtime);
|
||||
if ($sectionavailability === false) {
|
||||
return false;
|
||||
} else if ($sectionavailability === true) {
|
||||
// This activity belongs to this section time range.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// When the course is using format weeks we use the week's end date.
|
||||
$format = course_get_format($activity->get_modinfo()->get_course());
|
||||
if ($this->course->format === 'weeks') {
|
||||
$dates = $format->get_section_dates($section);
|
||||
|
||||
// We need to consider the +2 hours added by get_section_dates.
|
||||
// Avoid $starttime <= $dates->end because $starttime may be the start of the next week.
|
||||
if ($starttime < ($dates->end - 7200) && $endtime >= ($dates->end - 7200)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($activity->sectionnum == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->get_end() || !$this->get_start()) {
|
||||
debugging('Activities which due date is in a time range can not be calculated ' .
|
||||
'if the course doesn\'t have start and end date', DEBUG_DEVELOPER);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!course_format_uses_sections($this->course->format)) {
|
||||
// If it does not use sections and there are no availability conditions to access it it is available
|
||||
// and we can not magically classify it into any other time range than this one.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Split the course duration in the number of sections and consider the end of each section the due
|
||||
// date of all activities contained in that section.
|
||||
$formatoptions = $format->get_format_options();
|
||||
if (!empty($formatoptions['numsections'])) {
|
||||
$nsections = $formatoptions['numsections'];
|
||||
} else {
|
||||
// There are course format that use sections but without numsections, we fallback to the number
|
||||
// of cached sections in get_section_info_all, not that accurate though.
|
||||
$coursesections = $activity->get_modinfo()->get_section_info_all();
|
||||
$nsections = count($coursesections);
|
||||
if (isset($coursesections[0])) {
|
||||
// We don't count section 0 if it exists.
|
||||
$nsections--;
|
||||
}
|
||||
}
|
||||
|
||||
$courseduration = $this->get_end() - $this->get_start();
|
||||
$sectionduration = round($courseduration / $nsections);
|
||||
$activitysectionenddate = $this->get_start() + ($sectionduration * $activity->sectionnum);
|
||||
if ($activitysectionenddate > $starttime && $activitysectionenddate <= $endtime) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the activity/section should have been completed during the provided period according to its availability rules.
|
||||
*
|
||||
* @param \core_availability\info $info
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return bool|null
|
||||
*/
|
||||
protected function availability_completed_by(\core_availability\info $info, $starttime, $endtime) {
|
||||
|
||||
$dateconditions = $info->get_availability_tree()->get_all_children('\availability_date\condition');
|
||||
foreach ($dateconditions as $condition) {
|
||||
// Availability API does not allow us to check from / to dates nicely, we need to be naughty.
|
||||
$conditiondata = $condition->save();
|
||||
|
||||
if ($conditiondata->d === \availability_date\condition::DIRECTION_FROM &&
|
||||
$conditiondata->t > $endtime) {
|
||||
// Skip this activity if any 'from' date is later than the end time.
|
||||
return false;
|
||||
|
||||
} else if ($conditiondata->d === \availability_date\condition::DIRECTION_UNTIL &&
|
||||
($conditiondata->t < $starttime || $conditiondata->t > $endtime)) {
|
||||
// Skip activity if any 'until' date is not in $starttime - $endtime range.
|
||||
return false;
|
||||
} else if ($conditiondata->d === \availability_date\condition::DIRECTION_UNTIL &&
|
||||
$conditiondata->t < $endtime && $conditiondata->t > $starttime) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// This can be interpreted as 'the activity was available but we don't know if its expected completion date
|
||||
// was during this period.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by get_user_ids to extract the user id.
|
||||
*
|
||||
@@ -731,7 +583,7 @@ class course implements \core_analytics\analysable {
|
||||
}
|
||||
|
||||
// Check the amount of student logs in the 4 previous weeks.
|
||||
list($studentssql, $studentsparams) = $DB->get_in_or_equal($this->studentids, SQL_PARAMS_NAMED);
|
||||
list($studentssql, $studentsparams) = $DB->get_in_or_equal($this->get_students(), SQL_PARAMS_NAMED);
|
||||
$filterselect = $prefix . 'courseid = :courseid AND ' . $prefix . 'userid ' . $studentssql;
|
||||
$filterparams = array('courseid' => $this->course->id) + $studentsparams;
|
||||
|
||||
|
||||
@@ -44,17 +44,21 @@ abstract class by_course extends base {
|
||||
|
||||
// Default to all system courses.
|
||||
if (!empty($this->options['filter'])) {
|
||||
$courses = $this->options['filter'];
|
||||
$courses = array();
|
||||
foreach ($this->options['filter'] as $courseid) {
|
||||
$courses[$courseid] = new \stdClass();
|
||||
$courses[$courseid]->id = $courseid;
|
||||
}
|
||||
} else {
|
||||
// Iterate through all potentially valid courses.
|
||||
$courses = get_courses('all', 'c.sortorder ASC');
|
||||
$courses = get_courses('all', 'c.sortorder ASC', 'c.id');
|
||||
}
|
||||
unset($courses[SITEID]);
|
||||
|
||||
$analysables = array();
|
||||
foreach ($courses as $course) {
|
||||
// Skip the frontpage course.
|
||||
$analysable = \core_analytics\course::instance($course);
|
||||
$analysable = \core_analytics\course::instance($course->id);
|
||||
$analysables[$analysable->get_id()] = $analysable;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,13 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
abstract class community_of_inquiry_activity extends linear {
|
||||
|
||||
/**
|
||||
* instancedata
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $instancedata = array();
|
||||
|
||||
/**
|
||||
* @var \core_analytics\course
|
||||
*/
|
||||
@@ -60,6 +67,56 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
*/
|
||||
const INDICATOR_SOCIAL = "social";
|
||||
|
||||
/**
|
||||
* Constant for this cognitive level.
|
||||
*/
|
||||
const COGNITIVE_LEVEL_1 = 1;
|
||||
|
||||
/**
|
||||
* Constant for this cognitive level.
|
||||
*/
|
||||
const COGNITIVE_LEVEL_2 = 2;
|
||||
|
||||
/**
|
||||
* Constant for this cognitive level.
|
||||
*/
|
||||
const COGNITIVE_LEVEL_3 = 3;
|
||||
|
||||
/**
|
||||
* Constant for this cognitive level.
|
||||
*/
|
||||
const COGNITIVE_LEVEL_4 = 4;
|
||||
|
||||
/**
|
||||
* Constant for this cognitive level.
|
||||
*/
|
||||
const COGNITIVE_LEVEL_5 = 5;
|
||||
|
||||
/**
|
||||
* Constant for this social level.
|
||||
*/
|
||||
const SOCIAL_LEVEL_1 = 1;
|
||||
|
||||
/**
|
||||
* Constant for this social level.
|
||||
*/
|
||||
const SOCIAL_LEVEL_2 = 2;
|
||||
|
||||
/**
|
||||
* Constant for this social level.
|
||||
*/
|
||||
const SOCIAL_LEVEL_3 = 3;
|
||||
|
||||
/**
|
||||
* Constant for this social level.
|
||||
*/
|
||||
const SOCIAL_LEVEL_4 = 4;
|
||||
|
||||
/**
|
||||
* Constant for this social level.
|
||||
*/
|
||||
const SOCIAL_LEVEL_5 = 5;
|
||||
|
||||
/**
|
||||
* Max cognitive depth level accepted.
|
||||
*/
|
||||
@@ -428,8 +485,8 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
// Samples are at cm level or below.
|
||||
$useractivities = array(\context_module::instance($cm->id)->id => $cm);
|
||||
} else {
|
||||
// All course activities.
|
||||
$useractivities = $this->course->get_activities($this->get_activity_type(), $starttime, $endtime, $user);
|
||||
// Activities that should be completed during this time period.
|
||||
$useractivities = $this->get_activities($starttime, $endtime, $user);
|
||||
}
|
||||
|
||||
return $useractivities;
|
||||
@@ -530,13 +587,15 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
foreach ($useractivities as $contextid => $cm) {
|
||||
|
||||
$potentiallevel = $this->get_cognitive_depth_level($cm);
|
||||
if (!is_int($potentiallevel) || $potentiallevel > self::MAX_COGNITIVE_LEVEL || $potentiallevel < 1) {
|
||||
if (!is_int($potentiallevel)
|
||||
|| $potentiallevel > self::MAX_COGNITIVE_LEVEL
|
||||
|| $potentiallevel < self::COGNITIVE_LEVEL_1) {
|
||||
throw new \coding_exception('Activities\' potential cognitive depth go from 1 to 5.');
|
||||
}
|
||||
$scoreperlevel = $scoreperactivity / $potentiallevel;
|
||||
|
||||
switch ($potentiallevel) {
|
||||
case 5:
|
||||
case self::COGNITIVE_LEVEL_5:
|
||||
// Cognitive level 5 is to submit after feedback.
|
||||
if ($this->any_feedback('submitted', $cm, $contextid, $user)) {
|
||||
$score += $scoreperlevel * 5;
|
||||
@@ -544,7 +603,7 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
}
|
||||
// The user didn't reach the activity max cognitive depth, continue with level 2.
|
||||
|
||||
case 4:
|
||||
case self::COGNITIVE_LEVEL_4:
|
||||
// Cognitive level 4 is to comment on feedback.
|
||||
if ($this->any_feedback('replied', $cm, $contextid, $user)) {
|
||||
$score += $scoreperlevel * 4;
|
||||
@@ -552,7 +611,7 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
}
|
||||
// The user didn't reach the activity max cognitive depth, continue with level 2.
|
||||
|
||||
case 3:
|
||||
case self::COGNITIVE_LEVEL_3:
|
||||
// Cognitive level 3 is to view feedback.
|
||||
|
||||
if ($this->any_feedback('viewed', $cm, $contextid, $user)) {
|
||||
@@ -562,7 +621,7 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
}
|
||||
// The user didn't reach the activity max cognitive depth, continue with level 2.
|
||||
|
||||
case 2:
|
||||
case self::COGNITIVE_LEVEL_2:
|
||||
// Cognitive depth level 2 is to submit content.
|
||||
|
||||
if ($this->any_write_log($contextid, $user)) {
|
||||
@@ -571,7 +630,7 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
}
|
||||
// The user didn't reach the activity max cognitive depth, continue with level 1.
|
||||
|
||||
case 1:
|
||||
case self::COGNITIVE_LEVEL_1:
|
||||
// Cognitive depth level 1 is just accessing the activity.
|
||||
|
||||
if ($this->any_log($contextid, $user)) {
|
||||
@@ -617,16 +676,18 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
foreach ($useractivities as $contextid => $cm) {
|
||||
|
||||
$potentiallevel = $this->get_social_breadth_level($cm);
|
||||
if (!is_int($potentiallevel) || $potentiallevel > self::MAX_SOCIAL_LEVEL || $potentiallevel < 1) {
|
||||
if (!is_int($potentiallevel)
|
||||
|| $potentiallevel > self::MAX_SOCIAL_LEVEL
|
||||
|| $potentiallevel < self::SOCIAL_LEVEL_1) {
|
||||
throw new \coding_exception('Activities\' potential social breadth go from 1 to ' .
|
||||
community_of_inquiry_activity::MAX_SOCIAL_LEVEL . '.');
|
||||
}
|
||||
$scoreperlevel = $scoreperactivity / $potentiallevel;
|
||||
switch ($potentiallevel) {
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case self::SOCIAL_LEVEL_2:
|
||||
case self::SOCIAL_LEVEL_3:
|
||||
case self::SOCIAL_LEVEL_4:
|
||||
case self::SOCIAL_LEVEL_5:
|
||||
// Core activities social breadth only reaches level 2, until core activities social
|
||||
// breadth do not reach level 5 we limit it to what we currently support, which is level 2.
|
||||
|
||||
@@ -639,7 +700,7 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
}
|
||||
// The user didn't reach the activity max social breadth, continue with level 1.
|
||||
|
||||
case 1:
|
||||
case self::SOCIAL_LEVEL_1:
|
||||
// Social breadth level 1 is just accessing the activity.
|
||||
if ($this->any_log($contextid, $user)) {
|
||||
$score += $scoreperlevel;
|
||||
@@ -687,6 +748,250 @@ abstract class community_of_inquiry_activity extends linear {
|
||||
$this->grades = $course->get_student_grades($courseactivities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guesses all activities that were available during a period of time.
|
||||
*
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @param \stdClass|false $student
|
||||
* @return array
|
||||
*/
|
||||
protected function get_activities($starttime, $endtime, $student = false) {
|
||||
|
||||
$activitytype = $this->get_activity_type();
|
||||
|
||||
// Var $student may not be available, default to not calculating dynamic data.
|
||||
$studentid = -1;
|
||||
if ($student) {
|
||||
$studentid = $student->id;
|
||||
}
|
||||
$modinfo = get_fast_modinfo($this->course->get_course_data(), $studentid);
|
||||
$activities = $modinfo->get_instances_of($activitytype);
|
||||
|
||||
$timerangeactivities = array();
|
||||
foreach ($activities as $activity) {
|
||||
|
||||
if (!$this->activity_completed_by($activity, $starttime, $endtime, $student)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$timerangeactivities[$activity->context->id] = $activity;
|
||||
}
|
||||
|
||||
return $timerangeactivities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Was the activity supposed to be completed during the provided time range?.
|
||||
*
|
||||
* @param \cm_info $activity
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @param \stdClass|false $student
|
||||
* @return bool
|
||||
*/
|
||||
protected function activity_completed_by(\cm_info $activity, $starttime, $endtime, $student = false) {
|
||||
|
||||
// We can't check uservisible because:
|
||||
// - Any activity with available until would not be counted.
|
||||
// - Sites may block student's course view capabilities once the course is closed.
|
||||
|
||||
// Students can not view hidden activities by default, this is not reliable 100% but accurate in most of the cases.
|
||||
if ($activity->visible === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Give priority to the different methods activities have to set a "due" date.
|
||||
$return = $this->activity_type_completed_by($activity, $starttime, $endtime, $student);
|
||||
if (!is_null($return)) {
|
||||
// Method activity_type_completed_by returns null if there is no due date method or there is but it is not set.
|
||||
return $return;
|
||||
}
|
||||
|
||||
// We skip activities that were not yet visible or their 'until' was not in this $starttime - $endtime range.
|
||||
if ($activity->availability) {
|
||||
$info = new \core_availability\info_module($activity);
|
||||
$activityavailability = $this->availability_completed_by($info, $starttime, $endtime);
|
||||
if ($activityavailability === false) {
|
||||
return false;
|
||||
} else if ($activityavailability === true) {
|
||||
// This activity belongs to this time range.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We skip activities in sections that were not yet visible or their 'until' was not in this $starttime - $endtime range.
|
||||
$section = $activity->get_modinfo()->get_section_info($activity->sectionnum);
|
||||
if ($section->availability) {
|
||||
$info = new \core_availability\info_section($section);
|
||||
$sectionavailability = $this->availability_completed_by($info, $starttime, $endtime);
|
||||
if ($sectionavailability === false) {
|
||||
return false;
|
||||
} else if ($sectionavailability === true) {
|
||||
// This activity belongs to this section time range.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// When the course is using format weeks we use the week's end date.
|
||||
$format = course_get_format($activity->get_modinfo()->get_course());
|
||||
// We should change this in MDL-60702.
|
||||
if (method_exists($format, 'get_section_dates')) {
|
||||
$dates = $format->get_section_dates($section);
|
||||
|
||||
// We need to consider the +2 hours added by get_section_dates.
|
||||
// Avoid $starttime <= $dates->end because $starttime may be the start of the next week.
|
||||
if ($starttime < ($dates->end - 7200) && $endtime >= ($dates->end - 7200)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($activity->sectionnum == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->course->get_end() || !$this->course->get_start()) {
|
||||
debugging('Activities which due date is in a time range can not be calculated ' .
|
||||
'if the course doesn\'t have start and end date', DEBUG_DEVELOPER);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!course_format_uses_sections($this->course->get_course_data()->format)) {
|
||||
// If it does not use sections and there are no availability conditions to access it it is available
|
||||
// and we can not magically classify it into any other time range than this one.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Split the course duration in the number of sections and consider the end of each section the due
|
||||
// date of all activities contained in that section.
|
||||
$formatoptions = $format->get_format_options();
|
||||
if (!empty($formatoptions['numsections'])) {
|
||||
$nsections = $formatoptions['numsections'];
|
||||
} else {
|
||||
// There are course format that use sections but without numsections, we fallback to the number
|
||||
// of cached sections in get_section_info_all, not that accurate though.
|
||||
$coursesections = $activity->get_modinfo()->get_section_info_all();
|
||||
$nsections = count($coursesections);
|
||||
if (isset($coursesections[0])) {
|
||||
// We don't count section 0 if it exists.
|
||||
$nsections--;
|
||||
}
|
||||
}
|
||||
|
||||
$courseduration = $this->course->get_end() - $this->course->get_start();
|
||||
$sectionduration = round($courseduration / $nsections);
|
||||
$activitysectionenddate = $this->course->get_start() + ($sectionduration * $activity->sectionnum);
|
||||
if ($activitysectionenddate > $starttime && $activitysectionenddate <= $endtime) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the activity is due or it has been closed during this period, false if during another period, null if no due time.
|
||||
*
|
||||
* It can be overwritten by activities that allow teachers to set a due date or a time close separately
|
||||
* from Moodle availability system. Note that in most of the cases overwriting get_timeclose_field should
|
||||
* be enough.
|
||||
*
|
||||
* Returns true or false if the time close date falls into the provided time range. Null otherwise.
|
||||
*
|
||||
* @param \cm_info $activity
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @param \stdClass|false $student
|
||||
* @return null
|
||||
*/
|
||||
protected function activity_type_completed_by(\cm_info $activity, $starttime, $endtime, $student = false) {
|
||||
|
||||
$fieldname = $this->get_timeclose_field();
|
||||
if (!$fieldname) {
|
||||
// This activity type do not have its own availability control.
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->fill_instance_data($activity);
|
||||
$instance = $this->instancedata[$activity->instance];
|
||||
|
||||
if (!$instance->{$fieldname}) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($starttime < $instance->{$fieldname} && $endtime >= $instance->{$fieldname}) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the field that controls activity availability.
|
||||
*
|
||||
* Should be overwritten by activities that allow teachers to set a due date or a time close separately
|
||||
* from Moodle availability system.
|
||||
*
|
||||
* Just 1 field will not be enough for all cases, but for the most simple ones without
|
||||
* overrides and stuff like that.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
protected function get_timeclose_field() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the activity/section should have been completed during the provided period according to its availability rules.
|
||||
*
|
||||
* @param \core_availability\info $info
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @return bool|null
|
||||
*/
|
||||
protected function availability_completed_by(\core_availability\info $info, $starttime, $endtime) {
|
||||
|
||||
$dateconditions = $info->get_availability_tree()->get_all_children('\availability_date\condition');
|
||||
foreach ($dateconditions as $condition) {
|
||||
// Availability API does not allow us to check from / to dates nicely, we need to be naughty.
|
||||
$conditiondata = $condition->save();
|
||||
|
||||
if ($conditiondata->d === \availability_date\condition::DIRECTION_FROM &&
|
||||
$conditiondata->t > $endtime) {
|
||||
// Skip this activity if any 'from' date is later than the end time.
|
||||
return false;
|
||||
|
||||
} else if ($conditiondata->d === \availability_date\condition::DIRECTION_UNTIL &&
|
||||
($conditiondata->t < $starttime || $conditiondata->t > $endtime)) {
|
||||
// Skip activity if any 'until' date is not in $starttime - $endtime range.
|
||||
return false;
|
||||
} else if ($conditiondata->d === \availability_date\condition::DIRECTION_UNTIL &&
|
||||
$conditiondata->t < $endtime && $conditiondata->t > $starttime) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// This can be interpreted as 'the activity was available but we don't know if its expected completion date
|
||||
// was during this period.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in activity instance data.
|
||||
*
|
||||
* @param \cm_info $cm
|
||||
* @return void
|
||||
*/
|
||||
protected function fill_instance_data(\cm_info $cm) {
|
||||
global $DB;
|
||||
|
||||
if (!isset($this->instancedata[$cm->instance])) {
|
||||
$this->instancedata[$cm->instance] = $DB->get_record($this->get_activity_type(), array('id' => $cm->instance),
|
||||
'*', MUST_EXIST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines indicator type.
|
||||
*
|
||||
|
||||
@@ -197,6 +197,10 @@ abstract class base {
|
||||
|
||||
$dataset = $this->calculate_indicators($sampleids, $samplesorigin, $indicators, $ranges);
|
||||
|
||||
if (empty($dataset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now that we have the indicators in place we can add the time range indicators (and target if provided) to each of them.
|
||||
$this->fill_dataset($dataset, $calculatedtarget);
|
||||
|
||||
|
||||
+131
-26
@@ -15,7 +15,7 @@
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Unit tests for course activities.
|
||||
* Unit tests for activities completed by classification.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
@@ -25,13 +25,13 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Unit tests for course activities
|
||||
* Unit tests for activities completed by classification.
|
||||
*
|
||||
* @package core_analytics
|
||||
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
class community_of_inquiry_activities_completed_by_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* availability_levels
|
||||
@@ -59,8 +59,6 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
// Forum1 is ignored as section 0 does not count.
|
||||
$forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
|
||||
|
||||
$courseman = new \core_analytics\course($course);
|
||||
|
||||
$modinfo = get_fast_modinfo($course, $stu1->id);
|
||||
$cm = $modinfo->get_cm($forum->cmid);
|
||||
|
||||
@@ -93,20 +91,23 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
|
||||
$cm = $modinfo->get_cm($forum->cmid);
|
||||
|
||||
$course = new \core_analytics\course($course);
|
||||
list($indicator, $method) = $this->instantiate_indicator('mod_forum', $course);
|
||||
|
||||
// Condition from after provided end time.
|
||||
$this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-20 00:00:00 GMT'),
|
||||
$this->assertCount(0, $method->invoke($indicator, strtotime('2015-10-20 00:00:00 GMT'),
|
||||
strtotime('2015-10-21 00:00:00 GMT'), $stu1));
|
||||
|
||||
// Condition until before provided start time
|
||||
$this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-25 00:00:00 GMT'),
|
||||
$this->assertCount(0, $method->invoke($indicator, strtotime('2015-10-25 00:00:00 GMT'),
|
||||
strtotime('2015-10-26 00:00:00 GMT'), $stu1));
|
||||
|
||||
// Condition until after provided end time.
|
||||
$this->assertCount(0, $courseman->get_activities('forum', strtotime('2015-10-22 00:00:00 GMT'),
|
||||
$this->assertCount(0, $method->invoke($indicator, strtotime('2015-10-22 00:00:00 GMT'),
|
||||
strtotime('2015-10-23 00:00:00 GMT'), $stu1));
|
||||
|
||||
// Condition until after provided start time and before provided end time.
|
||||
$this->assertCount(1, $courseman->get_activities('forum', strtotime('2015-10-22 00:00:00 GMT'),
|
||||
$this->assertCount(1, $method->invoke($indicator, strtotime('2015-10-22 00:00:00 GMT'),
|
||||
strtotime('2015-10-25 00:00:00 GMT'), $stu1));
|
||||
}
|
||||
|
||||
@@ -138,7 +139,8 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
$forum5 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
|
||||
array('section' => 4));
|
||||
|
||||
$courseman = new \core_analytics\course($course);
|
||||
$course = new \core_analytics\course($course);
|
||||
list($indicator, $method) = $this->instantiate_indicator('mod_forum', $course);
|
||||
|
||||
$this->setUser($stu1);
|
||||
|
||||
@@ -146,10 +148,10 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
$second = $startdate + WEEKSECS;
|
||||
$third = $startdate + (WEEKSECS * 2);
|
||||
$forth = $startdate + (WEEKSECS * 3);
|
||||
$this->assertCount(1, $courseman->get_activities('forum', $first, $first + WEEKSECS, $stu1));
|
||||
$this->assertCount(1, $courseman->get_activities('forum', $second, $second + WEEKSECS, $stu1));
|
||||
$this->assertCount(0, $courseman->get_activities('forum', $third, $third + WEEKSECS, $stu1));
|
||||
$this->assertCount(2, $courseman->get_activities('forum', $forth, $forth + WEEKSECS, $stu1));
|
||||
$this->assertCount(1, $method->invoke($indicator, $first, $first + WEEKSECS, $stu1));
|
||||
$this->assertCount(1, $method->invoke($indicator, $second, $second + WEEKSECS, $stu1));
|
||||
$this->assertCount(0, $method->invoke($indicator, $third, $third + WEEKSECS, $stu1));
|
||||
$this->assertCount(2, $method->invoke($indicator, $forth, $forth + WEEKSECS, $stu1));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,7 +191,8 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
$forum6 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
|
||||
array('section' => 12));
|
||||
|
||||
$courseman = new \core_analytics\course($course);
|
||||
$course = new \core_analytics\course($course);
|
||||
list($indicator, $method) = $this->instantiate_indicator('mod_forum', $course);
|
||||
|
||||
$this->setUser($stu1);
|
||||
|
||||
@@ -199,10 +202,10 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
$second = $startdate + $duration;
|
||||
$third = $startdate + ($duration * 2);
|
||||
$forth = $startdate + ($duration * 3);
|
||||
$this->assertCount(1, $courseman->get_activities('forum', $first, $first + $duration, $stu1));
|
||||
$this->assertCount(1, $courseman->get_activities('forum', $second, $second + $duration, $stu1));
|
||||
$this->assertCount(1, $courseman->get_activities('forum', $third, $third + $duration, $stu1));
|
||||
$this->assertCount(2, $courseman->get_activities('forum', $forth, $forth + $duration, $stu1));
|
||||
$this->assertCount(1, $method->invoke($indicator, $first, $first + $duration, $stu1));
|
||||
$this->assertCount(1, $method->invoke($indicator, $second, $second + $duration, $stu1));
|
||||
$this->assertCount(1, $method->invoke($indicator, $third, $third + $duration, $stu1));
|
||||
$this->assertCount(2, $method->invoke($indicator, $forth, $forth + $duration, $stu1));
|
||||
|
||||
// Split the course in as many parts as sections.
|
||||
$duration = ($enddate - $startdate) / $numsections;
|
||||
@@ -210,15 +213,94 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
// The -1 because section 1 start represents the course start.
|
||||
$timeranges[$i] = $startdate + ($duration * ($i - 1));
|
||||
}
|
||||
$this->assertCount(1, $courseman->get_activities('forum', $timeranges[1], $timeranges[1] + $duration, $stu1));
|
||||
$this->assertCount(1, $courseman->get_activities('forum', $timeranges[4], $timeranges[4] + $duration, $stu1));
|
||||
$this->assertCount(1, $courseman->get_activities('forum', $timeranges[8], $timeranges[8] + $duration, $stu1));
|
||||
$this->assertCount(1, $courseman->get_activities('forum', $timeranges[10], $timeranges[10] + $duration, $stu1));
|
||||
$this->assertCount(1, $courseman->get_activities('forum', $timeranges[12], $timeranges[12] + $duration, $stu1));
|
||||
$this->assertCount(1, $method->invoke($indicator, $timeranges[1], $timeranges[1] + $duration, $stu1));
|
||||
$this->assertCount(1, $method->invoke($indicator, $timeranges[4], $timeranges[4] + $duration, $stu1));
|
||||
$this->assertCount(1, $method->invoke($indicator, $timeranges[8], $timeranges[8] + $duration, $stu1));
|
||||
$this->assertCount(1, $method->invoke($indicator, $timeranges[10], $timeranges[10] + $duration, $stu1));
|
||||
$this->assertCount(1, $method->invoke($indicator, $timeranges[12], $timeranges[12] + $duration, $stu1));
|
||||
|
||||
// Nothing here.
|
||||
$this->assertCount(0, $courseman->get_activities('forum', $timeranges[2], $timeranges[2] + $duration, $stu1));
|
||||
$this->assertCount(0, $courseman->get_activities('forum', $timeranges[3], $timeranges[3] + $duration, $stu1));
|
||||
$this->assertCount(0, $method->invoke($indicator, $timeranges[2], $timeranges[2] + $duration, $stu1));
|
||||
$this->assertCount(0, $method->invoke($indicator, $timeranges[3], $timeranges[3] + $duration, $stu1));
|
||||
}
|
||||
|
||||
/**
|
||||
* test_get_activities_with_specific_restrictions
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_get_activities_with_specific_restrictions() {
|
||||
|
||||
list($course, $stu1) = $this->setup_course();
|
||||
|
||||
$end = strtotime('2015-10-24 00:00:00 GMT');
|
||||
|
||||
// 1 with time close, one without.
|
||||
$params = array('course' => $course->id);
|
||||
$assign1 = $this->getDataGenerator()->create_module('assign', $params);
|
||||
$params['duedate'] = $end;
|
||||
$assign2 = $this->getDataGenerator()->create_module('assign', $params);
|
||||
|
||||
// 1 with time close, one without.
|
||||
$params = array('course' => $course->id);
|
||||
$choice1 = $this->getDataGenerator()->create_module('choice', $params);
|
||||
$params['timeclose'] = $end;
|
||||
$choice1 = $this->getDataGenerator()->create_module('choice', $params);
|
||||
|
||||
// 1 with time close, one without.
|
||||
$params = array('course' => $course->id);
|
||||
$data1 = $this->getDataGenerator()->create_module('data', $params);
|
||||
$params['timeavailableto'] = $end;
|
||||
$data1 = $this->getDataGenerator()->create_module('data', $params);
|
||||
|
||||
// 1 with time close, one without.
|
||||
$params = array('course' => $course->id);
|
||||
$feedback1 = $this->getDataGenerator()->create_module('feedback', $params);
|
||||
$params['timeclose'] = $end;
|
||||
$feedback1 = $this->getDataGenerator()->create_module('feedback', $params);
|
||||
|
||||
// 1 with time close, one without.
|
||||
$params = array('course' => $course->id);
|
||||
$lesson1 = $this->getDataGenerator()->create_module('lesson', $params);
|
||||
$params['deadline'] = $end;
|
||||
$lesson1 = $this->getDataGenerator()->create_module('lesson', $params);
|
||||
|
||||
// 1 with time close, one without.
|
||||
$params = array('course' => $course->id);
|
||||
$quiz1 = $this->getDataGenerator()->create_module('quiz', $params);
|
||||
$params['timeclose'] = $end;
|
||||
$quiz1 = $this->getDataGenerator()->create_module('quiz', $params);
|
||||
|
||||
// 1 with time close, one without.
|
||||
$params = array('course' => $course->id);
|
||||
$scorm1 = $this->getDataGenerator()->create_module('scorm', $params);
|
||||
$params['timeclose'] = $end;
|
||||
$scorm1 = $this->getDataGenerator()->create_module('scorm', $params);
|
||||
|
||||
// 1 with time close, one without.
|
||||
$params = array('course' => $course->id);
|
||||
$workshop1 = $this->getDataGenerator()->create_module('workshop', $params);
|
||||
$params['submissionend'] = $end;
|
||||
$workshop1 = $this->getDataGenerator()->create_module('workshop', $params);
|
||||
|
||||
$course = new \core_analytics\course($course);
|
||||
|
||||
$activitytypes = array('mod_assign', 'mod_choice', 'mod_data', 'mod_feedback', 'mod_lesson',
|
||||
'mod_quiz', 'mod_scorm', 'mod_workshop');
|
||||
foreach ($activitytypes as $activitytype) {
|
||||
|
||||
list($indicator, $method) = $this->instantiate_indicator($activitytype, $course);
|
||||
|
||||
$message = $activitytype . ' activity type returned activities do not match expected size';
|
||||
$this->assertCount(0, $method->invoke($indicator, strtotime('2015-10-20 00:00:00 GMT'),
|
||||
strtotime('2015-10-21 00:00:00 GMT'), $stu1), $message);
|
||||
|
||||
$this->assertCount(0, $method->invoke($indicator, strtotime('2015-10-25 00:00:00 GMT'),
|
||||
strtotime('2015-10-26 00:00:00 GMT'), $stu1), $message);
|
||||
|
||||
$this->assertCount(1, $method->invoke($indicator, strtotime('2015-10-22 00:00:00 GMT'),
|
||||
strtotime('2015-10-25 00:00:00 GMT'), $stu1), $message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,4 +325,27 @@ class core_analytics_course_activities_testcase extends advanced_testcase {
|
||||
return array($course, $stu1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the module cognitive depth indicator and the reflection method.
|
||||
*
|
||||
* @param string $modulename
|
||||
* @param \core_analytics\course $course
|
||||
* @return array
|
||||
*/
|
||||
private function instantiate_indicator($modulename, \core_analytics\course $course) {
|
||||
|
||||
$classname = '\\' . $modulename . '\analytics\indicator\cognitive_depth';
|
||||
$indicator = new $classname();
|
||||
$class = new ReflectionClass($indicator);
|
||||
|
||||
$property = $class->getProperty('course');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue($indicator, $course);
|
||||
|
||||
$method = new ReflectionMethod($indicator, 'get_activities');
|
||||
$method->setAccessible(true);
|
||||
|
||||
return array($indicator, $method);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -86,7 +86,6 @@ class core_analytics_course_testcase extends advanced_testcase {
|
||||
$courseman = new \core_analytics\course($this->course->id);
|
||||
$this->assertFalse($courseman->was_started());
|
||||
$this->assertFalse($courseman->is_finished());
|
||||
$this->assertFalse($courseman->is_valid());
|
||||
|
||||
// Nothing should change when assigning as teacher.
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
@@ -94,7 +93,8 @@ class core_analytics_course_testcase extends advanced_testcase {
|
||||
$this->getDataGenerator()->enrol_user($user->id, $this->course->id, $this->teacherroleid);
|
||||
}
|
||||
$courseman = new \core_analytics\course($this->course->id);
|
||||
$this->assertFalse($courseman->is_valid());
|
||||
$this->assertFalse($courseman->was_started());
|
||||
$this->assertFalse($courseman->is_finished());
|
||||
|
||||
// More students now.
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
@@ -102,7 +102,8 @@ class core_analytics_course_testcase extends advanced_testcase {
|
||||
$this->getDataGenerator()->enrol_user($user->id, $this->course->id, $this->studentroleid);
|
||||
}
|
||||
$courseman = new \core_analytics\course($this->course->id);
|
||||
$this->assertFalse($courseman->is_valid());
|
||||
$this->assertFalse($courseman->was_started());
|
||||
$this->assertFalse($courseman->is_finished());
|
||||
|
||||
// Valid start date unknown end date.
|
||||
$this->course->startdate = gmmktime('0', '0', '0', 10, 24, 2015);
|
||||
@@ -110,7 +111,6 @@ class core_analytics_course_testcase extends advanced_testcase {
|
||||
$courseman = new \core_analytics\course($this->course->id);
|
||||
$this->assertTrue($courseman->was_started());
|
||||
$this->assertFalse($courseman->is_finished());
|
||||
$this->assertFalse($courseman->is_valid());
|
||||
|
||||
// Valid start and end date.
|
||||
$this->course->enddate = gmmktime('0', '0', '0', 8, 27, 2016);
|
||||
@@ -118,7 +118,6 @@ class core_analytics_course_testcase extends advanced_testcase {
|
||||
$courseman = new \core_analytics\course($this->course->id);
|
||||
$this->assertTrue($courseman->was_started());
|
||||
$this->assertTrue($courseman->is_finished());
|
||||
$this->assertTrue($courseman->is_valid());
|
||||
|
||||
// Valid start and ongoing course.
|
||||
$this->course->enddate = gmmktime('0', '0', '0', 8, 27, 2286);
|
||||
@@ -126,7 +125,6 @@ class core_analytics_course_testcase extends advanced_testcase {
|
||||
$courseman = new \core_analytics\course($this->course->id);
|
||||
$this->assertTrue($courseman->was_started());
|
||||
$this->assertFalse($courseman->is_finished());
|
||||
$this->assertFalse($courseman->is_valid());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once(__DIR__ . '/fixtures/test_indicator_max.php');
|
||||
require_once(__DIR__ . '/fixtures/test_indicator_min.php');
|
||||
require_once(__DIR__ . '/fixtures/test_indicator_fullname.php');
|
||||
|
||||
@@ -121,7 +121,7 @@ $string['didntgetusersfromldap'] = "Did not get any users from LDAP -- error? --
|
||||
$string['gotcountrecordsfromldap'] = "Got {\$a} records from LDAP\n";
|
||||
$string['ldapnotconfigured'] = 'The LDAP host url is currently not configured';
|
||||
$string['morethanoneuser'] = 'Strange! More than one user record found in ldap. Only using the first one.';
|
||||
$string['needbcmath'] = 'You need the BCMath extension to use grace logins with Active Directory';
|
||||
$string['needbcmath'] = 'You need the BCMath extension to use expired password checking with Active Directory.';
|
||||
$string['needmbstring'] = 'You need the mbstring extension to change passwords in Active Directory';
|
||||
$string['nodnforusername'] = 'Error in user_update_password(). No DN for: {$a->username}';
|
||||
$string['noemail'] = 'Tried to send you an email but failed!';
|
||||
|
||||
@@ -49,41 +49,14 @@ class block_calendar_month extends block_base {
|
||||
$this->content->footer = '';
|
||||
|
||||
$courseid = $this->page->course->id;
|
||||
$issite = ($courseid == SITEID);
|
||||
|
||||
$course = null;
|
||||
$courses = null;
|
||||
$categories = null;
|
||||
|
||||
if ($issite) {
|
||||
// Being displayed at site level. This will cause the filter to fall back to auto-detecting
|
||||
// the list of courses it will be grabbing events from.
|
||||
$course = get_site();
|
||||
$courses = calendar_get_default_courses();
|
||||
|
||||
if ($this->page->context->contextlevel === CONTEXT_COURSECAT) {
|
||||
// Restrict to categories, and their parents, and the courses that the user is enrolled in within those
|
||||
// categories.
|
||||
$categories = array_keys($this->page->categories);
|
||||
$courses = array_filter($courses, function($course) use ($categories) {
|
||||
return array_search($course->category, $categories) !== false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Forcibly filter events to include only those from the particular course we are in.
|
||||
$course = $this->page->course;
|
||||
$courses = [$course->id => $course];
|
||||
}
|
||||
$categoryid = ($this->page->context->contextlevel === CONTEXT_COURSECAT) ? $this->page->category->id : null;
|
||||
$calendar = \calendar_information::create(time(), $courseid, $categoryid);
|
||||
list($data, $template) = calendar_get_view($calendar, 'mini');
|
||||
|
||||
$renderer = $this->page->get_renderer('core_calendar');
|
||||
|
||||
$calendar = new calendar_information();
|
||||
$calendar->set_sources($course, $courses, $this->page->category);
|
||||
|
||||
list($data, $template) = calendar_get_view($calendar, 'mini');
|
||||
$this->content->text .= $renderer->render_from_template($template, $data);
|
||||
|
||||
if (!$issite) {
|
||||
if ($this->page->course->id != SITEID) {
|
||||
$this->content->text .= $renderer->event_filter();
|
||||
}
|
||||
|
||||
|
||||
@@ -45,38 +45,28 @@ class block_calendar_upcoming extends block_base {
|
||||
}
|
||||
$this->content = new stdClass;
|
||||
$this->content->text = '';
|
||||
$this->content->footer = '';
|
||||
|
||||
$renderer = $this->page->get_renderer('core_calendar');
|
||||
$courseid = $this->page->course->id;
|
||||
$issite = ($courseid == SITEID);
|
||||
|
||||
if ($issite) {
|
||||
// Being displayed at site level. This will cause the filter to fall back to auto-detecting
|
||||
// the list of courses it will be grabbing events from.
|
||||
$course = get_site();
|
||||
$courses = calendar_get_default_courses();
|
||||
} else {
|
||||
// Forcibly filter events to include only those from the particular course we are in.
|
||||
$course = $this->page->course;
|
||||
$courses = [$course->id => $course];
|
||||
}
|
||||
$calendar = new calendar_information(0, 0, 0, time());
|
||||
$calendar->set_sources($course, $courses);
|
||||
|
||||
$categoryid = ($this->page->context->contextlevel === CONTEXT_COURSECAT) ? $this->page->category->id : null;
|
||||
$calendar = \calendar_information::create(time(), $courseid, $categoryid);
|
||||
list($data, $template) = calendar_get_view($calendar, 'upcoming_mini');
|
||||
|
||||
if (empty($data->events)) {
|
||||
$this->content->text = '<div class="post">'. get_string('noupcomingevents', 'calendar').'</div>';
|
||||
} else {
|
||||
$this->content->text .= $renderer->render_from_template($template, $data);
|
||||
$renderer = $this->page->get_renderer('core_calendar');
|
||||
$this->content->text .= $renderer->render_from_template($template, $data);
|
||||
|
||||
$url = new \moodle_url('/calendar/view.php', ['view' => 'upcoming']);
|
||||
if ($courseid != SITEID) {
|
||||
$url->param('course', $this->page->course->id);
|
||||
} else if (!empty($categoryid)) {
|
||||
$url->param('category', $this->page->category->id);
|
||||
}
|
||||
|
||||
$this->content->footer = '<div class="gotocal">
|
||||
<a href="'.$CFG->wwwroot.'/calendar/view.php?view=upcoming&course='.$courseid.'">'.
|
||||
get_string('gotocalendar', 'calendar').'</a>...</div>';
|
||||
$this->content->footer = html_writer::div(
|
||||
html_writer::link($url, get_string('gotocalendar', 'block_calendar_upcoming')),
|
||||
'gotocal'
|
||||
);
|
||||
|
||||
return $this->content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,4 +24,5 @@
|
||||
|
||||
$string['calendar_upcoming:addinstance'] = 'Add a new upcoming events block';
|
||||
$string['calendar_upcoming:myaddinstance'] = 'Add a new upcoming events block to Dashboard';
|
||||
$string['gotocalendar'] = 'Go to calendar...';
|
||||
$string['pluginname'] = 'Upcoming events';
|
||||
|
||||
@@ -37,7 +37,11 @@ class block_html extends block_base {
|
||||
}
|
||||
|
||||
function specialization() {
|
||||
$this->title = isset($this->config->title) ? format_string($this->config->title) : format_string(get_string('newhtmlblock', 'block_html'));
|
||||
if (isset($this->config->title)) {
|
||||
$this->title = $this->title = format_string($this->config->title, true, ['context' => $this->context]);
|
||||
} else {
|
||||
$this->title = get_string('newhtmlblock', 'block_html');
|
||||
}
|
||||
}
|
||||
|
||||
function instance_allow_multiple() {
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events","core_calendar/view_manager","core_calendar/crud","core_calendar/selectors"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o){var p={ROOT:"[data-region='calendar']",DAY:"[data-region='day']",EVENT_ITEM:"[data-region='event-item']",EVENT_LINK:"[data-action='view-event']",NEW_EVENT_BUTTON:"[data-action='new-event-button']",DAY_CONTENT:"[data-region='day-content']",LOADING_ICON:".loading-icon",VIEW_DAY_LINK:"[data-action='view-day-link']",CALENDAR_MONTH_WRAPPER:".calendarwrapper",COURSE_SELECTOR:'select[name="course"]',TODAY:".today"},q=function(a){var b="type"+a;return c.get_string(b,"core_calendar").then(function(a){return a})},r=function(a){k.getEventById(a).then(function(b){if(!b.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+a);var c=b.event;return q(c.eventtype).then(function(a){return c.eventtype=a,c})}).then(function(a){var b={title:a.name,type:j.TYPE,body:d.render("core_calendar/event_summary_body",a),templateContext:{canedit:a.canedit,candelete:a.candelete,isactionevent:a.isactionevent,url:a.url}};return h.create(b)}).done(function(a){a.getRoot().on(g.hidden,function(){a.destroy()}),a.show()}).fail(e.exception)},s=function(b,c,f,g){var h=null,i=g.attr("data-day-timestamp");f&&(h=f.attr("data-day-timestamp")),f&&h==i||d.render("core/loading",{}).then(function(a,b){g.find(p.DAY_CONTENT).addClass("hidden"),d.appendNodeContents(g,a,b),f&&(f.find(p.DAY_CONTENT).addClass("hidden"),d.appendNodeContents(f,a,b))}).then(function(){return k.updateEventStartDay(c,i)}).then(function(){a("body").trigger(l.eventMoved,[c,f,g])}).always(function(){var a=g.find(p.LOADING_ICON);if(g.find(p.DAY_CONTENT).removeClass("hidden"),d.replaceNode(a,"",""),f){var b=f.find(p.LOADING_ICON);f.find(p.DAY_CONTENT).removeClass("hidden"),d.replaceNode(b,"","")}}).fail(e.exception)},t=function(b,c){var d=a("body");d.on(l.created,function(){m.reloadCurrentMonth(b)}),d.on(l.deleted,function(){m.reloadCurrentMonth(b)}),d.on(l.updated,function(){m.reloadCurrentMonth(b)}),d.on(l.editActionEvent,function(a,b){window.location.assign(b)}),d.on(l.moveEvent,s),d.on(l.eventMoved,function(){m.reloadCurrentMonth(b)}),c.then(function(a){d.on(l.editEvent,function(c,d){var e=b.find(o.wrapper);a.setEventId(d),a.setContextId(e.data("contextId")),a.show()})}).fail(e.exception)},u=function(b){b.on("click",p.EVENT_ITEM,function(b){b.preventDefault(),b.stopPropagation();var c=a(b.target),d=null,e=c.closest(p.EVENT_LINK);d=e.length?e.data("eventId"):c.find(p.EVENT_LINK).data("eventId"),r(d)}),b.on("change",p.COURSE_SELECTOR,function(){var c=a(this),d=c.val();m.reloadCurrentMonth(b,d,null).then(function(){return b.find(p.COURSE_SELECTOR).val(d)}).fail(e.exception)});var c=n.registerEventFormModal(b);t(b,c),b.on("click",p.DAY,function(b){var d=a(b.target);if(!d.is(p.VIEW_DAY_LINK)){var f=a(this).attr("data-new-event-timestamp");c.then(function(a){var b=d.closest(o.wrapper);a.setCourseId(b.data("courseid"));var c=b.data("categoryid");"undefined"!=typeof c&&a.setCategoryId(c),a.setContextId(b.data("contextId")),a.setStartTime(f),a.show()}).fail(e.exception),b.preventDefault()}})};return{init:function(b){b=a(b),m.init(b),u(b)}}});
|
||||
define(["jquery","core/ajax","core/str","core/templates","core/notification","core/custom_interaction_events","core/modal_events","core/modal_factory","core_calendar/modal_event_form","core_calendar/summary_modal","core_calendar/repository","core_calendar/events","core_calendar/view_manager","core_calendar/crud","core_calendar/selectors"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o){var p={ROOT:"[data-region='calendar']",DAY:"[data-region='day']",NEW_EVENT_BUTTON:"[data-action='new-event-button']",DAY_CONTENT:"[data-region='day-content']",LOADING_ICON:".loading-icon",VIEW_DAY_LINK:"[data-action='view-day-link']",CALENDAR_MONTH_WRAPPER:".calendarwrapper",TODAY:".today"},q=function(b,c,f,g){var h=null,i=g.attr("data-day-timestamp");f&&(h=f.attr("data-day-timestamp")),f&&h==i||d.render("core/loading",{}).then(function(a,b){g.find(p.DAY_CONTENT).addClass("hidden"),d.appendNodeContents(g,a,b),f&&(f.find(p.DAY_CONTENT).addClass("hidden"),d.appendNodeContents(f,a,b))}).then(function(){return k.updateEventStartDay(c,i)}).then(function(){a("body").trigger(l.eventMoved,[c,f,g])}).always(function(){var a=g.find(p.LOADING_ICON);if(g.find(p.DAY_CONTENT).removeClass("hidden"),d.replaceNode(a,"",""),f){var b=f.find(p.LOADING_ICON);f.find(p.DAY_CONTENT).removeClass("hidden"),d.replaceNode(b,"","")}}).fail(e.exception)},r=function(b,c){var d=a("body");d.on(l.created,function(){m.reloadCurrentMonth(b)}),d.on(l.deleted,function(){m.reloadCurrentMonth(b)}),d.on(l.updated,function(){m.reloadCurrentMonth(b)}),d.on(l.editActionEvent,function(a,b){window.location.assign(b)}),d.on(l.moveEvent,q),d.on(l.eventMoved,function(){m.reloadCurrentMonth(b)}),n.registerEditListeners(b,c)},s=function(b){b.on("change",o.elements.courseSelector,function(){var c=a(this),d=c.val();m.reloadCurrentMonth(b,d,null).then(function(){return b.find(o.elements.courseSelector).val(d)}).fail(e.exception)});var c=n.registerEventFormModal(b);r(b,c),b.on("click",p.DAY,function(b){var d=a(b.target);if(!d.is(p.VIEW_DAY_LINK)){var f=a(this).attr("data-new-event-timestamp");c.then(function(a){var b=d.closest(o.wrapper);a.setCourseId(b.data("courseid"));var c=b.data("categoryid");"undefined"!=typeof c&&a.setCategoryId(c),a.setContextId(b.data("contextId")),a.setStartTime(f),a.show()}).fail(e.exception),b.preventDefault()}})};return{init:function(b){b=a(b),m.init(b),s(b)}}});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core_calendar/selectors","core_calendar/events","core_calendar/view_manager"],function(a,b,c,d){var e=function(b){var d=a("body"),e="."+b.attr("id");d.on(c.created+e,b,f),d.on(c.deleted+e,b,f),d.on(c.updated+e,b,f),d.on(c.eventMoved+e,b,f)},f=function(b){var e=b.data,f=a("body"),g="."+e.attr("id");e.is(":visible")?d.reloadCurrentMonth(e):(f.on(c.created+g),f.on(c.deleted+g),f.on(c.updated+g),f.on(c.eventMoved+g))},g=function(d){a("body").on(c.filterChanged,function(a,c){var e=d.find(b.eventType[c.type]);e.toggleClass("calendar_event_"+c.type,!c.hidden)})};return{init:function(b){b=a(b),d.init(b),g(b),e(b)}}});
|
||||
define(["jquery","core_calendar/selectors","core_calendar/events","core_calendar/view_manager"],function(a,b,c,d){var e=function(b){var d=a("body"),e="."+b.attr("id");d.on(c.created+e,b,f),d.on(c.deleted+e,b,f),d.on(c.updated+e,b,f),d.on(c.eventMoved+e,b,f)},f=function(b){var e=b.data,f=a("body"),g="."+e.attr("id");e.is(":visible")?d.reloadCurrentMonth(e):(f.off(c.created+g),f.off(c.deleted+g),f.off(c.updated+g),f.off(c.eventMoved+g))},g=function(e){a("body").on(c.filterChanged,function(a,c){var d=e.find(b.eventType[c.type]);d.toggleClass("calendar_event_"+c.type,!c.hidden)});var f="."+e.attr("id");a("body").on("change"+f,b.elements.courseSelector,function(){if(e.is(":visible")){var b=a(this),c=b.val(),g=null;d.reloadCurrentMonth(e,c,g)}else a("body").off("change"+f)})};return{init:function(b){b=a(b),d.init(b),g(b),e(b)}}});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/str","core/notification","core_calendar/selectors","core_calendar/events","core_calendar/view_manager","core_calendar/repository","core/modal_factory","core_calendar/modal_event_form","core/modal_events","core_calendar/crud"],function(a,b,c,d,e,f,g,h,i,j,k){var l=function(b,g){var h=a("body");k.registerEventFormModal(b),k.registerRemove(b);var i="reloadCurrent"+g.charAt(0).toUpperCase()+g.slice(1);h.on(e.created,function(){f[i](b)}),h.on(e.deleted,function(){f[i](b)}),h.on(e.updated,function(){f[i](b)}),b.on("change",d.courseSelector,function(){var e=a(this),g=e.val();f[i](b,g,null).then(function(){return b.find(d.courseSelector).val(g)}).fail(c.exception)}),h.on(e.filterChanged,function(a,c){var e=b.find(d.eventType[c.type]);1==c.hidden?e.addClass("hidden"):e.removeClass("hidden")})};return{init:function(b,c){b=a(b),f.init(b,c),l(b,c)}}});
|
||||
define(["jquery","core/str","core/notification","core_calendar/selectors","core_calendar/events","core_calendar/view_manager","core_calendar/repository","core/modal_factory","core_calendar/modal_event_form","core/modal_events","core_calendar/crud"],function(a,b,c,d,e,f,g,h,i,j,k){var l=function(b,g){var h=a("body");k.registerRemove(b);var i="reloadCurrent"+g.charAt(0).toUpperCase()+g.slice(1);h.on(e.created,function(){f[i](b)}),h.on(e.deleted,function(){f[i](b)}),h.on(e.updated,function(){f[i](b)}),b.on("change",d.courseSelector,function(){var e=a(this),g=e.val();f[i](b,g,null).then(function(){return b.find(d.courseSelector).val(g)}).then(function(){window.history.pushState({},"","?view=upcoming&course="+g)}).fail(c.exception)}),h.on(e.filterChanged,function(a,c){var e=b.find(d.eventType[c.type]);1==c.hidden?e.addClass("hidden"):e.removeClass("hidden")});var j=k.registerEventFormModal(b);k.registerEditListeners(b,j)};return{init:function(b,c){b=a(b),f.init(b,c),l(b,c)}}});
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/str","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","core/modal_factory","core/modal_events","core_calendar/modal_event_form","core_calendar/repository","core_calendar/events","core_calendar/modal_delete","core_calendar/selectors"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){function n(d,e,f){var i=[{key:"deleteevent",component:"calendar"}];f=parseInt(f,10);var m,n=f>1;n?(i.push({key:"confirmeventseriesdelete",component:"calendar",param:{name:e,count:f}}),m=g.create({type:l.TYPE})):(i.push({key:"confirmeventdelete",component:"calendar",param:e}),m=g.create({type:g.types.SAVE_CANCEL})),m.then(function(a){a.show()}).fail(c.exception);var o=b.get_strings(i),p=a.when(o,m).then(function(b,e){return e.setTitle(b[0]),e.setBody(b[1]),n||e.setSaveButtonText(b[0]),e.getRoot().on(h.save,function(){j.deleteEvent(d,!1).then(function(){a("body").trigger(k.deleted,[d,!1])})["catch"](c.exception)}),e.getRoot().on(k.deleteAll,function(){j.deleteEvent(d,!0).then(function(){a("body").trigger(k.deleted,[d,!0])})["catch"](c.exception)}),e}).fail(c.exception);return p}function o(b){b.on("click",m.actions.remove,function(b){var c=a(this).closest(m.eventItem),d=c.data("eventId"),e=c.data("eventTitle"),f=c.data("eventCount");n(d,e,f),b.preventDefault()})}var p=function(b){var d=g.create({type:i.TYPE,large:!0});return b.on("click",m.actions.create,function(a){d.then(function(a){var c=b.find(m.wrapper),d=c.data("categoryid");"undefined"!=typeof d&&a.setCategoryId(d);var e=b.find(m.today),f=b.find(m.day);!e.length&&f.length&&a.setStartTime(f.data("newEventTimestamp")),a.setContextId(c.data("contextId")),a.setCourseId(c.data("courseid")),a.show()}).fail(c.exception),a.preventDefault()}),b.on("click",m.actions.edit,function(b){b.preventDefault();var e=a(b.currentTarget),f=e.closest(m.wrapper),g=e.closest(m.eventItem);d.then(function(a){a.setEventId(g.data("eventId")),a.setContextId(f.data("contextId")),a.show()}).fail(c.exception)}),d};return{registerRemove:o,registerEventFormModal:p}});
|
||||
define(["jquery","core/str","core/notification","core/custom_interaction_events","core/modal","core/modal_registry","core/modal_factory","core/modal_events","core_calendar/modal_event_form","core_calendar/repository","core_calendar/events","core_calendar/modal_delete","core_calendar/selectors"],function(a,b,c,d,e,f,g,h,i,j,k,l,m){function n(d,e,f){var i=[{key:"deleteevent",component:"calendar"}];f=parseInt(f,10);var m,n=f>1;n?(i.push({key:"confirmeventseriesdelete",component:"calendar",param:{name:e,count:f}}),m=g.create({type:l.TYPE})):(i.push({key:"confirmeventdelete",component:"calendar",param:e}),m=g.create({type:g.types.SAVE_CANCEL})),m.then(function(a){a.show()}).fail(c.exception);var o=b.get_strings(i),p=a.when(o,m).then(function(b,e){return e.setTitle(b[0]),e.setBody(b[1]),n||e.setSaveButtonText(b[0]),e.getRoot().on(h.save,function(){j.deleteEvent(d,!1).then(function(){a("body").trigger(k.deleted,[d,!1])})["catch"](c.exception)}),e.getRoot().on(k.deleteAll,function(){j.deleteEvent(d,!0).then(function(){a("body").trigger(k.deleted,[d,!0])})["catch"](c.exception)}),e}).fail(c.exception);return p}function o(b){b.on("click",m.actions.remove,function(b){var c=a(this).closest(m.eventItem),d=c.data("eventId"),e=c.data("eventTitle"),f=c.data("eventCount");n(d,e,f),b.preventDefault()})}function p(b,d){return d.then(function(c){a("body").on(k.editEvent,function(a,d){var e=b.find(m.wrapper);c.setEventId(d),c.setContextId(e.data("contextId")),c.show(),a.stopImmediatePropagation()})}).fail(c.exception),d}var q=function(b){var d=g.create({type:i.TYPE,large:!0});return b.on("click",m.actions.create,function(a){d.then(function(a){var c=b.find(m.wrapper),d=c.data("categoryid");"undefined"!=typeof d&&a.setCategoryId(d);var e=b.find(m.today),f=b.find(m.day);!e.length&&f.length&&a.setStartTime(f.data("newEventTimestamp")),a.setContextId(c.data("contextId")),a.setCourseId(c.data("courseid")),a.show()}).fail(c.exception),a.preventDefault()}),b.on("click",m.actions.edit,function(b){b.preventDefault();var e=a(b.currentTarget),f=e.closest(m.wrapper),g=e.closest(m.eventItem);d.then(function(a){a.setEventId(g.data("eventId")),a.setContextId(f.data("contextId")),a.show(),b.stopImmediatePropagation()}).fail(c.exception)}),d};return{registerRemove:o,registerEditListeners:p,registerEventFormModal:q}});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/event","core/str","core/notification","core/templates","core/custom_interaction_events","core/modal","core/modal_registry","core/fragment","core_calendar/events","core_calendar/repository"],function(a,b,c,d,e,f,g,h,i,j,k){var l=!1,m={SAVE_BUTTON:'[data-action="save"]',LOADING_ICON_CONTAINER:'[data-region="loading-icon-container"]'},n=function(a){g.call(this,a),this.eventId=null,this.startTime=null,this.courseId=null,this.categoryId=null,this.contextId=null,this.reloadingBody=!1,this.reloadingTitle=!1,this.saveButton=this.getFooter().find(m.SAVE_BUTTON)};return n.TYPE="core_calendar-modal_event_form",n.prototype=Object.create(g.prototype),n.prototype.constructor=n,n.prototype.setContextId=function(a){this.contextId=a},n.prototype.getContextId=function(){return this.contextId},n.prototype.setCourseId=function(a){this.courseId=a},n.prototype.getCourseId=function(){return this.courseId},n.prototype.setCategoryId=function(a){this.categoryId=a},n.prototype.getCategoryId=function(){return this.categoryId},n.prototype.hasCourseId=function(){return null!==this.courseId},n.prototype.hasCategoryId=function(){return null!==this.categoryId},n.prototype.setEventId=function(a){this.eventId=a},n.prototype.getEventId=function(){return this.eventId},n.prototype.hasEventId=function(){return null!==this.eventId},n.prototype.setStartTime=function(a){this.startTime=a},n.prototype.getStartTime=function(){return this.startTime},n.prototype.hasStartTime=function(){return null!==this.startTime},n.prototype.getForm=function(){return this.getBody().find("form")},n.prototype.disableButtons=function(){this.saveButton.prop("disabled",!0)},n.prototype.enableButtons=function(){this.saveButton.prop("disabled",!1)},n.prototype.reloadTitleContent=function(){return this.reloadingTitle?this.titlePromise:(this.reloadingTitle=!0,this.hasEventId()?this.titlePromise=c.get_string("editevent","calendar"):this.titlePromise=c.get_string("newevent","calendar"),this.titlePromise.then(function(a){return this.setTitle(a),a}.bind(this)).always(function(){this.reloadingTitle=!1}.bind(this)).fail(d.exception),this.titlePromise)},n.prototype.reloadBodyContent=function(a){if(this.reloadingBody)return this.bodyPromise;this.reloadingBody=!0,this.disableButtons();var b={};return this.hasEventId()&&(b.eventid=this.getEventId()),this.hasStartTime()&&(b.starttime=this.getStartTime()),this.hasCourseId()&&(b.courseid=this.getCourseId()),this.hasCategoryId()&&(b.categoryid=this.getCategoryId()),"undefined"!=typeof a&&(b.formdata=a),this.bodyPromise=i.loadFragment("calendar","event_form",this.getContextId(),b),this.setBody(this.bodyPromise),this.bodyPromise.then(function(){this.enableButtons()}.bind(this)).fail(d.exception).always(function(){this.reloadingBody=!1}.bind(this)).fail(d.exception),this.bodyPromise},n.prototype.reloadAllContent=function(){return a.when(this.reloadTitleContent(),this.reloadBodyContent())},n.prototype.show=function(){this.reloadAllContent(),g.prototype.show.call(this)},n.prototype.hide=function(){g.prototype.hide.call(this),this.setEventId(null),this.setStartTime(null),this.setCourseId(null),this.setCategoryId(null)},n.prototype.getFormData=function(){return this.getForm().serialize()},n.prototype.save=function(){var b=this.saveButton.find(m.LOADING_ICON_CONTAINER);b.removeClass("hidden"),this.disableButtons();var c=this.getFormData();return k.submitCreateUpdateForm(c).then(function(b){return b.validationerror?void this.reloadBodyContent(c):(this.hide(),void(this.hasEventId()?a("body").trigger(j.updated,[b.event]):a("body").trigger(j.created,[b.event])))}.bind(this)).always(function(){b.addClass("hidden"),this.enableButtons()}.bind(this)).fail(d.exception)},n.prototype.registerEventListeners=function(){g.prototype.registerEventListeners.call(this),this.getModal().on(f.events.activate,m.SAVE_BUTTON,function(a,b){this.getForm().submit(),b.originalEvent.preventDefault(),a.stopPropagation()}.bind(this)),this.getModal().on("submit",function(a){this.save(),a.preventDefault(),a.stopPropagation()}.bind(this))},l||(h.register(n.TYPE,n,"calendar/modal_event_form"),l=!0),n});
|
||||
define(["jquery","core/event","core/str","core/notification","core/templates","core/custom_interaction_events","core/modal","core/modal_registry","core/fragment","core_calendar/events","core_calendar/repository"],function(a,b,c,d,e,f,g,h,i,j,k){var l=!1,m={SAVE_BUTTON:'[data-action="save"]',LOADING_ICON_CONTAINER:'[data-region="loading-icon-container"]'},n=function(a){g.call(this,a),this.eventId=null,this.startTime=null,this.courseId=null,this.categoryId=null,this.contextId=null,this.reloadingBody=!1,this.reloadingTitle=!1,this.saveButton=this.getFooter().find(m.SAVE_BUTTON)};return n.TYPE="core_calendar-modal_event_form",n.prototype=Object.create(g.prototype),n.prototype.constructor=n,n.prototype.setContextId=function(a){this.contextId=a},n.prototype.getContextId=function(){return this.contextId},n.prototype.setCourseId=function(a){this.courseId=a},n.prototype.getCourseId=function(){return this.courseId},n.prototype.setCategoryId=function(a){this.categoryId=a},n.prototype.getCategoryId=function(){return this.categoryId},n.prototype.hasCourseId=function(){return null!==this.courseId},n.prototype.hasCategoryId=function(){return null!==this.categoryId},n.prototype.setEventId=function(a){this.eventId=a},n.prototype.getEventId=function(){return this.eventId},n.prototype.hasEventId=function(){return null!==this.eventId},n.prototype.setStartTime=function(a){this.startTime=a},n.prototype.getStartTime=function(){return this.startTime},n.prototype.hasStartTime=function(){return null!==this.startTime},n.prototype.getForm=function(){return this.getBody().find("form")},n.prototype.disableButtons=function(){this.saveButton.prop("disabled",!0)},n.prototype.enableButtons=function(){this.saveButton.prop("disabled",!1)},n.prototype.reloadTitleContent=function(){return this.reloadingTitle?this.titlePromise:(this.reloadingTitle=!0,this.hasEventId()?this.titlePromise=c.get_string("editevent","calendar"):this.titlePromise=c.get_string("newevent","calendar"),this.titlePromise.then(function(a){return this.setTitle(a),a}.bind(this)).always(function(){this.reloadingTitle=!1}.bind(this)).fail(d.exception),this.titlePromise)},n.prototype.reloadBodyContent=function(a){if(this.reloadingBody)return this.bodyPromise;this.reloadingBody=!0,this.disableButtons();var b={};return this.hasEventId()&&(b.eventid=this.getEventId()),this.hasStartTime()&&(b.starttime=this.getStartTime()),this.hasCourseId()&&(b.courseid=this.getCourseId()),this.hasCategoryId()&&(b.categoryid=this.getCategoryId()),"undefined"!=typeof a&&(b.formdata=a),this.bodyPromise=i.loadFragment("calendar","event_form",this.getContextId(),b),this.setBody(this.bodyPromise),this.bodyPromise.then(function(){this.enableButtons()}.bind(this)).fail(d.exception).always(function(){this.reloadingBody=!1}.bind(this)).fail(d.exception),this.bodyPromise},n.prototype.reloadAllContent=function(){return a.when(this.reloadTitleContent(),this.reloadBodyContent())},n.prototype.show=function(){this.reloadAllContent(),g.prototype.show.call(this)},n.prototype.hide=function(){g.prototype.hide.call(this),this.setEventId(null),this.setStartTime(null),this.setCourseId(null),this.setCategoryId(null)},n.prototype.getFormData=function(){return this.getForm().serialize()},n.prototype.save=function(){var b=this.saveButton.find(m.LOADING_ICON_CONTAINER);b.removeClass("hidden"),this.disableButtons();var c=this.getFormData();return k.submitCreateUpdateForm(c).then(function(b){if(b.validationerror)return void this.reloadBodyContent(c);var d=this.hasEventId();this.hide(),d?a("body").trigger(j.updated,[b.event]):a("body").trigger(j.created,[b.event])}.bind(this)).always(function(){b.addClass("hidden"),this.enableButtons()}.bind(this)).fail(d.exception)},n.prototype.registerEventListeners=function(){g.prototype.registerEventListeners.call(this),this.getModal().on(f.events.activate,m.SAVE_BUTTON,function(a,b){this.getForm().submit(),b.originalEvent.preventDefault(),a.stopPropagation()}.bind(this)),this.getModal().on("submit",function(a){this.save(),a.preventDefault(),a.stopPropagation()}.bind(this))},l||(h.register(n.TYPE,n,"calendar/modal_event_form"),l=!0),n});
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/ajax"],function(a,b){var c=function(a,c){"undefined"==typeof c&&(c=!1);var d={methodname:"core_calendar_delete_calendar_events",args:{events:[{eventid:a,repeat:c}]}};return b.call([d])[0]},d=function(a){var c={methodname:"core_calendar_get_calendar_event_by_id",args:{eventid:a}};return b.call([c])[0]},e=function(a){var c={methodname:"core_calendar_submit_create_update_form",args:{formdata:a}};return b.call([c])[0]},f=function(a,c,d,e,f){var g={methodname:"core_calendar_get_calendar_monthly_view",args:{year:a,month:c,courseid:d,categoryid:e,includenavigation:f}};return b.call([g])[0]},g=function(a,c,d,e,f){var g={methodname:"core_calendar_get_calendar_day_view",args:{year:a,month:c,day:d,courseid:e,categoryid:f}};return b.call([g])[0]},h=function(a,c){var d={methodname:"core_calendar_update_event_start_day",args:{eventid:a,daytimestamp:c}};return b.call([d])[0]},i=function(a){var c={methodname:"core_calendar_get_calendar_upcoming_view",args:{courseid:a}};return b.call([c])[0]};return{getEventById:d,deleteEvent:c,updateEventStartDay:h,submitCreateUpdateForm:e,getCalendarMonthData:f,getCalendarDayData:g,getCalendarUpcomingData:i}});
|
||||
define(["jquery","core/ajax"],function(a,b){var c=function(a,c){"undefined"==typeof c&&(c=!1);var d={methodname:"core_calendar_delete_calendar_events",args:{events:[{eventid:a,repeat:c}]}};return b.call([d])[0]},d=function(a){var c={methodname:"core_calendar_get_calendar_event_by_id",args:{eventid:a}};return b.call([c])[0]},e=function(a){var c={methodname:"core_calendar_submit_create_update_form",args:{formdata:a}};return b.call([c])[0]},f=function(a,c,d,e,f){var g={methodname:"core_calendar_get_calendar_monthly_view",args:{year:a,month:c,courseid:d,categoryid:e,includenavigation:f}};return b.call([g])[0]},g=function(a,c,d,e,f){var g={methodname:"core_calendar_get_calendar_day_view",args:{year:a,month:c,day:d,courseid:e,categoryid:f}};return b.call([g])[0]},h=function(a,c){var d={methodname:"core_calendar_update_event_start_day",args:{eventid:a,daytimestamp:c}};return b.call([d])[0]},i=function(a,c){var d={methodname:"core_calendar_get_calendar_upcoming_view",args:{courseid:a,categoryid:c}};return b.call([d])[0]};return{getEventById:d,deleteEvent:c,updateEventStartDay:h,submitCreateUpdateForm:e,getCalendarMonthData:f,getCalendarDayData:g,getCalendarUpcomingData:i}});
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
define([],function(){return{eventFilterItem:"[data-action='filter-event-type']",eventType:{site:"[data-eventtype-site]",category:"[data-eventtype-category]",course:"[data-eventtype-course]",group:"[data-eventtype-group]",user:"[data-eventtype-user]"},popoverType:{site:"[data-popover-eventtype-site]",category:"[data-popover-eventtype-category]",course:"[data-popover-eventtype-course]",group:"[data-popover-eventtype-group]",user:"[data-popover-eventtype-user]"},calendarPeriods:{month:"[data-period='month']"},courseSelector:'select[name="course"]',actions:{create:'[data-action="new-event-button"]',edit:'[data-action="edit"]',remove:'[data-action="delete"]'},today:".today",day:'[data-region="day"]',wrapper:".calendarwrapper",eventItem:'[data-type="event"]'}});
|
||||
define([],function(){return{eventFilterItem:"[data-action='filter-event-type']",eventType:{site:"[data-eventtype-site]",category:"[data-eventtype-category]",course:"[data-eventtype-course]",group:"[data-eventtype-group]",user:"[data-eventtype-user]"},popoverType:{site:"[data-popover-eventtype-site]",category:"[data-popover-eventtype-category]",course:"[data-popover-eventtype-course]",group:"[data-popover-eventtype-group]",user:"[data-popover-eventtype-user]"},calendarPeriods:{month:"[data-period='month']"},courseSelector:'select[name="course"]',actions:{create:'[data-action="new-event-button"]',edit:'[data-action="edit"]',remove:'[data-action="delete"]',viewEvent:'[data-action="view-event"]'},elements:{courseSelector:'select[name="course"]'},today:".today",day:'[data-region="day"]',wrapper:".calendarwrapper",eventItem:'[data-type="event"]',links:{navLink:".calendarwrapper .arrow_link",eventLink:"[data-region='event-item']"},containers:{loadingIcon:'[data-region="overlay-icon-container"]'}}});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/templates","core/notification","core_calendar/repository","core_calendar/events","core_calendar/selectors"],function(a,b,c,d,e,f){var g={CALENDAR_NAV_LINK:".calendarwrapper .arrow_link",LOADING_ICON_CONTAINER:'[data-region="overlay-icon-container"]'},h=function(b){b=a(b),b.on("click",g.CALENDAR_NAV_LINK,function(c){var d=b.find(f.wrapper),e=d.data("view"),g=d.data("courseid"),h=d.data("categoryid"),i=a(c.currentTarget);"month"===e?(j(b,i.attr("href"),i.data("year"),i.data("month"),g,h),c.preventDefault()):"day"===e&&(n(b,i.attr("href"),i.data("year"),i.data("month"),i.data("day"),g,h),c.preventDefault())})},i=function(g,h,i,j,k,l){o(g),l=l||g.find(f.wrapper),M.util.js_pending([g.get("id"),h,i,j].join("-"));var m=g.data("includenavigation");return d.getCalendarMonthData(h,i,j,k,m).then(function(a){return b.render(g.attr("data-template"),a)}).then(function(a,c){return b.replaceNode(l,a,c)}).then(function(){a("body").trigger(e.viewUpdated)}).always(function(){return M.util.js_complete([g.get("id"),h,i,j].join("-")),p(g)}).fail(c.exception)},j=function(b,c,d,f,g,h){return i(b,d,f,g,h).then(function(){return c.length&&"#"!==c&&window.history.pushState({},"",c),arguments}).then(function(){return a("body").trigger(e.monthChanged,[d,f,g,h]),arguments})},k=function(a,b,c){var d=a.find(f.wrapper).data("year"),e=a.find(f.wrapper).data("month");return"undefined"==typeof b&&(b=a.find(f.wrapper).data("courseid")),"undefined"==typeof c&&(c=a.find(f.wrapper).data("categoryid")),i(a,d,e,b,c)},l=function(g,h,i,j,k,l,m){o(g),m=m||g.find(f.wrapper),M.util.js_pending([g.get("id"),h,i,j,k,l].join("-"));var n=g.data("includenavigation");return d.getCalendarDayData(h,i,j,k,l,n).then(function(a){return b.render(g.attr("data-template"),a)}).then(function(a,c){return b.replaceNode(m,a,c)}).then(function(){a("body").trigger(e.viewUpdated)}).always(function(){return M.util.js_complete([g.get("id"),h,i,j,k,l].join("-")),p(g)}).fail(c.exception)},m=function(a,b,c){var d=a.find(f.wrapper),e=d.data("year"),g=d.data("month"),h=d.data("day");return b||(b=a.find(f.wrapper).data("courseid")),"undefined"==typeof c&&(c=a.find(f.wrapper).data("categoryid")),l(a,e,g,h,b,c)},n=function(b,c,d,f,g,h,i){return l(b,d,f,g,h,i).then(function(){return c.length&&"#"!==c&&window.history.pushState({},"",c),arguments}).then(function(){return a("body").trigger(e.dayChanged,[d,f,g,h,i]),arguments})},o=function(a){var b=a.find(g.LOADING_ICON_CONTAINER);b.removeClass("hidden")},p=function(a){var b=a.find(g.LOADING_ICON_CONTAINER);b.addClass("hidden")},q=function(g,h){o(g);var i=g.find(f.wrapper);return h||(h=g.find(f.wrapper).data("courseid")),d.getCalendarUpcomingData(h).then(function(a){return b.render(g.attr("data-template"),a)}).then(function(a,c){return window.history.replaceState(null,null,"?view=upcoming&course="+h),b.replaceNode(i,a,c)}).then(function(){a("body").trigger(e.viewUpdated)}).always(function(){return p(g)}).fail(c.exception)};return{init:function(a){h(a)},reloadCurrentMonth:k,changeMonth:j,refreshMonthContent:i,reloadCurrentDay:m,changeDay:n,refreshDayContent:l,reloadCurrentUpcoming:q}});
|
||||
define(["jquery","core/templates","core/str","core/notification","core_calendar/repository","core_calendar/events","core_calendar/selectors","core/modal_factory","core/modal_events","core_calendar/summary_modal"],function(a,b,c,d,e,f,g,h,i,j){var k=function(b){b=a(b),b.on("click",g.links.eventLink,function(b){var c,d=a(b.target),e=null;c=d.is(g.actions.viewEvent)?d:d.closest(g.actions.viewEvent),e=c.length?c.data("eventId"):d.find(g.actions.viewEvent).data("eventId"),e&&(b.preventDefault(),b.stopPropagation(),w(e))}),b.on("click",g.links.navLink,function(c){var d=b.find(g.wrapper),e=d.data("view"),f=d.data("courseid"),h=d.data("categoryid"),i=a(c.currentTarget);"month"===e?(m(b,i.attr("href"),i.data("year"),i.data("month"),f,h),c.preventDefault()):"day"===e&&(q(b,i.attr("href"),i.data("year"),i.data("month"),i.data("day"),f,h),c.preventDefault())})},l=function(c,h,i,j,k,l){r(c),l=l||c.find(g.wrapper),M.util.js_pending([c.get("id"),h,i,j].join("-"));var m=c.data("includenavigation");return e.getCalendarMonthData(h,i,j,k,m).then(function(a){return b.render(c.attr("data-template"),a)}).then(function(a,c){return b.replaceNode(l,a,c)}).then(function(){a("body").trigger(f.viewUpdated)}).always(function(){return M.util.js_complete([c.get("id"),h,i,j].join("-")),s(c)}).fail(d.exception)},m=function(b,c,d,e,g,h){return l(b,d,e,g,h).then(function(){return c.length&&"#"!==c&&window.history.pushState({},"",c),arguments}).then(function(){return a("body").trigger(f.monthChanged,[d,e,g,h]),arguments})},n=function(a,b,c){var d=a.find(g.wrapper).data("year"),e=a.find(g.wrapper).data("month");return"undefined"==typeof b&&(b=a.find(g.wrapper).data("courseid")),"undefined"==typeof c&&(c=a.find(g.wrapper).data("categoryid")),l(a,d,e,b,c)},o=function(c,h,i,j,k,l,m){r(c),m=m||c.find(g.wrapper),M.util.js_pending([c.get("id"),h,i,j,k,l].join("-"));var n=c.data("includenavigation");return e.getCalendarDayData(h,i,j,k,l,n).then(function(a){return b.render(c.attr("data-template"),a)}).then(function(a,c){return b.replaceNode(m,a,c)}).then(function(){a("body").trigger(f.viewUpdated)}).always(function(){return M.util.js_complete([c.get("id"),h,i,j,k,l].join("-")),s(c)}).fail(d.exception)},p=function(a,b,c){var d=a.find(g.wrapper),e=d.data("year"),f=d.data("month"),h=d.data("day");return b||(b=a.find(g.wrapper).data("courseid")),"undefined"==typeof c&&(c=a.find(g.wrapper).data("categoryid")),o(a,e,f,h,b,c)},q=function(b,c,d,e,g,h,i){return o(b,d,e,g,h,i).then(function(){return c.length&&"#"!==c&&window.history.pushState({},"",c),arguments}).then(function(){return a("body").trigger(f.dayChanged,[d,e,g,h,i]),arguments})},r=function(a){var b=a.find(g.containers.loadingIcon);b.removeClass("hidden")},s=function(a){var b=a.find(g.containers.loadingIcon);b.addClass("hidden")},t=function(c,h,i){r(c);var j=c.find(g.wrapper);return"undefined"==typeof h&&(h=c.find(g.wrapper).data("courseid")),"undefined"==typeof i&&(i=c.find(g.wrapper).data("categoryid")),e.getCalendarUpcomingData(h,i).then(function(a){return b.render(c.attr("data-template"),a)}).then(function(a,c){return b.replaceNode(j,a,c)}).then(function(){a("body").trigger(f.viewUpdated)}).always(function(){return s(c)}).fail(d.exception)},u=function(a){switch(a){case"user":return"user";case"site":return"site";case"group":return"group";case"category":return"category";default:return"course"}},v=function(a){return"calendar_event_"+u(a)},w=function(a){var c="";e.getEventById(a).then(function(b){if(!b.event)throw new Error("Error encountered while trying to fetch calendar event with ID: "+a);var d=b.event;return c=v(d.eventtype),x(d.eventtype).then(function(a){return d.eventtype=a,d})}).then(function(a){var d={title:a.name,type:j.TYPE,body:b.render("core_calendar/event_summary_body",a),templateContext:{canedit:a.canedit,candelete:a.candelete,headerclasses:c,isactionevent:a.isactionevent,url:a.url}};return h.create(d)}).done(function(a){a.getRoot().on(i.hidden,function(){a.destroy()}),a.show()}).fail(d.exception)},x=function(a){var b="type"+u(a);return c.get_string(b,"core_calendar").then(function(a){return a})};return{init:function(a){k(a)},reloadCurrentMonth:n,changeMonth:m,refreshMonthContent:l,reloadCurrentDay:p,changeDay:q,refreshDayContent:o,reloadCurrentUpcoming:t}});
|
||||
@@ -63,77 +63,14 @@ define([
|
||||
var SELECTORS = {
|
||||
ROOT: "[data-region='calendar']",
|
||||
DAY: "[data-region='day']",
|
||||
EVENT_ITEM: "[data-region='event-item']",
|
||||
EVENT_LINK: "[data-action='view-event']",
|
||||
NEW_EVENT_BUTTON: "[data-action='new-event-button']",
|
||||
DAY_CONTENT: "[data-region='day-content']",
|
||||
LOADING_ICON: '.loading-icon',
|
||||
VIEW_DAY_LINK: "[data-action='view-day-link']",
|
||||
CALENDAR_MONTH_WRAPPER: ".calendarwrapper",
|
||||
COURSE_SELECTOR: 'select[name="course"]',
|
||||
TODAY: '.today',
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the event type lang string.
|
||||
*
|
||||
* @param {String} eventType The event type.
|
||||
* @return {promise} The lang string promise.
|
||||
*/
|
||||
var getEventType = function(eventType) {
|
||||
var lang = 'type' + eventType;
|
||||
return Str.get_string(lang, 'core_calendar').then(function(langStr) {
|
||||
return langStr;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the event summary modal.
|
||||
*
|
||||
* @param {Number} eventId The calendar event id.
|
||||
*/
|
||||
var renderEventSummaryModal = function(eventId) {
|
||||
// Calendar repository promise.
|
||||
CalendarRepository.getEventById(eventId).then(function(getEventResponse) {
|
||||
if (!getEventResponse.event) {
|
||||
throw new Error('Error encountered while trying to fetch calendar event with ID: ' + eventId);
|
||||
}
|
||||
var eventData = getEventResponse.event;
|
||||
|
||||
return getEventType(eventData.eventtype).then(function(eventType) {
|
||||
eventData.eventtype = eventType;
|
||||
return eventData;
|
||||
});
|
||||
}).then(function(eventData) {
|
||||
// Build the modal parameters from the event data.
|
||||
var modalParams = {
|
||||
title: eventData.name,
|
||||
type: SummaryModal.TYPE,
|
||||
body: Templates.render('core_calendar/event_summary_body', eventData),
|
||||
templateContext: {
|
||||
canedit: eventData.canedit,
|
||||
candelete: eventData.candelete,
|
||||
isactionevent: eventData.isactionevent,
|
||||
url: eventData.url
|
||||
}
|
||||
};
|
||||
|
||||
// Create the modal.
|
||||
return ModalFactory.create(modalParams);
|
||||
|
||||
}).done(function(modal) {
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
// Finally, render the modal!
|
||||
modal.show();
|
||||
|
||||
}).fail(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handler for the drag and drop move event. Provides a loading indicator
|
||||
* while the request is sent to the server to update the event start date.
|
||||
@@ -226,19 +163,7 @@ define([
|
||||
CalendarViewManager.reloadCurrentMonth(root);
|
||||
});
|
||||
|
||||
eventFormModalPromise
|
||||
.then(function(modal) {
|
||||
// When something within the calendar tells us the user wants
|
||||
// to edit an event then show the event form modal.
|
||||
body.on(CalendarEvents.editEvent, function(e, eventId) {
|
||||
var calendarWrapper = root.find(CalendarSelectors.wrapper);
|
||||
modal.setEventId(eventId);
|
||||
modal.setContextId(calendarWrapper.data('contextId'));
|
||||
modal.show();
|
||||
});
|
||||
return;
|
||||
})
|
||||
.fail(Notification.exception);
|
||||
CalendarCrud.registerEditListeners(root, eventFormModalPromise);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -247,34 +172,13 @@ define([
|
||||
* @param {object} root The calendar root element
|
||||
*/
|
||||
var registerEventListeners = function(root) {
|
||||
// Bind click events to event links.
|
||||
root.on('click', SELECTORS.EVENT_ITEM, function(e) {
|
||||
e.preventDefault();
|
||||
// We've handled the event so stop it from bubbling
|
||||
// and causing the day click handler to fire.
|
||||
e.stopPropagation();
|
||||
|
||||
var target = $(e.target);
|
||||
var eventId = null;
|
||||
|
||||
var eventLink = target.closest(SELECTORS.EVENT_LINK);
|
||||
|
||||
if (eventLink.length) {
|
||||
eventId = eventLink.data('eventId');
|
||||
} else {
|
||||
eventId = target.find(SELECTORS.EVENT_LINK).data('eventId');
|
||||
}
|
||||
|
||||
renderEventSummaryModal(eventId);
|
||||
});
|
||||
|
||||
root.on('change', SELECTORS.COURSE_SELECTOR, function() {
|
||||
root.on('change', CalendarSelectors.elements.courseSelector, function() {
|
||||
var selectElement = $(this);
|
||||
var courseId = selectElement.val();
|
||||
CalendarViewManager.reloadCurrentMonth(root, courseId, null)
|
||||
.then(function() {
|
||||
// We need to get the selector again because the content has changed.
|
||||
return root.find(SELECTORS.COURSE_SELECTOR).val(courseId);
|
||||
return root.find(CalendarSelectors.elements.courseSelector).val(courseId);
|
||||
})
|
||||
.fail(Notification.exception);
|
||||
});
|
||||
|
||||
@@ -69,10 +69,10 @@ function(
|
||||
} else {
|
||||
// The root has been removed.
|
||||
// Remove all events in the namespace.
|
||||
body.on(CalendarEvents.created + namespace);
|
||||
body.on(CalendarEvents.deleted + namespace);
|
||||
body.on(CalendarEvents.updated + namespace);
|
||||
body.on(CalendarEvents.eventMoved + namespace);
|
||||
body.off(CalendarEvents.created + namespace);
|
||||
body.off(CalendarEvents.deleted + namespace);
|
||||
body.off(CalendarEvents.updated + namespace);
|
||||
body.off(CalendarEvents.eventMoved + namespace);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -82,6 +82,20 @@ function(
|
||||
|
||||
daysWithEvent.toggleClass('calendar_event_' + data.type, !data.hidden);
|
||||
});
|
||||
|
||||
var namespace = '.' + root.attr('id');
|
||||
$('body').on('change' + namespace, CalendarSelectors.elements.courseSelector, function() {
|
||||
if (root.is(':visible')) {
|
||||
var selectElement = $(this);
|
||||
var courseId = selectElement.val();
|
||||
var categoryId = null;
|
||||
|
||||
CalendarViewManager.reloadCurrentMonth(root, courseId, categoryId);
|
||||
} else {
|
||||
$('body').off('change' + namespace);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -51,7 +51,6 @@ define([
|
||||
var registerEventListeners = function(root, type) {
|
||||
var body = $('body');
|
||||
|
||||
CalendarCrud.registerEventFormModal(root);
|
||||
CalendarCrud.registerRemove(root);
|
||||
|
||||
var reloadFunction = 'reloadCurrent' + type.charAt(0).toUpperCase() + type.slice(1);
|
||||
@@ -74,6 +73,11 @@ define([
|
||||
// We need to get the selector again because the content has changed.
|
||||
return root.find(CalendarSelectors.courseSelector).val(courseId);
|
||||
})
|
||||
.then(function() {
|
||||
window.history.pushState({}, '', '?view=upcoming&course=' + courseId);
|
||||
|
||||
return;
|
||||
})
|
||||
.fail(Notification.exception);
|
||||
});
|
||||
|
||||
@@ -85,6 +89,9 @@ define([
|
||||
daysWithEvent.removeClass('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
var eventFormPromise = CalendarCrud.registerEventFormModal(root);
|
||||
CalendarCrud.registerEditListeners(root, eventFormPromise);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -198,6 +198,8 @@ function(
|
||||
|
||||
modal.setContextId(calendarWrapper.data('contextId'));
|
||||
modal.show();
|
||||
|
||||
e.stopImmediatePropagation();
|
||||
return;
|
||||
}).fail(Notification.exception);
|
||||
});
|
||||
@@ -223,8 +225,36 @@ function(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the listeners required to edit the event.
|
||||
*
|
||||
* @param {jQuery} root
|
||||
* @param {Promise} eventFormModalPromise
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function registerEditListeners(root, eventFormModalPromise) {
|
||||
eventFormModalPromise
|
||||
.then(function(modal) {
|
||||
// When something within the calendar tells us the user wants
|
||||
// to edit an event then show the event form modal.
|
||||
$('body').on(CalendarEvents.editEvent, function(e, eventId) {
|
||||
var calendarWrapper = root.find(CalendarSelectors.wrapper);
|
||||
modal.setEventId(eventId);
|
||||
modal.setContextId(calendarWrapper.data('contextId'));
|
||||
modal.show();
|
||||
|
||||
e.stopImmediatePropagation();
|
||||
});
|
||||
return;
|
||||
})
|
||||
.fail(Notification.exception);
|
||||
|
||||
return eventFormModalPromise;
|
||||
}
|
||||
|
||||
return {
|
||||
registerRemove: registerRemove,
|
||||
registerEditListeners: registerEditListeners,
|
||||
registerEventFormModal: registerEventFormModal
|
||||
};
|
||||
});
|
||||
|
||||
@@ -423,12 +423,15 @@ define([
|
||||
this.reloadBodyContent(formData);
|
||||
return;
|
||||
} else {
|
||||
// Check whether this was a new event or not.
|
||||
// The hide function unsets the form data so grab this before the hide.
|
||||
var isExisting = this.hasEventId();
|
||||
|
||||
// No problemo! Our work here is done.
|
||||
this.hide();
|
||||
|
||||
// Trigger the appropriate calendar event so that the view can
|
||||
// be updated.
|
||||
if (this.hasEventId()) {
|
||||
// Trigger the appropriate calendar event so that the view can be updated.
|
||||
if (isExisting) {
|
||||
$('body').trigger(CalendarEvents.updated, [response.event]);
|
||||
} else {
|
||||
$('body').trigger(CalendarEvents.created, [response.event]);
|
||||
|
||||
@@ -164,13 +164,15 @@ define(['jquery', 'core/ajax'], function($, Ajax) {
|
||||
*
|
||||
* @method getCalendarUpcomingData
|
||||
* @param {Number} courseid The course id.
|
||||
* @param {Number} categoryid The category id.
|
||||
* @return {promise} Resolved with the month view data.
|
||||
*/
|
||||
var getCalendarUpcomingData = function(courseid) {
|
||||
var getCalendarUpcomingData = function(courseid, categoryid) {
|
||||
var request = {
|
||||
methodname: 'core_calendar_get_calendar_upcoming_view',
|
||||
args: {
|
||||
courseid: courseid,
|
||||
categoryid: categoryid,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -46,10 +46,21 @@ define([], function() {
|
||||
create: '[data-action="new-event-button"]',
|
||||
edit: '[data-action="edit"]',
|
||||
remove: '[data-action="delete"]',
|
||||
viewEvent: '[data-action="view-event"]',
|
||||
},
|
||||
elements: {
|
||||
courseSelector: 'select[name="course"]',
|
||||
},
|
||||
today: '.today',
|
||||
day: '[data-region="day"]',
|
||||
wrapper: '.calendarwrapper',
|
||||
eventItem: '[data-type="event"]',
|
||||
links: {
|
||||
navLink: '.calendarwrapper .arrow_link',
|
||||
eventLink: "[data-region='event-item']",
|
||||
},
|
||||
containers: {
|
||||
loadingIcon: '[data-region="overlay-icon-container"]',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -24,24 +24,27 @@
|
||||
define([
|
||||
'jquery',
|
||||
'core/templates',
|
||||
'core/str',
|
||||
'core/notification',
|
||||
'core_calendar/repository',
|
||||
'core_calendar/events',
|
||||
'core_calendar/selectors',
|
||||
'core/modal_factory',
|
||||
'core/modal_events',
|
||||
'core_calendar/summary_modal',
|
||||
], function(
|
||||
$,
|
||||
Templates,
|
||||
Str,
|
||||
Notification,
|
||||
CalendarRepository,
|
||||
CalendarEvents,
|
||||
CalendarSelectors
|
||||
CalendarSelectors,
|
||||
ModalFactory,
|
||||
ModalEvents,
|
||||
SummaryModal
|
||||
) {
|
||||
|
||||
var SELECTORS = {
|
||||
CALENDAR_NAV_LINK: ".calendarwrapper .arrow_link",
|
||||
LOADING_ICON_CONTAINER: '[data-region="overlay-icon-container"]'
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners for the module.
|
||||
*
|
||||
@@ -50,7 +53,38 @@ define([
|
||||
var registerEventListeners = function(root) {
|
||||
root = $(root);
|
||||
|
||||
root.on('click', SELECTORS.CALENDAR_NAV_LINK, function(e) {
|
||||
// Bind click events to event links.
|
||||
root.on('click', CalendarSelectors.links.eventLink, function(e) {
|
||||
var target = $(e.target);
|
||||
var eventId = null;
|
||||
|
||||
var eventLink;
|
||||
if (target.is(CalendarSelectors.actions.viewEvent)) {
|
||||
eventLink = target;
|
||||
} else {
|
||||
eventLink = target.closest(CalendarSelectors.actions.viewEvent);
|
||||
}
|
||||
|
||||
if (eventLink.length) {
|
||||
eventId = eventLink.data('eventId');
|
||||
} else {
|
||||
eventId = target.find(CalendarSelectors.actions.viewEvent).data('eventId');
|
||||
}
|
||||
|
||||
if (eventId) {
|
||||
// A link was found. Show the modal.
|
||||
|
||||
e.preventDefault();
|
||||
// We've handled the event so stop it from bubbling
|
||||
// and causing the day click handler to fire.
|
||||
e.stopPropagation();
|
||||
|
||||
renderEventSummaryModal(eventId);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
root.on('click', CalendarSelectors.links.navLink, function(e) {
|
||||
var wrapper = root.find(CalendarSelectors.wrapper);
|
||||
var view = wrapper.data('view');
|
||||
var courseId = wrapper.data('courseid');
|
||||
@@ -249,7 +283,7 @@ define([
|
||||
* @method startLoading
|
||||
*/
|
||||
var startLoading = function(root) {
|
||||
var loadingIconContainer = root.find(SELECTORS.LOADING_ICON_CONTAINER);
|
||||
var loadingIconContainer = root.find(CalendarSelectors.containers.loadingIcon);
|
||||
|
||||
loadingIconContainer.removeClass('hidden');
|
||||
};
|
||||
@@ -261,7 +295,7 @@ define([
|
||||
* @method stopLoading
|
||||
*/
|
||||
var stopLoading = function(root) {
|
||||
var loadingIconContainer = root.find(SELECTORS.LOADING_ICON_CONTAINER);
|
||||
var loadingIconContainer = root.find(CalendarSelectors.containers.loadingIcon);
|
||||
|
||||
loadingIconContainer.addClass('hidden');
|
||||
};
|
||||
@@ -271,23 +305,27 @@ define([
|
||||
*
|
||||
* @param {object} root The container element.
|
||||
* @param {Number} courseId The course id.
|
||||
* @param {Number} categoryId The id of the category whose events are shown
|
||||
* @return {promise}
|
||||
*/
|
||||
var reloadCurrentUpcoming = function(root, courseId) {
|
||||
var reloadCurrentUpcoming = function(root, courseId, categoryId) {
|
||||
startLoading(root);
|
||||
|
||||
var target = root.find(CalendarSelectors.wrapper);
|
||||
|
||||
if (!courseId) {
|
||||
if (typeof courseId === 'undefined') {
|
||||
courseId = root.find(CalendarSelectors.wrapper).data('courseid');
|
||||
}
|
||||
|
||||
return CalendarRepository.getCalendarUpcomingData(courseId)
|
||||
if (typeof categoryId === 'undefined') {
|
||||
categoryId = root.find(CalendarSelectors.wrapper).data('categoryid');
|
||||
}
|
||||
|
||||
return CalendarRepository.getCalendarUpcomingData(courseId, categoryId)
|
||||
.then(function(context) {
|
||||
return Templates.render(root.attr('data-template'), context);
|
||||
})
|
||||
.then(function(html, js) {
|
||||
window.history.replaceState(null, null, '?view=upcoming&course=' + courseId);
|
||||
return Templates.replaceNode(target, html, js);
|
||||
})
|
||||
.then(function() {
|
||||
@@ -300,6 +338,102 @@ define([
|
||||
.fail(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert the given event type into one of either user, site,
|
||||
* group, category, or course.
|
||||
*
|
||||
* @param {String} eventType The calendar event type
|
||||
* @return {String}
|
||||
*/
|
||||
var normaliseEventType = function(eventType) {
|
||||
switch (eventType) {
|
||||
case 'user':
|
||||
return 'user';
|
||||
case 'site':
|
||||
return 'site';
|
||||
case 'group':
|
||||
return 'group';
|
||||
case 'category':
|
||||
return 'category';
|
||||
default:
|
||||
return 'course';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the CSS class to apply for the given event type.
|
||||
*
|
||||
* @param {String} eventType The calendar event type
|
||||
* @return {String}
|
||||
*/
|
||||
var getEventTypeClassFromType = function(eventType) {
|
||||
return 'calendar_event_' + normaliseEventType(eventType);
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the event summary modal.
|
||||
*
|
||||
* @param {Number} eventId The calendar event id.
|
||||
*/
|
||||
var renderEventSummaryModal = function(eventId) {
|
||||
var typeClass = '';
|
||||
|
||||
// Calendar repository promise.
|
||||
CalendarRepository.getEventById(eventId).then(function(getEventResponse) {
|
||||
if (!getEventResponse.event) {
|
||||
throw new Error('Error encountered while trying to fetch calendar event with ID: ' + eventId);
|
||||
}
|
||||
var eventData = getEventResponse.event;
|
||||
typeClass = getEventTypeClassFromType(eventData.eventtype);
|
||||
|
||||
return getEventType(eventData.eventtype).then(function(eventType) {
|
||||
eventData.eventtype = eventType;
|
||||
return eventData;
|
||||
});
|
||||
}).then(function(eventData) {
|
||||
// Build the modal parameters from the event data.
|
||||
var modalParams = {
|
||||
title: eventData.name,
|
||||
type: SummaryModal.TYPE,
|
||||
body: Templates.render('core_calendar/event_summary_body', eventData),
|
||||
templateContext: {
|
||||
canedit: eventData.canedit,
|
||||
candelete: eventData.candelete,
|
||||
headerclasses: typeClass,
|
||||
isactionevent: eventData.isactionevent,
|
||||
url: eventData.url
|
||||
}
|
||||
};
|
||||
|
||||
// Create the modal.
|
||||
return ModalFactory.create(modalParams);
|
||||
|
||||
}).done(function(modal) {
|
||||
// Handle hidden event.
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
// Destroy when hidden.
|
||||
modal.destroy();
|
||||
});
|
||||
|
||||
// Finally, render the modal!
|
||||
modal.show();
|
||||
|
||||
}).fail(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the event type lang string.
|
||||
*
|
||||
* @param {String} eventType The event type.
|
||||
* @return {promise} The lang string promise.
|
||||
*/
|
||||
var getEventType = function(eventType) {
|
||||
var lang = 'type' + normaliseEventType(eventType);
|
||||
return Str.get_string(lang, 'core_calendar').then(function(langStr) {
|
||||
return langStr;
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
init: function(root) {
|
||||
registerEventListeners(root);
|
||||
|
||||
+2
-2
@@ -74,7 +74,7 @@ class calendar_day_exporter extends exporter {
|
||||
],
|
||||
'defaulteventcontext' => [
|
||||
'type' => PARAM_INT,
|
||||
'default' => null,
|
||||
'default' => 0,
|
||||
],
|
||||
'filter_selector' => [
|
||||
'type' => PARAM_RAW,
|
||||
@@ -238,7 +238,7 @@ class calendar_day_exporter extends exporter {
|
||||
* @return string The html code for the course filter selector.
|
||||
*/
|
||||
protected function get_course_filter_selector(renderer_base $output) {
|
||||
$langstr = get_string('upcomingeventsfor', 'calendar');
|
||||
$langstr = get_string('dayviewfor', 'calendar');
|
||||
return $output->course_filter_selector($this->url, $langstr, $this->calendar->course->id);
|
||||
}
|
||||
|
||||
|
||||
+24
-3
@@ -74,6 +74,10 @@ class calendar_event_exporter extends event_exporter_base {
|
||||
'type' => PARAM_TEXT,
|
||||
'optional' => true
|
||||
];
|
||||
$values['draggable'] = [
|
||||
'type' => PARAM_BOOL,
|
||||
'default' => false
|
||||
];
|
||||
|
||||
return $values;
|
||||
}
|
||||
@@ -90,6 +94,10 @@ class calendar_event_exporter extends event_exporter_base {
|
||||
$values = parent::get_other_values($output);
|
||||
$event = $this->event;
|
||||
|
||||
// By default all events that can be edited are
|
||||
// draggable.
|
||||
$values['draggable'] = $values['canedit'];
|
||||
|
||||
if ($moduleproxy = $event->get_course_module()) {
|
||||
$modulename = $moduleproxy->get('modname');
|
||||
$moduleid = $moduleproxy->get('id');
|
||||
@@ -213,17 +221,30 @@ class calendar_event_exporter extends event_exporter_base {
|
||||
* @return array
|
||||
*/
|
||||
protected function get_module_timestamp_limits($event) {
|
||||
global $DB;
|
||||
|
||||
$values = [];
|
||||
$mapper = container::get_event_mapper();
|
||||
$starttime = $event->get_times()->get_start_time();
|
||||
$modname = $event->get_course_module()->get('modname');
|
||||
$modid = $event->get_course_module()->get('instance');
|
||||
$moduleinstance = $DB->get_record($modname, ['id' => $modid]);
|
||||
|
||||
list($min, $max) = component_callback(
|
||||
'mod_' . $event->get_course_module()->get('modname'),
|
||||
'mod_' . $modname,
|
||||
'core_calendar_get_valid_event_timestart_range',
|
||||
[$mapper->from_event_to_legacy_event($event)],
|
||||
[null, null]
|
||||
[$mapper->from_event_to_legacy_event($event), $moduleinstance],
|
||||
[false, false]
|
||||
);
|
||||
|
||||
// The callback will return false for either of the
|
||||
// min or max cutoffs to indicate that there are no
|
||||
// valid timestart values. In which case the event is
|
||||
// not draggable.
|
||||
if ($min === false || $max === false) {
|
||||
return ['draggable' => false];
|
||||
}
|
||||
|
||||
if ($min) {
|
||||
$values = array_merge($values, $this->get_module_timestamp_min_limit($starttime, $min));
|
||||
}
|
||||
|
||||
+11
-1
@@ -74,7 +74,7 @@ class calendar_upcoming_exporter extends exporter {
|
||||
],
|
||||
'defaulteventcontext' => [
|
||||
'type' => PARAM_INT,
|
||||
'default' => null,
|
||||
'default' => 0,
|
||||
],
|
||||
'filter_selector' => [
|
||||
'type' => PARAM_RAW,
|
||||
@@ -82,6 +82,11 @@ class calendar_upcoming_exporter extends exporter {
|
||||
'courseid' => [
|
||||
'type' => PARAM_INT,
|
||||
],
|
||||
'categoryid' => [
|
||||
'type' => PARAM_INT,
|
||||
'optional' => true,
|
||||
'default' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -127,6 +132,11 @@ class calendar_upcoming_exporter extends exporter {
|
||||
}
|
||||
$return['filter_selector'] = $this->get_course_filter_selector($output);
|
||||
$return['courseid'] = $this->calendar->courseid;
|
||||
|
||||
if ($this->calendar->categoryid) {
|
||||
$return['categoryid'] = $this->calendar->categoryid;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
-90
@@ -147,20 +147,10 @@ class day_exporter extends exporter {
|
||||
'navigation' => [
|
||||
'type' => PARAM_RAW,
|
||||
],
|
||||
'popovertitle' => [
|
||||
'type' => PARAM_RAW,
|
||||
'default' => '',
|
||||
],
|
||||
'haslastdayofevent' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'default' => false,
|
||||
],
|
||||
'filter_selector' => [
|
||||
'type' => PARAM_RAW,
|
||||
],
|
||||
'new_event_button' => [
|
||||
'type' => PARAM_RAW,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -190,8 +180,6 @@ class day_exporter extends exporter {
|
||||
'previousperiod' => $this->get_previous_day_timestamp($daytimestamp),
|
||||
'nextperiod' => $this->get_next_day_timestamp($daytimestamp),
|
||||
'navigation' => $this->get_navigation(),
|
||||
'filter_selector' => $this->get_course_filter_selector($output),
|
||||
'new_event_button' => $this->get_new_event_button(),
|
||||
'viewdaylink' => $this->url->out(false),
|
||||
];
|
||||
|
||||
@@ -277,82 +265,4 @@ class day_exporter extends exporter {
|
||||
'time' => $this->calendar->time,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the course filter selector.
|
||||
*
|
||||
* This is a temporary solution, this code will be removed by MDL-60096.
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return string The html code for the course filter selector.
|
||||
*/
|
||||
protected function get_course_filter_selector(renderer_base $output) {
|
||||
global $CFG;
|
||||
// TODO remove this code on MDL-60096.
|
||||
if (!isloggedin() or isguestuser()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (has_capability('moodle/calendar:manageentries', \context_system::instance()) && !empty($CFG->calendar_adminseesall)) {
|
||||
$courses = get_courses('all', 'c.shortname', 'c.id, c.shortname');
|
||||
} else {
|
||||
$courses = enrol_get_my_courses();
|
||||
}
|
||||
|
||||
unset($courses[SITEID]);
|
||||
|
||||
$courseoptions = array();
|
||||
$courseoptions[SITEID] = get_string('fulllistofcourses');
|
||||
foreach ($courses as $course) {
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
$courseoptions[$course->id] = format_string($course->shortname, true, array('context' => $coursecontext));
|
||||
}
|
||||
|
||||
if ($this->calendar->courseid !== SITEID) {
|
||||
$selected = $this->calendar->courseid;
|
||||
} else {
|
||||
$selected = '';
|
||||
}
|
||||
|
||||
$courseurl = new moodle_url($this->url);
|
||||
$courseurl->remove_params('course');
|
||||
$select = new \single_select($courseurl, 'courseselect', $courseoptions, $selected, null);
|
||||
$select->class = 'm-r-1';
|
||||
$label = get_string('dayviewfor', 'calendar');
|
||||
if ($label !== null) {
|
||||
$select->set_label($label);
|
||||
} else {
|
||||
$select->set_label(get_string('listofcourses'), array('class' => 'accesshide'));
|
||||
}
|
||||
|
||||
return $output->render($select);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the course filter selector.
|
||||
*
|
||||
* This is a temporary solution, this code will be removed by MDL-60096.
|
||||
*
|
||||
* @return string The html code for the course filter selector.
|
||||
*/
|
||||
protected function get_new_event_button() {
|
||||
// TODO remove this code on MDL-60096.
|
||||
$output = \html_writer::start_tag('div', array('class' => 'buttons'));
|
||||
$output .= \html_writer::start_tag('form',
|
||||
array('action' => CALENDAR_URL . 'event.php', 'method' => 'get'));
|
||||
$output .= \html_writer::start_tag('div');
|
||||
$output .= \html_writer::empty_tag('input',
|
||||
array('type' => 'hidden', 'name' => 'action', 'value' => 'new'));
|
||||
$output .= \html_writer::empty_tag('input',
|
||||
array('type' => 'hidden', 'name' => 'course', 'value' => $this->calendar->courseid));
|
||||
$output .= \html_writer::empty_tag('input',
|
||||
array('type' => 'hidden', 'name' => 'time', 'value' => $this->calendar->time));
|
||||
$attributes = array('type' => 'submit', 'value' => get_string('newevent', 'calendar'),
|
||||
'class' => 'btn btn-secondary');
|
||||
$output .= \html_writer::empty_tag('input', $attributes);
|
||||
$output .= \html_writer::end_tag('div');
|
||||
$output .= \html_writer::end_tag('form');
|
||||
$output .= \html_writer::end_tag('div');
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-5
@@ -72,8 +72,8 @@ class footer_options_exporter extends exporter {
|
||||
* @return \single_button The export calendar button html.
|
||||
*/
|
||||
protected function get_export_calendar_button() {
|
||||
$exportcalendarurl = new moodle_url('/calendar/export.php', ['course' => $this->calendar->course->id]);
|
||||
return new \single_button($exportcalendarurl, get_string('exportcalendar', 'calendar'));
|
||||
$exportcalendarurl = new moodle_url('/calendar/export.php', $this->get_link_params());
|
||||
return new \single_button($exportcalendarurl, get_string('exportcalendar', 'calendar'), 'get');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,13 +83,30 @@ class footer_options_exporter extends exporter {
|
||||
*/
|
||||
protected function get_manage_subscriptions_button() {
|
||||
if (calendar_user_can_add_event($this->calendar->course)) {
|
||||
$managesubscriptionurl = new moodle_url('/calendar/managesubscriptions.php',
|
||||
['course' => $this->calendar->course->id]);
|
||||
$managesubscriptionurl = new moodle_url('/calendar/managesubscriptions.php', $this->get_link_params());
|
||||
return new \single_button($managesubscriptionurl,
|
||||
get_string('managesubscriptions', 'calendar'));
|
||||
get_string('managesubscriptions', 'calendar'), 'get');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of URL parameters for calendar links.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_link_params() {
|
||||
$params = [];
|
||||
if (SITEID !== $this->calendar->course->id) {
|
||||
$params['course'] = $this->calendar->course->id;
|
||||
} else if (null !== $this->calendar->categoryid && $this->calendar->categoryid > 0) {
|
||||
$params['category'] = $this->calendar->categoryid;
|
||||
} else {
|
||||
$params['course'] = SITEID;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the additional values to inject while exporting.
|
||||
*
|
||||
|
||||
+1
-1
@@ -171,7 +171,7 @@ class month_exporter extends exporter {
|
||||
],
|
||||
'defaulteventcontext' => [
|
||||
'type' => PARAM_INT,
|
||||
'default' => null,
|
||||
'default' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
-18
@@ -67,28 +67,10 @@ class week_day_exporter extends day_exporter {
|
||||
protected static function define_other_properties() {
|
||||
$return = parent::define_other_properties();
|
||||
$return = array_merge($return, [
|
||||
'timestamp' => [
|
||||
'type' => PARAM_INT,
|
||||
],
|
||||
'neweventtimestamp' => [
|
||||
'type' => PARAM_INT,
|
||||
],
|
||||
'viewdaylink' => [
|
||||
'type' => PARAM_URL,
|
||||
'optional' => true,
|
||||
],
|
||||
'calendareventtypes' => [
|
||||
'type' => PARAM_RAW,
|
||||
'multiple' => true,
|
||||
],
|
||||
'popovertitle' => [
|
||||
'type' => PARAM_RAW,
|
||||
'default' => '',
|
||||
],
|
||||
'haslastdayofevent' => [
|
||||
'type' => PARAM_BOOL,
|
||||
'default' => false,
|
||||
],
|
||||
]);
|
||||
|
||||
return $return;
|
||||
|
||||
@@ -231,9 +231,12 @@ class api {
|
||||
event_interface $event,
|
||||
\DateTimeInterface $startdate
|
||||
) {
|
||||
global $DB;
|
||||
|
||||
$mapper = container::get_event_mapper();
|
||||
$legacyevent = $mapper->from_event_to_legacy_event($event);
|
||||
$hascoursemodule = !empty($event->get_course_module());
|
||||
$moduleinstance = null;
|
||||
$starttime = $event->get_times()->get_start_time()->setDate(
|
||||
$startdate->format('Y'),
|
||||
$startdate->format('n'),
|
||||
@@ -241,15 +244,38 @@ class api {
|
||||
);
|
||||
|
||||
if ($hascoursemodule) {
|
||||
$legacyevent->timestart = $starttime->getTimestamp();
|
||||
// If this event is from an activity then we need to call
|
||||
// the activity callback to let it validate that the changes
|
||||
// to the event are correct.
|
||||
component_callback(
|
||||
'mod_' . $event->get_course_module()->get('modname'),
|
||||
'core_calendar_validate_event_timestart',
|
||||
[$legacyevent]
|
||||
$moduleinstance = $DB->get_record(
|
||||
$event->get_course_module()->get('modname'),
|
||||
['id' => $event->get_course_module()->get('instance')],
|
||||
'*',
|
||||
MUST_EXIST
|
||||
);
|
||||
$legacyevent->timestart = $starttime->getTimestamp();
|
||||
|
||||
// If there is a timestart range callback implemented then we can
|
||||
// use the values returned from the valid timestart range to apply
|
||||
// some default validation on the event's timestart value to ensure
|
||||
// that it falls within the specified range.
|
||||
list($min, $max) = component_callback(
|
||||
'mod_' . $event->get_course_module()->get('modname'),
|
||||
'core_calendar_get_valid_event_timestart_range',
|
||||
[$legacyevent, $moduleinstance],
|
||||
[false, false]
|
||||
);
|
||||
|
||||
// If the callback returns false for either value it means that
|
||||
// there is no valid time start range.
|
||||
if ($min === false || $max === false) {
|
||||
throw new \moodle_exception('The start day of this event can not be modified');
|
||||
}
|
||||
|
||||
if ($min && $legacyevent->timestart < $min[0]) {
|
||||
throw new \moodle_exception($min[1]);
|
||||
}
|
||||
|
||||
if ($max && $legacyevent->timestart > $max[0]) {
|
||||
throw new \moodle_exception($max[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// This function does our capability checks.
|
||||
@@ -262,7 +288,7 @@ class api {
|
||||
// We don't want to call the event update callback if the user isn't allowed
|
||||
// to modify course modules because depending on the callback it can make
|
||||
// some changes that would be considered security issues, such as updating the
|
||||
// due date for and assignment.
|
||||
// due date for an assignment.
|
||||
if ($hascoursemodule && calendar_edit_event_allowed($legacyevent, true)) {
|
||||
// If this event is from an activity then we need to call
|
||||
// the activity callback to let it know that the event it
|
||||
@@ -270,7 +296,7 @@ class api {
|
||||
component_callback(
|
||||
'mod_' . $event->get_course_module()->get('modname'),
|
||||
'core_calendar_event_timestart_updated',
|
||||
[$legacyevent]
|
||||
[$legacyevent, $moduleinstance]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
*/
|
||||
namespace core_calendar\local\event\forms;
|
||||
|
||||
use context_system;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot.'/lib/formslib.php');
|
||||
@@ -35,6 +37,8 @@ require_once($CFG->dirroot.'/lib/formslib.php');
|
||||
*/
|
||||
class create extends \moodleform {
|
||||
|
||||
use eventtype;
|
||||
|
||||
/**
|
||||
* Build the editor options using the given context.
|
||||
*
|
||||
@@ -48,7 +52,8 @@ class create extends \moodleform {
|
||||
'context' => $context,
|
||||
'maxfiles' => EDITOR_UNLIMITED_FILES,
|
||||
'maxbytes' => $CFG->maxbytes,
|
||||
'noclean' => true
|
||||
'noclean' => true,
|
||||
'autosave' => false
|
||||
];
|
||||
}
|
||||
|
||||
@@ -180,101 +185,6 @@ class create extends \moodleform {
|
||||
$mform->setDefault('visible', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the appropriate elements for the available event types.
|
||||
*
|
||||
* If the only event type available is 'user' then we add a hidden
|
||||
* element because there is nothing for the user to choose.
|
||||
*
|
||||
* If more than one type is available then we add the elements as
|
||||
* follows:
|
||||
* - Always add the event type selector
|
||||
* - Elements per type:
|
||||
* - course: add an additional select element with each
|
||||
* course as an option.
|
||||
* - group: add a select element for the course (different
|
||||
* from the above course select) and a select
|
||||
* element for the group.
|
||||
*
|
||||
* @param MoodleQuickForm $mform
|
||||
* @param array $eventtypes The available event types for the user
|
||||
*/
|
||||
protected function add_event_type_elements($mform, $eventtypes) {
|
||||
$options = [];
|
||||
|
||||
if (isset($eventtypes['user'])) {
|
||||
$options['user'] = get_string('user');
|
||||
}
|
||||
if (isset($eventtypes['group'])) {
|
||||
$options['group'] = get_string('group');
|
||||
}
|
||||
if (isset($eventtypes['course'])) {
|
||||
$options['course'] = get_string('course');
|
||||
}
|
||||
if (isset($eventtypes['category'])) {
|
||||
$options['category'] = get_string('category');
|
||||
}
|
||||
if (isset($eventtypes['site'])) {
|
||||
$options['site'] = get_string('site');
|
||||
}
|
||||
|
||||
// If we only have one event type and it's 'user' event then don't bother
|
||||
// rendering the select boxes because there is no choice for the user to
|
||||
// make.
|
||||
if (count(array_keys($eventtypes)) == 1 && isset($eventtypes['user'])) {
|
||||
$mform->addElement('hidden', 'eventtype');
|
||||
$mform->setType('eventtype', PARAM_TEXT);
|
||||
$mform->setDefault('eventtype', 'user');
|
||||
|
||||
// Render a static element to tell the user what type of event will
|
||||
// be created.
|
||||
$mform->addElement('static', 'staticeventtype', get_string('eventkind', 'calendar'), $options['user']);
|
||||
return;
|
||||
} else {
|
||||
$mform->addElement('select', 'eventtype', get_string('eventkind', 'calendar'), $options);
|
||||
}
|
||||
|
||||
if (isset($eventtypes['category'])) {
|
||||
$categoryoptions = [];
|
||||
foreach ($eventtypes['category'] as $id => $category) {
|
||||
$categoryoptions[$id] = $category;
|
||||
}
|
||||
|
||||
$mform->addElement('select', 'categoryid', get_string('category'), $categoryoptions);
|
||||
$mform->hideIf('categoryid', 'eventtype', 'noteq', 'category');
|
||||
}
|
||||
|
||||
if (isset($eventtypes['course'])) {
|
||||
$mform->addElement('course', 'courseid', get_string('course'), ['limittoenrolled' => true]);
|
||||
$mform->hideIf('courseid', 'eventtype', 'noteq', 'course');
|
||||
}
|
||||
|
||||
if (isset($eventtypes['group'])) {
|
||||
$options = ['limittoenrolled' => true];
|
||||
// Exclude courses without group.
|
||||
if (isset($eventtypes['course']) && isset($eventtypes['groupcourses'])) {
|
||||
$options['exclude'] = array_diff(array_keys($eventtypes['course']),
|
||||
array_keys($eventtypes['groupcourses']));
|
||||
}
|
||||
|
||||
$mform->addElement('course', 'groupcourseid', get_string('course'), $options);
|
||||
$mform->hideIf('groupcourseid', 'eventtype', 'noteq', 'group');
|
||||
|
||||
$groupoptions = [];
|
||||
foreach ($eventtypes['group'] as $group) {
|
||||
// We are formatting it this way in order to provide the javascript both
|
||||
// the course and group ids so that it can enhance the form for the user.
|
||||
$index = "{$group->courseid}-{$group->id}";
|
||||
$groupoptions[$index] = format_string($group->name, true,
|
||||
['context' => \context_course::instance($group->courseid)]);
|
||||
}
|
||||
|
||||
$mform->addElement('select', 'groupid', get_string('group'), $groupoptions);
|
||||
$mform->hideIf('groupid', 'eventtype', 'noteq', 'group');
|
||||
// We handle the group select hide/show actions on the event_form module.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the various elements to express the duration options available
|
||||
* for an event.
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* The trait for adding eventtype fields to a form.
|
||||
*
|
||||
* @package core_calendar
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_calendar\local\event\forms;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* The trait for adding eventtype fields to a form.
|
||||
*
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
trait eventtype {
|
||||
|
||||
/**
|
||||
* Add the appropriate elements for the available event types.
|
||||
*
|
||||
* If the only event type available is 'user' then we add a hidden
|
||||
* element because there is nothing for the user to choose.
|
||||
*
|
||||
* If more than one type is available then we add the elements as
|
||||
* follows:
|
||||
* - Always add the event type selector
|
||||
* - Elements per type:
|
||||
* - course: add an additional select element with each
|
||||
* course as an option.
|
||||
* - group: add a select element for the course (different
|
||||
* from the above course select) and a select
|
||||
* element for the group.
|
||||
*
|
||||
* @param MoodleQuickForm $mform
|
||||
* @param array $eventtypes The available event types for the user
|
||||
*/
|
||||
protected function add_event_type_elements($mform, $eventtypes) {
|
||||
$options = [];
|
||||
|
||||
if (isset($eventtypes['user'])) {
|
||||
$options['user'] = get_string('user');
|
||||
}
|
||||
if (isset($eventtypes['group'])) {
|
||||
$options['group'] = get_string('group');
|
||||
}
|
||||
if (isset($eventtypes['course'])) {
|
||||
$options['course'] = get_string('course');
|
||||
}
|
||||
if (isset($eventtypes['category'])) {
|
||||
$options['category'] = get_string('category');
|
||||
}
|
||||
if (isset($eventtypes['site'])) {
|
||||
$options['site'] = get_string('site');
|
||||
}
|
||||
|
||||
// If we only have one event type and it's 'user' event then don't bother
|
||||
// rendering the select boxes because there is no choice for the user to
|
||||
// make.
|
||||
if (count(array_keys($eventtypes)) == 1 && isset($eventtypes['user'])) {
|
||||
$mform->addElement('hidden', 'eventtype');
|
||||
$mform->setType('eventtype', PARAM_TEXT);
|
||||
$mform->setDefault('eventtype', 'user');
|
||||
|
||||
// Render a static element to tell the user what type of event will
|
||||
// be created.
|
||||
$mform->addElement('static', 'staticeventtype', get_string('eventkind', 'calendar'), $options['user']);
|
||||
return;
|
||||
} else {
|
||||
$mform->addElement('select', 'eventtype', get_string('eventkind', 'calendar'), $options);
|
||||
}
|
||||
|
||||
if (isset($eventtypes['category'])) {
|
||||
$categoryoptions = [];
|
||||
foreach ($eventtypes['category'] as $id => $category) {
|
||||
$categoryoptions[$id] = $category;
|
||||
}
|
||||
|
||||
$mform->addElement('select', 'categoryid', get_string('category'), $categoryoptions);
|
||||
$mform->hideIf('categoryid', 'eventtype', 'noteq', 'category');
|
||||
}
|
||||
|
||||
if (isset($eventtypes['course'])) {
|
||||
$limit = !has_capability('moodle/calendar:manageentries', \context_system::instance());
|
||||
$mform->addElement('course', 'courseid', get_string('course'), ['limittoenrolled' => $limit]);
|
||||
$mform->hideIf('courseid', 'eventtype', 'noteq', 'course');
|
||||
}
|
||||
|
||||
if (isset($eventtypes['group'])) {
|
||||
$options = ['limittoenrolled' => true];
|
||||
// Exclude courses without group.
|
||||
if (isset($eventtypes['course']) && isset($eventtypes['groupcourses'])) {
|
||||
$options['exclude'] = array_diff(array_keys($eventtypes['course']),
|
||||
array_keys($eventtypes['groupcourses']));
|
||||
}
|
||||
|
||||
$mform->addElement('course', 'groupcourseid', get_string('course'), $options);
|
||||
$mform->hideIf('groupcourseid', 'eventtype', 'noteq', 'group');
|
||||
|
||||
$groupoptions = [];
|
||||
foreach ($eventtypes['group'] as $group) {
|
||||
// We are formatting it this way in order to provide the javascript both
|
||||
// the course and group ids so that it can enhance the form for the user.
|
||||
$index = "{$group->courseid}-{$group->id}";
|
||||
$groupoptions[$index] = format_string($group->name, true,
|
||||
['context' => \context_course::instance($group->courseid)]);
|
||||
}
|
||||
|
||||
$mform->addElement('select', 'groupid', get_string('group'), $groupoptions);
|
||||
$mform->hideIf('groupid', 'eventtype', 'noteq', 'group');
|
||||
// We handle the group select hide/show actions on the event_form module.
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
-25
@@ -21,26 +21,30 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @package calendar
|
||||
*/
|
||||
namespace core_calendar\local\event\forms;
|
||||
|
||||
if (!defined('MOODLE_INTERNAL')) {
|
||||
die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
|
||||
}
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->libdir.'/formslib.php');
|
||||
require_once($CFG->libdir . '/formslib.php');
|
||||
|
||||
/**
|
||||
* Form for adding a subscription to a Moodle course calendar.
|
||||
* @copyright 2012 Jonathan Harker
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class calendar_addsubscription_form extends moodleform {
|
||||
class managesubscriptions extends \moodleform {
|
||||
|
||||
use eventtype;
|
||||
|
||||
/**
|
||||
* Defines the form used to add calendar subscriptions.
|
||||
*/
|
||||
public function definition() {
|
||||
$mform = $this->_form;
|
||||
$courseid = optional_param('course', 0, PARAM_INT);
|
||||
$eventtypes = calendar_get_all_allowed_types();
|
||||
if (empty($eventtypes)) {
|
||||
print_error('nopermissiontoupdatecalendar');
|
||||
}
|
||||
|
||||
$mform->addElement('header', 'addsubscriptionform', get_string('importcalendarheading', 'calendar'));
|
||||
|
||||
@@ -77,24 +81,10 @@ class calendar_addsubscription_form extends moodleform {
|
||||
$mform->disabledIf('url', 'importfrom', 'eq', CALENDAR_IMPORT_FROM_FILE);
|
||||
$mform->disabledIf('importfile', 'importfrom', 'eq', CALENDAR_IMPORT_FROM_URL);
|
||||
|
||||
// Add the select elements for the available event types.
|
||||
$this->add_event_type_elements($mform, $eventtypes);
|
||||
|
||||
// Eventtype: 0 = user, 1 = global, anything else = course ID.
|
||||
list($choices, $groups) = calendar_get_eventtype_choices($courseid);
|
||||
$mform->addElement('select', 'eventtype', get_string('eventkind', 'calendar'), $choices);
|
||||
$mform->addRule('eventtype', get_string('required'), 'required');
|
||||
$mform->setType('eventtype', PARAM_ALPHA);
|
||||
|
||||
if (!empty($groups) and is_array($groups)) {
|
||||
$groupoptions = array();
|
||||
foreach ($groups as $group) {
|
||||
$groupoptions[$group->id] = $group->name;
|
||||
}
|
||||
$mform->addElement('select', 'groupid', get_string('typegroup', 'calendar'), $groupoptions);
|
||||
$mform->setType('groupid', PARAM_INT);
|
||||
$mform->disabledIf('groupid', 'eventtype', 'noteq', 'group');
|
||||
}
|
||||
|
||||
$mform->addElement('hidden', 'course');
|
||||
$mform->setType('course', PARAM_INT);
|
||||
$mform->addElement('submit', 'add', get_string('add'));
|
||||
}
|
||||
|
||||
@@ -110,6 +100,14 @@ class calendar_addsubscription_form extends moodleform {
|
||||
|
||||
$errors = parent::validation($data, $files);
|
||||
|
||||
$coursekey = isset($data['groupcourseid']) ? 'groupcourseid' : 'courseid';
|
||||
$eventtypes = calendar_get_all_allowed_types();
|
||||
$eventtype = isset($data['eventtype']) ? $data['eventtype'] : null;
|
||||
|
||||
if (empty($eventtype) || !isset($eventtypes[$eventtype])) {
|
||||
$errors['eventtype'] = get_string('invalideventtype', 'calendar');
|
||||
}
|
||||
|
||||
if ($data['importfrom'] == CALENDAR_IMPORT_FROM_FILE) {
|
||||
if (empty($data['importfile'])) {
|
||||
$errors['importfile'] = get_string('errorrequiredurlorfile', 'calendar');
|
||||
@@ -117,7 +115,7 @@ class calendar_addsubscription_form extends moodleform {
|
||||
// Make sure the file area is not empty and contains only one file.
|
||||
$draftitemid = $data['importfile'];
|
||||
$fs = get_file_storage();
|
||||
$usercontext = context_user::instance($USER->id);
|
||||
$usercontext = \context_user::instance($USER->id);
|
||||
$files = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id DESC', false);
|
||||
if (count($files) !== 1) {
|
||||
$errors['importfile'] = get_string('errorrequiredurlorfile', 'calendar');
|
||||
@@ -140,7 +138,7 @@ class calendar_addsubscription_form extends moodleform {
|
||||
public function definition_after_data() {
|
||||
$mform =& $this->_form;
|
||||
|
||||
$mform->applyFilter('url', 'calendar_addsubscription_form::strip_webcal');
|
||||
$mform->applyFilter('url', static::class . '::strip_webcal');
|
||||
$mform->applyFilter('url', 'trim');
|
||||
}
|
||||
|
||||
@@ -45,13 +45,15 @@ class update extends create {
|
||||
$mform->addElement('hidden', 'repeatid');
|
||||
$mform->setType('repeatid', PARAM_INT);
|
||||
|
||||
$group = [];
|
||||
$group[] = $mform->createElement('radio', 'repeateditall', null, get_string('repeateditall', 'calendar',
|
||||
$event->eventrepeats), 1);
|
||||
$group[] = $mform->createElement('radio', 'repeateditall', null, get_string('repeateditthis', 'calendar'), 0);
|
||||
$mform->addGroup($group, 'repeatgroup', get_string('repeatedevents', 'calendar'), '<br />', false);
|
||||
if (!empty($event->repeatid)) {
|
||||
$group = [];
|
||||
$group[] = $mform->createElement('radio', 'repeateditall', null, get_string('repeateditall', 'calendar',
|
||||
$event->eventrepeats), 1);
|
||||
$group[] = $mform->createElement('radio', 'repeateditall', null, get_string('repeateditthis', 'calendar'), 0);
|
||||
$mform->addGroup($group, 'repeatgroup', get_string('repeatedevents', 'calendar'), '<br />', false);
|
||||
|
||||
$mform->setDefault('repeateditall', 1);
|
||||
$mform->setAdvanced('repeatgroup');
|
||||
$mform->setDefault('repeateditall', 1);
|
||||
$mform->setAdvanced('repeatgroup');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
-75
@@ -203,6 +203,8 @@ class core_calendar_external extends external_api {
|
||||
$courses = $params['events']['courseids'];
|
||||
$funcparam['courses'] = $courses;
|
||||
}
|
||||
// Now get categories we can get events from.
|
||||
$categories = \coursecat::get_all();
|
||||
|
||||
// Let us findout groups that we can return events from.
|
||||
if (!$hassystemcap) {
|
||||
@@ -273,7 +275,8 @@ class core_calendar_external extends external_api {
|
||||
} else {
|
||||
// Can the user actually see this event?
|
||||
$eventobj = calendar_event::load($eventobj);
|
||||
if (($eventobj->courseid == $SITE->id) ||
|
||||
if ((($eventobj->courseid == $SITE->id) && (empty($eventobj->categoryid))) ||
|
||||
(!empty($eventobj->categoryid) && in_array($eventobj->categoryid, $categories)) ||
|
||||
(!empty($eventobj->groupid) && in_array($eventobj->groupid, $groups)) ||
|
||||
(!empty($eventobj->courseid) && in_array($eventobj->courseid, $courses)) ||
|
||||
($USER->id == $eventobj->userid) ||
|
||||
@@ -716,14 +719,21 @@ class core_calendar_external extends external_api {
|
||||
global $CFG, $PAGE, $USER;
|
||||
require_once($CFG->dirroot."/calendar/lib.php");
|
||||
|
||||
$params = ['eventid' => $eventid];
|
||||
self::validate_parameters(self::get_calendar_event_by_id_parameters(), $params);
|
||||
$params = self::validate_parameters(self::get_calendar_event_by_id_parameters(), ['eventid' => $eventid]);
|
||||
$context = \context_user::instance($USER->id);
|
||||
|
||||
self::validate_context($context);
|
||||
$warnings = array();
|
||||
|
||||
$legacyevent = calendar_event::load($eventid);
|
||||
// Must check we can see this event.
|
||||
if (!calendar_view_event_allowed($legacyevent)) {
|
||||
// We can't return a warning in this case because the event is not optional.
|
||||
// We don't know the context for the event and it's not worth loading it.
|
||||
$syscontext = context_system::instance();
|
||||
throw new \required_capability_exception($syscontext, 'moodle/course:view', 'nopermission', '');
|
||||
}
|
||||
|
||||
$legacyevent->count_repeats();
|
||||
|
||||
$eventmapper = event_container::get_event_mapper();
|
||||
@@ -899,41 +909,15 @@ class core_calendar_external extends external_api {
|
||||
'includenavigation' => $includenavigation,
|
||||
]);
|
||||
|
||||
// TODO: Copy what we do in calendar/view.php.
|
||||
$context = \context_user::instance($USER->id);
|
||||
self::validate_context($context);
|
||||
$PAGE->set_url('/calendar/');
|
||||
|
||||
if ($courseid != SITEID && !empty($courseid)) {
|
||||
// Course ID must be valid and existing.
|
||||
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
|
||||
$courses = [$course->id => $course];
|
||||
|
||||
$coursecat = \coursecat::get($course->category);
|
||||
$category = $coursecat->get_db_record();
|
||||
} else {
|
||||
$course = get_site();
|
||||
$courses = calendar_get_default_courses();
|
||||
$category = null;
|
||||
|
||||
if ($categoryid) {
|
||||
self::validate_context(context_coursecat::instance($categoryid));
|
||||
$ids = [$categoryid];
|
||||
$category = \coursecat::get($categoryid);
|
||||
$ids += $category->get_parents();
|
||||
$categories = \coursecat::get_many($ids);
|
||||
$courses = array_filter($courses, function($course) use ($categories) {
|
||||
return array_search($course->category, $categories) !== false;
|
||||
});
|
||||
$category = $category->get_db_record();
|
||||
}
|
||||
}
|
||||
|
||||
$type = \core_calendar\type_factory::get_calendar_instance();
|
||||
|
||||
$time = $type->convert_to_timestamp($year, $month, 1);
|
||||
$calendar = new calendar_information(0, 0, 0, $time);
|
||||
$calendar->set_sources($course, $courses, $category);
|
||||
$time = $type->convert_to_timestamp($params['year'], $params['month'], 1);
|
||||
$calendar = \calendar_information::create($time, $params['courseid'], $params['categoryid']);
|
||||
self::validate_context($calendar->context);
|
||||
|
||||
list($data, $template) = calendar_get_view($calendar, 'month', $params['includenavigation']);
|
||||
|
||||
@@ -994,42 +978,16 @@ class core_calendar_external extends external_api {
|
||||
'categoryid' => $categoryid,
|
||||
]);
|
||||
|
||||
if ($courseid != SITEID && !empty($courseid)) {
|
||||
// Course ID must be valid and existing.
|
||||
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
|
||||
$courses = [$course->id => $course];
|
||||
|
||||
$coursecat = \coursecat::get($course->category);
|
||||
$category = $coursecat->get_db_record();
|
||||
} else {
|
||||
$course = get_site();
|
||||
$courses = calendar_get_default_courses();
|
||||
$category = null;
|
||||
|
||||
if ($categoryid) {
|
||||
self::validate_context(context_coursecat::instance($categoryid));
|
||||
$ids = [$categoryid];
|
||||
$category = \coursecat::get($categoryid);
|
||||
$ids += $category->get_parents();
|
||||
$categories = \coursecat::get_many($ids);
|
||||
$courses = array_filter($courses, function($course) use ($categories) {
|
||||
return array_search($course->category, $categories) !== false;
|
||||
});
|
||||
$category = $category->get_db_record();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Copy what we do in calendar/view.php.
|
||||
$context = \context_user::instance($USER->id);
|
||||
self::validate_context($context);
|
||||
|
||||
$type = \core_calendar\type_factory::get_calendar_instance();
|
||||
|
||||
$time = $type->convert_to_timestamp($year, $month, $day);
|
||||
$calendar = new calendar_information(0, 0, 0, $time);
|
||||
$calendar->set_sources($course, $courses, $category);
|
||||
$time = $type->convert_to_timestamp($params['year'], $params['month'], $params['day']);
|
||||
$calendar = \calendar_information::create($time, $params['courseid'], $params['categoryid']);
|
||||
self::validate_context($calendar->context);
|
||||
|
||||
list($data, $template) = calendar_get_view($calendar, 'day', $params['includenavigation']);
|
||||
list($data, $template) = calendar_get_view($calendar, 'day');
|
||||
|
||||
return $data;
|
||||
}
|
||||
@@ -1145,31 +1103,25 @@ class core_calendar_external extends external_api {
|
||||
* Get data for the monthly calendar view.
|
||||
*
|
||||
* @param int $courseid The course to be included
|
||||
* @param int $categoryid The category to be included
|
||||
* @return array
|
||||
*/
|
||||
public static function get_calendar_upcoming_view($courseid) {
|
||||
public static function get_calendar_upcoming_view($courseid, $categoryid) {
|
||||
global $CFG, $DB, $USER, $PAGE;
|
||||
require_once($CFG->dirroot."/calendar/lib.php");
|
||||
|
||||
// Parameter validation.
|
||||
self::validate_parameters(self::get_calendar_upcoming_view_parameters(), [
|
||||
$params = self::validate_parameters(self::get_calendar_upcoming_view_parameters(), [
|
||||
'courseid' => $courseid,
|
||||
'categoryid' => $categoryid,
|
||||
]);
|
||||
|
||||
if ($courseid != SITEID && !empty($courseid)) {
|
||||
// Course ID must be valid and existing.
|
||||
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
|
||||
$courses = [$course->id => $course];
|
||||
} else {
|
||||
$course = get_site();
|
||||
$courses = calendar_get_default_courses();
|
||||
}
|
||||
|
||||
$context = \context_user::instance($USER->id);
|
||||
self::validate_context($context);
|
||||
$PAGE->set_url('/calendar/');
|
||||
|
||||
$calendar = new calendar_information(0, 0, 0, time());
|
||||
$calendar->set_sources($course, $courses);
|
||||
$calendar = \calendar_information::create(time(), $params['courseid'], $params['categoryid']);
|
||||
self::validate_context($calendar->context);
|
||||
|
||||
list($data, $template) = calendar_get_view($calendar, 'upcoming');
|
||||
|
||||
@@ -1185,6 +1137,7 @@ class core_calendar_external extends external_api {
|
||||
return new external_function_parameters(
|
||||
[
|
||||
'courseid' => new external_value(PARAM_INT, 'Course being viewed', VALUE_DEFAULT, SITEID, NULL_ALLOWED),
|
||||
'categoryid' => new external_value(PARAM_INT, 'Category being viewed', VALUE_DEFAULT, null, NULL_ALLOWED),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
+314
-47
@@ -998,6 +998,9 @@ class calendar_information {
|
||||
/** @var array An array of users */
|
||||
public $users = array();
|
||||
|
||||
/** @var context The anticipated context that the calendar is viewed in */
|
||||
public $context = null;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
@@ -1032,6 +1035,55 @@ class calendar_information {
|
||||
$this->set_time($time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and set up a instance.
|
||||
*
|
||||
* @param int $time the unixtimestamp representing the date we want to view.
|
||||
* @param int $courseid The ID of the course the user wishes to view.
|
||||
* @param int $categoryid The ID of the category the user wishes to view
|
||||
* If a courseid is specified, this value is ignored.
|
||||
* @return calendar_information
|
||||
*/
|
||||
public static function create($time, int $courseid, int $categoryid = null) : calendar_information {
|
||||
$calendar = new static(0, 0, 0, $time);
|
||||
if ($courseid != SITEID && !empty($courseid)) {
|
||||
// Course ID must be valid and existing.
|
||||
$course = get_course($courseid);
|
||||
$calendar->context = context_course::instance($course->id);
|
||||
|
||||
if (!$course->visible) {
|
||||
require_capability('moodle/course:viewhiddencourses', $calendar->context);
|
||||
}
|
||||
|
||||
$courses = [$course->id => $course];
|
||||
$category = (\coursecat::get($course->category))->get_db_record();
|
||||
} else if (!empty($categoryid)) {
|
||||
$course = get_site();
|
||||
$courses = calendar_get_default_courses();
|
||||
|
||||
// Filter available courses to those within this category or it's children.
|
||||
$ids = [$categoryid];
|
||||
$category = \coursecat::get($categoryid);
|
||||
$ids = array_merge($ids, array_keys($category->get_children()));
|
||||
$courses = array_filter($courses, function($course) use ($ids) {
|
||||
return array_search($course->category, $ids) !== false;
|
||||
});
|
||||
$category = $category->get_db_record();
|
||||
|
||||
$calendar->context = context_coursecat::instance($categoryid);
|
||||
} else {
|
||||
$course = get_site();
|
||||
$courses = calendar_get_default_courses();
|
||||
$category = null;
|
||||
|
||||
$calendar->context = context_system::instance();
|
||||
}
|
||||
|
||||
$calendar->set_sources($course, $courses, $category);
|
||||
|
||||
return $calendar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time period of this instance.
|
||||
*
|
||||
@@ -1073,6 +1125,8 @@ class calendar_information {
|
||||
* @param stdClass $category The current category to show.
|
||||
*/
|
||||
public function set_sources(stdClass $course, array $courses, stdClass $category = null) {
|
||||
global $USER;
|
||||
|
||||
// A cousre must always be specified.
|
||||
$this->course = $course;
|
||||
$this->courseid = $course->id;
|
||||
@@ -1086,33 +1140,63 @@ class calendar_information {
|
||||
$this->categoryid = null;
|
||||
$this->categories = null;
|
||||
|
||||
if (null !== $category && $category->id > 0) {
|
||||
// A specific category was requested - set the current category, and include all parents of that category.
|
||||
$category = \coursecat::get($category->id);
|
||||
// Determine the correct category information to show.
|
||||
// When called with a course, the category of that course is usually included too.
|
||||
// When a category was specifically requested, it should be requested with the site id.
|
||||
if (SITEID !== $this->courseid) {
|
||||
// A specific course was requested.
|
||||
// Fetch the category that this course is in, along with all parents.
|
||||
// Do not include child categories of this category, as the user many not have enrolments in those siblings or children.
|
||||
$category = \coursecat::get($course->category);
|
||||
$this->categoryid = $category->id;
|
||||
|
||||
$this->categories = $category->get_parents();
|
||||
$this->categories[] = $category->id;
|
||||
} else if (SITEID === $this->courseid) {
|
||||
// This is the site.
|
||||
// Show categories for all courses the user has access to.
|
||||
$this->categories = true;
|
||||
$categories = [];
|
||||
foreach ($courses as $course) {
|
||||
if ($category = \coursecat::get($course->category, IGNORE_MISSING)) {
|
||||
$categories = array_merge($categories, $category->get_parents());
|
||||
$categories[] = $category->id;
|
||||
} else if (null !== $category && $category->id > 0) {
|
||||
// A specific category was requested.
|
||||
// Fetch all parents of this category, along with all children too.
|
||||
$category = \coursecat::get($category->id);
|
||||
$this->categoryid = $category->id;
|
||||
|
||||
// Build the category list.
|
||||
// This includes the current category.
|
||||
$this->categories = [$category->id];
|
||||
|
||||
// All of its descendants.
|
||||
foreach (\coursecat::get_all() as $cat) {
|
||||
if (array_search($category->id, $cat->get_parents()) !== false) {
|
||||
$this->categories[] = $cat->id;
|
||||
}
|
||||
}
|
||||
|
||||
// And all categories that the user can manage.
|
||||
foreach (\coursecat::get_all() as $category) {
|
||||
if (!$category->has_manage_capability()) {
|
||||
continue;
|
||||
}
|
||||
// And all of its parents.
|
||||
$this->categories = array_merge($this->categories, $category->get_parents());
|
||||
} else if (SITEID === $this->courseid) {
|
||||
// The site was requested.
|
||||
// Fetch all categories where this user has any enrolment, and all categories that this user can manage.
|
||||
|
||||
$categories = array_merge($categories, $category->get_parents());
|
||||
$categories[] = $category->id;
|
||||
// Grab the list of categories that this user has courses in.
|
||||
$coursecategories = array_flip(array_map(function($course) {
|
||||
return $course->category;
|
||||
}, $courses));
|
||||
|
||||
$categories = [];
|
||||
foreach (\coursecat::get_all() as $category) {
|
||||
if (has_capability('moodle/category:manage', $category->get_context(), $USER, false)) {
|
||||
// If a user can manage a category, then they can see all child categories. as well as all parent categories.
|
||||
$categories[] = $category->id;
|
||||
foreach (\coursecat::get_all() as $cat) {
|
||||
if (array_search($category->id, $cat->get_parents()) !== false) {
|
||||
$categories[] = $cat->id;
|
||||
}
|
||||
}
|
||||
$categories = array_merge($categories, $category->get_parents());
|
||||
} else if (isset($coursecategories[$category->id])) {
|
||||
// The user has access to a course in this category.
|
||||
// Fetch all of the parents too.
|
||||
$categories = array_merge($categories, [$category->id], $category->get_parents());
|
||||
$categories[] = $category->id;
|
||||
}
|
||||
}
|
||||
|
||||
$this->categories = array_unique($categories);
|
||||
@@ -2029,6 +2113,91 @@ function calendar_set_filters(array $courseeventsfrom, $ignorefilters = false) {
|
||||
return array($courses, $group, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the capability for viewing a calendar event.
|
||||
*
|
||||
* @param calendar_event $event event object
|
||||
* @return boolean
|
||||
*/
|
||||
function calendar_view_event_allowed(calendar_event $event) {
|
||||
global $USER;
|
||||
|
||||
// Anyone can see site events.
|
||||
if ($event->courseid && $event->courseid == SITEID) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If a user can manage events at the site level they can see any event.
|
||||
$sitecontext = \context_system::instance();
|
||||
// If user has manageentries at site level, return true.
|
||||
if (has_capability('moodle/calendar:manageentries', $sitecontext)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!empty($event->groupid)) {
|
||||
// If it is a group event we need to be able to manage events in the course, or be in the group.
|
||||
if (has_capability('moodle/calendar:manageentries', $event->context) ||
|
||||
has_capability('moodle/calendar:managegroupentries', $event->context)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$mycourses = enrol_get_my_courses('id');
|
||||
return isset($mycourses[$event->courseid]) && groups_is_member($event->groupid);
|
||||
} else if ($event->modulename) {
|
||||
// If this is a module event we need to be able to see the module.
|
||||
$coursemodules = [];
|
||||
$courseid = 0;
|
||||
// Override events do not have the courseid set.
|
||||
if ($event->courseid) {
|
||||
$courseid = $event->courseid;
|
||||
$coursemodules = get_fast_modinfo($event->courseid)->instances;
|
||||
} else {
|
||||
$cmraw = get_coursemodule_from_instance($event->modulename, $event->instance, 0, false, MUST_EXIST);
|
||||
$courseid = $cmraw->course;
|
||||
$coursemodules = get_fast_modinfo($cmraw->course)->instances;
|
||||
}
|
||||
$hasmodule = isset($coursemodules[$event->modulename]);
|
||||
$hasinstance = isset($coursemodules[$event->modulename][$event->instance]);
|
||||
|
||||
// If modinfo doesn't know about the module, return false to be safe.
|
||||
if (!$hasmodule || !$hasinstance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must be able to see the course and the module - MDL-59304.
|
||||
$cm = $coursemodules[$event->modulename][$event->instance];
|
||||
if (!$cm->uservisible) {
|
||||
return false;
|
||||
}
|
||||
$mycourses = enrol_get_my_courses('id');
|
||||
return isset($mycourses[$courseid]);
|
||||
} else if ($event->categoryid) {
|
||||
// If this is a category we need to be able to see the category.
|
||||
$cat = \coursecat::get($event->categoryid, IGNORE_MISSING);
|
||||
if (!$cat) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if (!empty($event->courseid)) {
|
||||
// If it is a course event we need to be able to manage events in the course, or be in the course.
|
||||
if (has_capability('moodle/calendar:manageentries', $event->context)) {
|
||||
return true;
|
||||
}
|
||||
$mycourses = enrol_get_my_courses('id');
|
||||
return isset($mycourses[$event->courseid]);
|
||||
} else if ($event->userid) {
|
||||
if ($event->userid != $USER->id) {
|
||||
// No-one can ever see another users events.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
throw new moodle_exception('unknown event type');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the capability for editing calendar event.
|
||||
*
|
||||
@@ -2137,31 +2306,41 @@ function calendar_delete_event_allowed($event) {
|
||||
* Returns the default courses to display on the calendar when there isn't a specific
|
||||
* course to display.
|
||||
*
|
||||
* @param int $courseid (optional) If passed, an additional course can be returned for admins (the current course).
|
||||
* @param string $fields Comma separated list of course fields to return.
|
||||
* @param bool $canmanage If true, this will return the list of courses the current user can create events in, rather
|
||||
* than the list of courses they see events from (an admin can always add events in a course
|
||||
* calendar, even if they are not enrolled in the course).
|
||||
* @return array $courses Array of courses to display
|
||||
*/
|
||||
function calendar_get_default_courses() {
|
||||
function calendar_get_default_courses($courseid = null, $fields = '*', $canmanage=false) {
|
||||
global $CFG, $DB;
|
||||
|
||||
if (!isloggedin()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if (!empty($CFG->calendar_adminseesall) && has_capability('moodle/calendar:manageentries', \context_system::instance())) {
|
||||
$select = ', ' . \context_helper::get_preload_record_columns_sql('ctx');
|
||||
$join = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
|
||||
$sql = "SELECT c.* $select
|
||||
FROM {course} c
|
||||
$join
|
||||
WHERE EXISTS (SELECT 1 FROM {event} e WHERE e.courseid = c.id)
|
||||
";
|
||||
$courses = $DB->get_records_sql($sql, array('contextlevel' => CONTEXT_COURSE), 0, 20);
|
||||
foreach ($courses as $course) {
|
||||
\context_helper::preload_from_record($course);
|
||||
}
|
||||
return $courses;
|
||||
if (has_capability('moodle/calendar:manageentries', context_system::instance()) &&
|
||||
(!empty($CFG->calendar_adminseesall) || $canmanage)) {
|
||||
|
||||
// Add a c. prefix to every field as expected by get_courses function.
|
||||
$fieldlist = explode(',', $fields);
|
||||
|
||||
$prefixedfields = array_map(function($value) {
|
||||
return 'c.' . trim($value);
|
||||
}, $fieldlist);
|
||||
$courses = get_courses('all', 'c.shortname', implode(',', $prefixedfields));
|
||||
} else {
|
||||
$courses = enrol_get_my_courses($fields);
|
||||
}
|
||||
|
||||
$courses = enrol_get_my_courses();
|
||||
if ($courseid && $courseid != SITEID) {
|
||||
if (empty($courses[$courseid]) && has_capability('moodle/calendar:manageentries', context_system::instance())) {
|
||||
// Allow a site admin to see calendars from courses he is not enrolled in.
|
||||
// This will come from $COURSE.
|
||||
$courses[$courseid] = get_course($courseid);
|
||||
}
|
||||
}
|
||||
|
||||
return $courses;
|
||||
}
|
||||
@@ -2404,6 +2583,8 @@ function calendar_get_all_allowed_types() {
|
||||
|
||||
$types = [];
|
||||
|
||||
$allowed = new stdClass();
|
||||
|
||||
calendar_get_allowed_types($allowed);
|
||||
|
||||
if ($allowed->user) {
|
||||
@@ -2421,7 +2602,8 @@ function calendar_get_all_allowed_types() {
|
||||
// This function warms the context cache for the course so the calls
|
||||
// to load the course context in calendar_get_allowed_types don't result
|
||||
// in additional DB queries.
|
||||
$courses = enrol_get_users_courses($USER->id, true);
|
||||
$courses = calendar_get_default_courses(null, '*', true);
|
||||
|
||||
// We want to pre-fetch all of the groups for each course in a single
|
||||
// query to avoid calendar_get_allowed_types from hitting the DB for
|
||||
// each separate course.
|
||||
@@ -2567,12 +2749,36 @@ function calendar_get_eventtype_choices($courseid) {
|
||||
function calendar_add_subscription($sub) {
|
||||
global $DB, $USER, $SITE;
|
||||
|
||||
// Undo the form definition work around to allow us to have two different
|
||||
// course selectors present depending on which event type the user selects.
|
||||
if (!empty($sub->groupcourseid)) {
|
||||
$sub->courseid = $sub->groupcourseid;
|
||||
unset($sub->groupcourseid);
|
||||
}
|
||||
|
||||
// Pull the group id back out of the value. The form saves the value
|
||||
// as "<courseid>-<groupid>" to allow the javascript to work correctly.
|
||||
if (!empty($sub->groupid)) {
|
||||
list($courseid, $groupid) = explode('-', $sub->groupid);
|
||||
$sub->courseid = $courseid;
|
||||
$sub->groupid = $groupid;
|
||||
}
|
||||
|
||||
// Default course id if none is set.
|
||||
if (empty($sub->courseid)) {
|
||||
if ($sub->eventtype === 'site') {
|
||||
$sub->courseid = SITEID;
|
||||
} else {
|
||||
$sub->courseid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($sub->eventtype === 'site') {
|
||||
$sub->courseid = $SITE->id;
|
||||
} else if ($sub->eventtype === 'group' || $sub->eventtype === 'course') {
|
||||
$sub->courseid = $sub->course;
|
||||
$sub->courseid = $sub->courseid;
|
||||
} else if ($sub->eventtype === 'category') {
|
||||
$sub->categoryid = $sub->category;
|
||||
$sub->categoryid = $sub->categoryid;
|
||||
} else {
|
||||
// User events.
|
||||
$sub->courseid = 0;
|
||||
@@ -2592,8 +2798,25 @@ function calendar_add_subscription($sub) {
|
||||
// Trigger event, calendar subscription added.
|
||||
$eventparams = array('objectid' => $sub->id,
|
||||
'context' => calendar_get_calendar_context($sub),
|
||||
'other' => array('eventtype' => $sub->eventtype, 'courseid' => $sub->courseid)
|
||||
'other' => array(
|
||||
'eventtype' => $sub->eventtype,
|
||||
)
|
||||
);
|
||||
switch ($sub->eventtype) {
|
||||
case 'category':
|
||||
$eventparams['other']['categoryid'] = $sub->categoryid;
|
||||
break;
|
||||
case 'course':
|
||||
$eventparams['other']['courseid'] = $sub->courseid;
|
||||
break;
|
||||
case 'group':
|
||||
$eventparams['other']['courseid'] = $sub->courseid;
|
||||
$eventparams['other']['groupid'] = $sub->groupid;
|
||||
break;
|
||||
default:
|
||||
$eventparams['other']['courseid'] = $sub->courseid;
|
||||
}
|
||||
|
||||
$event = \core\event\calendar_subscription_created::create($eventparams);
|
||||
$event->trigger();
|
||||
return $id;
|
||||
@@ -2611,13 +2834,13 @@ function calendar_add_subscription($sub) {
|
||||
* Add an iCalendar event to the Moodle calendar.
|
||||
*
|
||||
* @param stdClass $event The RFC-2445 iCalendar event
|
||||
* @param int $courseid The course ID
|
||||
* @param int $unused Deprecated
|
||||
* @param int $subscriptionid The iCalendar subscription ID
|
||||
* @param string $timezone The X-WR-TIMEZONE iCalendar property if provided
|
||||
* @throws dml_exception A DML specific exception is thrown for invalid subscriptionids.
|
||||
* @return int Code: CALENDAR_IMPORT_EVENT_UPDATED = updated, CALENDAR_IMPORT_EVENT_INSERTED = inserted, 0 = error
|
||||
*/
|
||||
function calendar_add_icalendar_event($event, $courseid, $subscriptionid, $timezone='UTC') {
|
||||
function calendar_add_icalendar_event($event, $unused = null, $subscriptionid, $timezone='UTC') {
|
||||
global $DB;
|
||||
|
||||
// Probably an unsupported X-MICROSOFT-CDO-BUSYSTATUS event.
|
||||
@@ -2690,6 +2913,7 @@ function calendar_add_icalendar_event($event, $courseid, $subscriptionid, $timez
|
||||
$eventrecord->userid = $sub->userid;
|
||||
$eventrecord->groupid = $sub->groupid;
|
||||
$eventrecord->courseid = $sub->courseid;
|
||||
$eventrecord->categoryid = $sub->categoryid;
|
||||
$eventrecord->eventtype = $sub->eventtype;
|
||||
|
||||
if ($updaterecord = $DB->get_record('event', array('uuid' => $eventrecord->uuid,
|
||||
@@ -2770,8 +2994,24 @@ function calendar_delete_subscription($subscription) {
|
||||
// Trigger event, calendar subscription deleted.
|
||||
$eventparams = array('objectid' => $subscription->id,
|
||||
'context' => calendar_get_calendar_context($subscription),
|
||||
'other' => array('courseid' => $subscription->courseid)
|
||||
'other' => array(
|
||||
'eventtype' => $subscription->eventtype,
|
||||
)
|
||||
);
|
||||
switch ($subscription->eventtype) {
|
||||
case 'category':
|
||||
$eventparams['other']['categoryid'] = $subscription->categoryid;
|
||||
break;
|
||||
case 'course':
|
||||
$eventparams['other']['courseid'] = $subscription->courseid;
|
||||
break;
|
||||
case 'group':
|
||||
$eventparams['other']['courseid'] = $subscription->courseid;
|
||||
$eventparams['other']['groupid'] = $subscription->groupid;
|
||||
break;
|
||||
default:
|
||||
$eventparams['other']['courseid'] = $subscription->courseid;
|
||||
}
|
||||
$event = \core\event\calendar_subscription_deleted::create($eventparams);
|
||||
$event->trigger();
|
||||
}
|
||||
@@ -2810,7 +3050,7 @@ function calendar_get_icalendar($url) {
|
||||
* @param int $subscriptionid The subscription ID.
|
||||
* @return string A log of the import progress, including errors.
|
||||
*/
|
||||
function calendar_import_icalendar_events($ical, $courseid, $subscriptionid = null) {
|
||||
function calendar_import_icalendar_events($ical, $unused = null, $subscriptionid = null) {
|
||||
global $DB;
|
||||
|
||||
$return = '';
|
||||
@@ -2837,7 +3077,7 @@ function calendar_import_icalendar_events($ical, $courseid, $subscriptionid = nu
|
||||
|
||||
$return = '';
|
||||
foreach ($ical->components['VEVENT'] as $event) {
|
||||
$res = calendar_add_icalendar_event($event, $courseid, $subscriptionid, $timezone);
|
||||
$res = calendar_add_icalendar_event($event, null, $subscriptionid, $timezone);
|
||||
switch ($res) {
|
||||
case CALENDAR_IMPORT_EVENT_UPDATED:
|
||||
$updatecount++;
|
||||
@@ -2887,7 +3127,7 @@ function calendar_update_subscription_events($subscriptionid) {
|
||||
}
|
||||
|
||||
$ical = calendar_get_icalendar($sub->url);
|
||||
$return = calendar_import_icalendar_events($ical, $sub->courseid, $subscriptionid);
|
||||
$return = calendar_import_icalendar_events($ical, null, $subscriptionid);
|
||||
$sub->lastupdated = time();
|
||||
|
||||
calendar_update_subscription($sub);
|
||||
@@ -2922,8 +3162,24 @@ function calendar_update_subscription($subscription) {
|
||||
$eventparams = array('userid' => $subscription->userid,
|
||||
'objectid' => $subscription->id,
|
||||
'context' => calendar_get_calendar_context($subscription),
|
||||
'other' => array('eventtype' => $subscription->eventtype, 'courseid' => $subscription->courseid)
|
||||
'other' => array(
|
||||
'eventtype' => $subscription->eventtype,
|
||||
)
|
||||
);
|
||||
switch ($subscription->eventtype) {
|
||||
case 'category':
|
||||
$eventparams['other']['categoryid'] = $subscription->categoryid;
|
||||
break;
|
||||
case 'course':
|
||||
$eventparams['other']['courseid'] = $subscription->courseid;
|
||||
break;
|
||||
case 'group':
|
||||
$eventparams['other']['courseid'] = $subscription->courseid;
|
||||
$eventparams['other']['groupid'] = $subscription->groupid;
|
||||
break;
|
||||
default:
|
||||
$eventparams['other']['courseid'] = $subscription->courseid;
|
||||
}
|
||||
$event = \core\event\calendar_subscription_updated::create($eventparams);
|
||||
$event->trigger();
|
||||
}
|
||||
@@ -2945,9 +3201,14 @@ function calendar_can_edit_subscription($subscriptionorid) {
|
||||
|
||||
$allowed = new \stdClass;
|
||||
$courseid = $subscription->courseid;
|
||||
$categoryid = $subscription->categoryid;
|
||||
$groupid = $subscription->groupid;
|
||||
$category = null;
|
||||
|
||||
calendar_get_allowed_types($allowed, $courseid);
|
||||
if (!empty($categoryid)) {
|
||||
$category = \coursecat::get($categoryid);
|
||||
}
|
||||
calendar_get_allowed_types($allowed, $courseid, null, $category);
|
||||
switch ($subscription->eventtype) {
|
||||
case 'user':
|
||||
return $allowed->user;
|
||||
@@ -2957,6 +3218,12 @@ function calendar_can_edit_subscription($subscriptionorid) {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case 'category':
|
||||
if (isset($allowed->categories[$categoryid])) {
|
||||
return $allowed->categories[$categoryid];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case 'site':
|
||||
return $allowed->site;
|
||||
case 'group':
|
||||
@@ -3207,7 +3474,7 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
|
||||
if ($view == "upcoming") {
|
||||
$template = 'core_calendar/calendar_upcoming';
|
||||
} else if ($view == "upcoming_mini") {
|
||||
$template = 'core_calendar/upcoming_mini';
|
||||
$template = 'core_calendar/calendar_upcoming_mini';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,10 @@ require_once('../config.php');
|
||||
require_once($CFG->libdir.'/bennu/bennu.inc.php');
|
||||
require_once($CFG->dirroot.'/course/lib.php');
|
||||
require_once($CFG->dirroot.'/calendar/lib.php');
|
||||
require_once($CFG->dirroot.'/calendar/managesubscriptions_form.php');
|
||||
|
||||
// Required use.
|
||||
$courseid = optional_param('course', SITEID, PARAM_INT);
|
||||
$courseid = optional_param('course', null, PARAM_INT);
|
||||
$categoryid = optional_param('category', null, PARAM_INT);
|
||||
// Used for processing subscription actions.
|
||||
$subscriptionid = optional_param('id', 0, PARAM_INT);
|
||||
$pollinterval = optional_param('pollinterval', 0, PARAM_INT);
|
||||
@@ -39,6 +39,9 @@ $url = new moodle_url('/calendar/managesubscriptions.php');
|
||||
if ($courseid != SITEID) {
|
||||
$url->param('course', $courseid);
|
||||
}
|
||||
if ($categoryid) {
|
||||
$url->param('categoryid', $categoryid);
|
||||
}
|
||||
navigation_node::override_active_url(new moodle_url('/calendar/view.php', array('view' => 'month')));
|
||||
$PAGE->set_url($url);
|
||||
$PAGE->set_pagelayout('admin');
|
||||
@@ -58,7 +61,7 @@ if (!calendar_user_can_add_event($course)) {
|
||||
print_error('errorcannotimport', 'calendar');
|
||||
}
|
||||
|
||||
$form = new calendar_addsubscription_form(null);
|
||||
$form = new \core_calendar\local\event\forms\managesubscriptions();
|
||||
$form->set_data(array(
|
||||
'course' => $course->id
|
||||
));
|
||||
@@ -75,7 +78,7 @@ if (!empty($formdata)) {
|
||||
$calendar = $form->get_file_content('importfile');
|
||||
$ical = new iCalendar();
|
||||
$ical->unserialize($calendar);
|
||||
$importresults = calendar_import_icalendar_events($ical, $courseid, $subscriptionid);
|
||||
$importresults = calendar_import_icalendar_events($ical, null, $subscriptionid);
|
||||
} else {
|
||||
try {
|
||||
$importresults = calendar_update_subscription_events($subscriptionid);
|
||||
@@ -102,11 +105,54 @@ if (!empty($formdata)) {
|
||||
}
|
||||
}
|
||||
|
||||
$sql = 'SELECT *
|
||||
FROM {event_subscriptions}
|
||||
WHERE courseid = :courseid
|
||||
OR (courseid = 0 AND userid = :userid)';
|
||||
$params = array('courseid' => $courseid, 'userid' => $USER->id);
|
||||
$types = calendar_get_all_allowed_types();
|
||||
|
||||
$searches = [];
|
||||
$params = [];
|
||||
|
||||
$usedefaultfilters = true;
|
||||
if (!empty($courseid) && $courseid == SITEID && isset($types['site'])) {
|
||||
$searches[] = "(eventtype = 'site')";
|
||||
$searches[] = "(eventtype = 'user' AND userid = :userid)";
|
||||
$params['userid'] = $USER->id;
|
||||
$usedefaultfilters = false;
|
||||
}
|
||||
|
||||
if (!empty($courseid) && isset($types['course']) && array_key_exists($courseid, $types['course'])) {
|
||||
$searches[] = "((eventtype = 'course' OR eventtype = 'group') AND courseid = :courseid)";
|
||||
$params += ['courseid' => $courseid];
|
||||
$usedefaultfilters = false;
|
||||
}
|
||||
|
||||
if (!empty($categoryid) && isset($types['category']) && array_key_exists($categoryid, $types['category'])) {
|
||||
$searches[] = "(eventtype = 'category' AND categoryid = :categoryid)";
|
||||
$params += ['categoryid' => $categoryid];
|
||||
$usedefaultfilters = false;
|
||||
}
|
||||
|
||||
if ($usedefaultfilters) {
|
||||
$searches[] = "(eventtype = 'user' AND userid = :userid)";
|
||||
$params['userid'] = $USER->id;
|
||||
|
||||
if (isset($types['site'])) {
|
||||
$searches[] = "(eventtype = 'site' AND courseid = :siteid)";
|
||||
$params += ['siteid' => SITEID];
|
||||
}
|
||||
|
||||
if (isset($types['course'])) {
|
||||
list($courseinsql, $courseparams) = $DB->get_in_or_equal(array_keys($types['course']), SQL_PARAMS_NAMED, 'course');
|
||||
$searches[] = "((eventtype = 'course' OR eventtype = 'group') AND courseid {$courseinsql})";
|
||||
$params += $courseparams;
|
||||
}
|
||||
|
||||
if (isset($types['category'])) {
|
||||
list($categoryinsql, $categoryparams) = $DB->get_in_or_equal(array_keys($types['category']), SQL_PARAMS_NAMED, 'category');
|
||||
$searches[] = "(eventtype = 'category' AND categoryid {$categoryinsql})";
|
||||
$params += $categoryparams;
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "SELECT * FROM {event_subscriptions} WHERE " . implode(' OR ', $searches);;
|
||||
$subscriptions = $DB->get_records_sql($sql, $params);
|
||||
|
||||
// Print title and header.
|
||||
|
||||
+13
-17
@@ -64,15 +64,17 @@ class core_calendar_renderer extends plugin_renderer_base {
|
||||
$time = $calendartype->timestamp_to_date_array($calendar->time);
|
||||
|
||||
$current = $calendar->time;
|
||||
$prevmonthyear = $calendartype->get_prev_month($time['year'], $time['mon']);
|
||||
$prev = $calendartype->convert_to_timestamp(
|
||||
$time['year'],
|
||||
$time['mon'] - 1,
|
||||
$time['mday']
|
||||
$prevmonthyear[1],
|
||||
$prevmonthyear[0],
|
||||
1
|
||||
);
|
||||
$nextmonthyear = $calendartype->get_next_month($time['year'], $time['mon']);
|
||||
$next = $calendartype->convert_to_timestamp(
|
||||
$time['year'],
|
||||
$time['mon'] + 1,
|
||||
$time['mday']
|
||||
$nextmonthyear[1],
|
||||
$nextmonthyear[0],
|
||||
1
|
||||
);
|
||||
|
||||
$content = '';
|
||||
@@ -247,11 +249,7 @@ class core_calendar_renderer extends plugin_renderer_base {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (has_capability('moodle/calendar:manageentries', context_system::instance()) && !empty($CFG->calendar_adminseesall)) {
|
||||
$courses = get_courses('all', 'c.shortname','c.id,c.shortname');
|
||||
} else {
|
||||
$courses = enrol_get_my_courses();
|
||||
}
|
||||
$courses = calendar_get_default_courses($courseid, 'id, shortname');
|
||||
|
||||
unset($courses[SITEID]);
|
||||
|
||||
@@ -285,12 +283,12 @@ class core_calendar_renderer extends plugin_renderer_base {
|
||||
/**
|
||||
* Renders a table containing information about calendar subscriptions.
|
||||
*
|
||||
* @param int $courseid
|
||||
* @param int $unused
|
||||
* @param array $subscriptions
|
||||
* @param string $importresults
|
||||
* @return string
|
||||
*/
|
||||
public function subscription_details($courseid, $subscriptions, $importresults = '') {
|
||||
public function subscription_details($unused = null, $subscriptions, $importresults = '') {
|
||||
$table = new html_table();
|
||||
$table->head = array(
|
||||
get_string('colcalendar', 'calendar'),
|
||||
@@ -320,7 +318,7 @@ class core_calendar_renderer extends plugin_renderer_base {
|
||||
$lastupdated = userdate($sub->lastupdated, get_string('strftimedatetimeshort', 'langconfig'));
|
||||
}
|
||||
|
||||
$cell = new html_table_cell($this->subscription_action_form($sub, $courseid));
|
||||
$cell = new html_table_cell($this->subscription_action_form($sub));
|
||||
$cell->colspan = 2;
|
||||
$type = $sub->eventtype . 'events';
|
||||
|
||||
@@ -344,10 +342,9 @@ class core_calendar_renderer extends plugin_renderer_base {
|
||||
* Creates a form to perform actions on a given subscription.
|
||||
*
|
||||
* @param stdClass $subscription
|
||||
* @param int $courseid
|
||||
* @return string
|
||||
*/
|
||||
protected function subscription_action_form($subscription, $courseid) {
|
||||
protected function subscription_action_form($subscription) {
|
||||
// Assemble form for the subscription row.
|
||||
$html = html_writer::start_tag('form', array('action' => new moodle_url('/calendar/managesubscriptions.php'), 'method' => 'post'));
|
||||
if (empty($subscription->url)) {
|
||||
@@ -369,7 +366,6 @@ class core_calendar_renderer extends plugin_renderer_base {
|
||||
$html .= html_writer::end_tag('div');
|
||||
}
|
||||
$html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
|
||||
$html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'course', 'value' => $courseid));
|
||||
$html .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'id', 'value' => $subscription->id));
|
||||
$html .= html_writer::start_tag('div', array('class' => 'btn-group pull-right'));
|
||||
if (!empty($subscription->url)) {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_calendar/calendar_upcoming_block
|
||||
|
||||
Calendar upcoming view.
|
||||
|
||||
The purpose of this template is to render the calendar upcoming view.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
<div id="calendar-upcoming-block-{{uniqid}}" data-template="core_calendar/upcoming_mini">
|
||||
{{> core_calendar/upcoming_mini}}
|
||||
</div>
|
||||
{{#js}}
|
||||
require(['jquery', 'core_calendar/calendar_view'], function($, CalendarView) {
|
||||
CalendarView.init($("#calendar-upcoming-block-{{uniqid}}"), 'upcoming');
|
||||
});
|
||||
{{/js}}
|
||||
@@ -29,6 +29,7 @@
|
||||
}
|
||||
}}
|
||||
<div{{!
|
||||
}} class="summary-modal-container"{{!
|
||||
}} data-region="summary-modal-container"{{!
|
||||
}} data-event-id="{{id}}"{{!
|
||||
}} data-event-title="{{name}}"{{!
|
||||
@@ -37,24 +38,63 @@
|
||||
}} data-action-event="{{isactionevent}}"{{!
|
||||
}} data-edit-url="{{editurl}}"{{!
|
||||
}}>
|
||||
<h4>{{#str}} when, core_calendar {{/str}}</h4>
|
||||
{{#userdate}} {{timestart}}, {{#str}} strftimerecentfull {{/str}} {{/userdate}}
|
||||
<br>
|
||||
{{#description}}
|
||||
<h4>{{#str}} description {{/str}}</h4>
|
||||
{{{description}}}
|
||||
{{/description}}
|
||||
<h4>{{#str}} eventtype, core_calendar {{/str}}</h4>
|
||||
{{eventtype}}
|
||||
{{#iscategoryevent}}
|
||||
<div>{{{category.nestedname}}}</div>
|
||||
{{/iscategoryevent}}
|
||||
{{#iscourseevent}}
|
||||
<div><a href="{{url}}">{{course.fullname}}</a></div>
|
||||
{{/iscourseevent}}
|
||||
{{> core_calendar/event_subscription}}
|
||||
{{#groupname}}
|
||||
<div><a href="{{url}}">{{{course.fullname}}}</a></div>
|
||||
<div>{{{groupname}}}</div>
|
||||
{{/groupname}}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-xs-1">{{#pix}} i/calendareventtime, core, {{#str}} when, core_calendar {{/str}} {{/pix}}</div>
|
||||
<div class="col-xs-11">{{#userdate}} {{timestart}}, {{#str}} strftimerecentfull {{/str}} {{/userdate}}</div>
|
||||
</div>
|
||||
<div class="row m-t-1">
|
||||
<div class="col-xs-1">{{#pix}} i/calendar, core, {{#str}} eventtype, core_calendar {{/str}} {{/pix}}</div>
|
||||
<div class="col-xs-11">{{eventtype}}</div>
|
||||
</div>
|
||||
{{#description}}
|
||||
<div class="row m-t-1">
|
||||
<div class="col-xs-1">{{#pix}} i/calendareventdescription, core, {{#str}} description {{/str}} {{/pix}}</div>
|
||||
<div class="description-content col-xs-11">{{{.}}}</div>
|
||||
</div>
|
||||
{{/description}}
|
||||
{{#isactionevent}}
|
||||
<div class="row m-t-1">
|
||||
<div class="col-xs-1">{{#pix}} i/courseevent, core, {{#str}} course {{/str}} {{/pix}}</div>
|
||||
<div class="col-xs-11"><a href="{{course.viewurl}}">{{{course.fullname}}}</a></div>
|
||||
</div>
|
||||
{{/isactionevent}}
|
||||
{{#iscategoryevent}}
|
||||
<div class="row m-t-1">
|
||||
<div class="col-xs-1">{{#pix}} i/categoryevent, core, {{#str}} category {{/str}} {{/pix}}</div>
|
||||
<div class="col-xs-11">{{{category.nestedname}}}</div>
|
||||
</div>
|
||||
{{/iscategoryevent}}
|
||||
{{#iscourseevent}}
|
||||
<div class="row m-t-1">
|
||||
<div class="col-xs-1">{{#pix}} i/courseevent, core, {{#str}} course {{/str}} {{/pix}}</div>
|
||||
<div class="col-xs-11"><a href="{{url}}">{{{course.fullname}}}</a></div>
|
||||
</div>
|
||||
{{/iscourseevent}}
|
||||
{{#groupname}}
|
||||
<div class="row m-t-1">
|
||||
<div class="col-xs-1">{{#pix}} i/courseevent, core, {{#str}} course {{/str}} {{/pix}}</div>
|
||||
<div class="col-xs-11"><a href="{{url}}">{{{course.fullname}}}</a></div>
|
||||
</div>
|
||||
<div class="row m-t-1">
|
||||
<div class="col-xs-1">{{#pix}} i/groupevent, core, {{#str}} group {{/str}} {{/pix}}</div>
|
||||
<div class="col-xs-11">{{{groupname}}}</div>
|
||||
</div>
|
||||
{{/groupname}}
|
||||
{{#subscription}}
|
||||
{{#displayeventsource}}
|
||||
<div class="row m-t-1">
|
||||
<div class="col-xs-1">{{#pix}} i/rss, core, {{#str}} eventsource, core_calendar {{/str}} {{/pix}}</div>
|
||||
<div class="col-xs-11">
|
||||
{{#subscriptionurl}}
|
||||
<p><a href="{{subscriptionurl}}">{{#str}}subscriptionsource, core_calendar, {{{subscriptionname}}}{{/str}}</a></p>
|
||||
{{/subscriptionurl}}
|
||||
{{^subscriptionurl}}
|
||||
<p>{{#str}}subscriptionsource, core_calendar, {{{subscriptionname}}}{{/str}}</p>
|
||||
{{/subscriptionurl}}
|
||||
</div>
|
||||
</div>
|
||||
{{/displayeventsource}}
|
||||
{{/subscription}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
data-drop-zone="month-view-day"
|
||||
data-region="day"
|
||||
data-new-event-timestamp="{{neweventtimestamp}}">
|
||||
<div class="hidden-sm-down text-xs-center">
|
||||
<div class="hidden-sm-down hidden-phone text-xs-center">
|
||||
{{#hasevents}}
|
||||
<a data-action="view-day-link" href="{{viewdaylink}}" class="day" title="{{viewdaylinktitle}}">{{mday}}</a>
|
||||
{{/hasevents}}
|
||||
@@ -87,7 +87,7 @@
|
||||
{{^underway}}
|
||||
<li data-region="event-item"
|
||||
data-eventtype-{{calendareventtype}}="1"
|
||||
{{#canedit}}
|
||||
{{#draggable}}
|
||||
draggable="true"
|
||||
data-drag-type="move"
|
||||
{{#mindaytimestamp}}
|
||||
@@ -102,14 +102,14 @@
|
||||
{{#maxdayerror}}
|
||||
data-max-day-error="{{.}}"
|
||||
{{/maxdayerror}}
|
||||
{{/canedit}}>
|
||||
{{/draggable}}>
|
||||
|
||||
<a data-action="view-event" data-event-id="{{id}}" href="{{url}}" title="{{name}}">
|
||||
<span class="badge badge-circle calendar_event_{{calendareventtype}}">
|
||||
|
||||
</span>
|
||||
{{> core_calendar/event_icon}}
|
||||
{{name}}
|
||||
<span class="eventname">{{name}}</span>
|
||||
</a>
|
||||
</li>
|
||||
{{/underway}}
|
||||
@@ -118,9 +118,9 @@
|
||||
</div>
|
||||
{{/hasevents}}
|
||||
</div>
|
||||
<div class="hidden-md-up hidden-desktop">
|
||||
<div class="hidden-md-up hidden-desktop hidden-tablet">
|
||||
{{#hasevents}}
|
||||
<a href="{{viewdaylink}}" class="day" title="{{viewdaylinktitle}}">{{mday}}</a>
|
||||
<a data-action="view-day-link" href="{{viewdaylink}}" class="day" title="{{viewdaylinktitle}}">{{mday}}</a>
|
||||
{{/hasevents}}
|
||||
{{^hasevents}}
|
||||
<div data-region="day-content">
|
||||
|
||||
@@ -31,13 +31,71 @@
|
||||
{
|
||||
}
|
||||
}}
|
||||
<div class="card-text content">
|
||||
<div class="card-text content calendarwrapper"{{!
|
||||
}} id="month-upcoming-mini-{{uniqid}}"{{!
|
||||
}} data-context-id="{{defaulteventcontext}}"{{!
|
||||
}} data-courseid="{{courseid}}"{{!
|
||||
}} data-categoryid="{{categoryid}}"{{!
|
||||
}}>
|
||||
{{> core/overlay_loading}}
|
||||
{{#events}}
|
||||
<div class="event">
|
||||
<div{{!
|
||||
}} class="event"{{!
|
||||
}} data-eventtype-{{calendareventtype}}="1"{{!
|
||||
}} data-region="event-item"{{!
|
||||
}}>
|
||||
<span>{{#icon}}{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}{{/icon}}</span>
|
||||
<a href="{{viewurl}}">{{{name}}}</a>
|
||||
<a{{!
|
||||
}} data-type="event"{{!
|
||||
}} data-action="view-event"{{!
|
||||
}} data-event-id="{{id}}"{{!
|
||||
}} href="{{viewurl}}"{{!
|
||||
}}>{{{name}}}</a>
|
||||
<div class="date">{{{formattedtime}}}</div>
|
||||
<hr>
|
||||
</div>
|
||||
<hr>
|
||||
{{/events}}
|
||||
{{^events}}
|
||||
{{#str}}noupcomingevents, calendar{{/str}}
|
||||
{{/events}}
|
||||
</div>
|
||||
{{#js}}
|
||||
require([
|
||||
'jquery',
|
||||
'core_calendar/selectors',
|
||||
'core_calendar/events',
|
||||
], function(
|
||||
$,
|
||||
CalendarSelectors,
|
||||
CalendarEvents
|
||||
) {
|
||||
var root = $('#month-upcoming-mini-{{uniqid}}');
|
||||
|
||||
$('body').on(CalendarEvents.filterChanged, function(e, data) {
|
||||
M.util.js_pending("month-upcoming-mini-{{uniqid}}-filterChanged");
|
||||
|
||||
// A filter value has been changed.
|
||||
// Find all matching cells in the popover data, and hide them.
|
||||
var target = $("#month-upcoming-mini-{{uniqid}}").find(CalendarSelectors.eventType[data.type]);
|
||||
|
||||
var transitionPromise = $.Deferred();
|
||||
if (data.hidden) {
|
||||
transitionPromise.then(function() {
|
||||
return target.slideUp('fast').promise();
|
||||
});
|
||||
} else {
|
||||
transitionPromise.then(function() {
|
||||
return target.slideDown('fast').promise();
|
||||
});
|
||||
}
|
||||
|
||||
transitionPromise.then(function() {
|
||||
M.util.js_complete("month-upcoming-mini-{{uniqid}}-filterChanged");
|
||||
|
||||
return;
|
||||
});
|
||||
|
||||
transitionPromise.resolve();
|
||||
});
|
||||
});
|
||||
{{/js}}
|
||||
|
||||
@@ -141,7 +141,7 @@ Feature: Perform basic calendar functionality
|
||||
When I click on "Test choice opens" "link"
|
||||
Then "Delete" "button" should not exist
|
||||
And "Edit" "button" should not exist
|
||||
And I should see "Open event"
|
||||
And I should see "Course event"
|
||||
When I click on "Go to activity" "link"
|
||||
And I wait to be redirected
|
||||
Then I should see "Test choice"
|
||||
@@ -150,7 +150,7 @@ Feature: Perform basic calendar functionality
|
||||
When I click on "Test choice closes" "link"
|
||||
Then "Delete" "button" should not exist
|
||||
And "Edit" "button" should not exist
|
||||
And I should see "Close event"
|
||||
And I should see "Course event"
|
||||
When I click on "Go to activity" "link"
|
||||
And I wait to be redirected
|
||||
Then I should see "Test choice"
|
||||
|
||||
@@ -22,7 +22,7 @@ Feature: Import and edit calendar events
|
||||
And I set the following fields to these values:
|
||||
| Calendar name | Test Import |
|
||||
| Import from | Calendar file (.ics) |
|
||||
| Type of event | User events |
|
||||
| Type of event | User |
|
||||
And I upload "calendar/tests/fixtures/import.ics" file to "Calendar file (.ics)" filemanager
|
||||
And I press "Add"
|
||||
And I should see "Events imported: 2"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -423,9 +423,9 @@ class core_calendar_events_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for calendar_subscription_added event.
|
||||
* Tests for calendar_subscription_added event for a site subscription.
|
||||
*/
|
||||
public function test_calendar_subscription_created() {
|
||||
public function test_calendar_subscription_created_site() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
$this->resetAfterTest(true);
|
||||
@@ -447,15 +447,115 @@ class core_calendar_events_testcase extends advanced_testcase {
|
||||
$this->assertEquals($id, $event->objectid);
|
||||
$this->assertEquals($subscription->courseid, $event->other['courseid']);
|
||||
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
|
||||
$this->assertArrayNotHasKey('categoryid', $event->other);
|
||||
$this->assertArrayNotHasKey('groupid', $event->other);
|
||||
$this->assertDebuggingNotCalled();
|
||||
$sink->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for calendar_subscription_added event for a category subscription.
|
||||
*/
|
||||
public function test_calendar_subscription_created_category() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$categoryid = $this->course->category;
|
||||
|
||||
// Create a mock subscription.
|
||||
$subscription = new stdClass();
|
||||
$subscription->eventtype = 'category';
|
||||
$subscription->name = 'test';
|
||||
$subscription->categoryid = $categoryid;
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
$id = calendar_add_subscription($subscription);
|
||||
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\calendar_subscription_created', $event);
|
||||
$this->assertEquals($id, $event->objectid);
|
||||
$this->assertEquals($categoryid, $event->other['categoryid']);
|
||||
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
|
||||
$this->assertArrayNotHasKey('courseid', $event->other);
|
||||
$this->assertArrayNotHasKey('groupid', $event->other);
|
||||
$this->assertDebuggingNotCalled();
|
||||
$sink->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for calendar_subscription_added event for a course subscription.
|
||||
*/
|
||||
public function test_calendar_subscription_created_course() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Create a mock subscription.
|
||||
$subscription = new stdClass();
|
||||
$subscription->eventtype = 'course';
|
||||
$subscription->name = 'test';
|
||||
$subscription->courseid = $this->course->id;
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
$id = calendar_add_subscription($subscription);
|
||||
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\calendar_subscription_created', $event);
|
||||
$this->assertEquals($id, $event->objectid);
|
||||
$this->assertEquals($subscription->courseid, $event->other['courseid']);
|
||||
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
|
||||
$this->assertArrayNotHasKey('categoryid', $event->other);
|
||||
$this->assertArrayNotHasKey('groupid', $event->other);
|
||||
$this->assertDebuggingNotCalled();
|
||||
$sink->close();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for calendar_subscription_updated event.
|
||||
* Tests for calendar_subscription_added event for a group subscription.
|
||||
*/
|
||||
public function test_calendar_subscription_updated() {
|
||||
public function test_calendar_subscription_created_group() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$courseid = $this->course->id;
|
||||
$groupid = 42;
|
||||
|
||||
// Create a mock subscription.
|
||||
$subscription = new stdClass();
|
||||
$subscription->eventtype = 'group';
|
||||
$subscription->name = 'test';
|
||||
$subscription->groupid = "{$courseid}-{$groupid}";
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
$id = calendar_add_subscription($subscription);
|
||||
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\calendar_subscription_created', $event);
|
||||
$this->assertEquals($id, $event->objectid);
|
||||
$this->assertEquals($courseid, $event->other['courseid']);
|
||||
$this->assertEquals($groupid, $event->other['groupid']);
|
||||
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
|
||||
$this->assertArrayNotHasKey('categoryid', $event->other);
|
||||
$this->assertDebuggingNotCalled();
|
||||
$sink->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for calendar_subscription_updated event for a site subscription.
|
||||
*/
|
||||
public function test_calendar_subscription_updated_site() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
$this->resetAfterTest(true);
|
||||
@@ -479,15 +579,120 @@ class core_calendar_events_testcase extends advanced_testcase {
|
||||
$this->assertEquals($subscription->id, $event->objectid);
|
||||
$this->assertEquals($subscription->courseid, $event->other['courseid']);
|
||||
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
|
||||
$this->assertArrayNotHasKey('categoryid', $event->other);
|
||||
$this->assertArrayNotHasKey('groupid', $event->other);
|
||||
$this->assertDebuggingNotCalled();
|
||||
$sink->close();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for calendar_subscription_deleted event.
|
||||
* Tests for calendar_subscription_updated event for a category subscription.
|
||||
*/
|
||||
public function test_calendar_subscription_deleted() {
|
||||
public function test_calendar_subscription_updated_category() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$categoryid = $this->course->category;
|
||||
|
||||
// Create a mock subscription.
|
||||
$subscription = new stdClass();
|
||||
$subscription->eventtype = 'category';
|
||||
$subscription->name = 'test';
|
||||
$subscription->categoryid = $categoryid;
|
||||
$subscription->id = calendar_add_subscription($subscription);
|
||||
// Now edit it.
|
||||
$subscription->name = 'awesome';
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
calendar_update_subscription($subscription);
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\calendar_subscription_updated', $event);
|
||||
$this->assertEquals($subscription->id, $event->objectid);
|
||||
$this->assertEquals($categoryid, $event->other['categoryid']);
|
||||
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
|
||||
$this->assertArrayNotHasKey('courseid', $event->other);
|
||||
$this->assertArrayNotHasKey('groupid', $event->other);
|
||||
$this->assertDebuggingNotCalled();
|
||||
$sink->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for calendar_subscription_updated event for a group subscription.
|
||||
*/
|
||||
public function test_calendar_subscription_updated_course() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Create a mock subscription.
|
||||
$subscription = new stdClass();
|
||||
$subscription->eventtype = 'course';
|
||||
$subscription->name = 'test';
|
||||
$subscription->courseid = $this->course->id;
|
||||
$subscription->id = calendar_add_subscription($subscription);
|
||||
// Now edit it.
|
||||
$subscription->name = 'awesome';
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
calendar_update_subscription($subscription);
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\calendar_subscription_updated', $event);
|
||||
$this->assertEquals($subscription->id, $event->objectid);
|
||||
$this->assertEquals($this->course->id, $event->other['courseid']);
|
||||
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
|
||||
$this->assertArrayNotHasKey('categoryid', $event->other);
|
||||
$this->assertArrayNotHasKey('groupid', $event->other);
|
||||
$this->assertDebuggingNotCalled();
|
||||
$sink->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for calendar_subscription_updated event for a course subscription.
|
||||
*/
|
||||
public function test_calendar_subscription_updated_group() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$courseid = $this->course->id;
|
||||
$groupid = 42;
|
||||
|
||||
// Create a mock subscription.
|
||||
$subscription = new stdClass();
|
||||
$subscription->eventtype = 'group';
|
||||
$subscription->name = 'test';
|
||||
$subscription->groupid = "{$courseid}-{$groupid}";
|
||||
$subscription->id = calendar_add_subscription($subscription);
|
||||
// Now edit it.
|
||||
$subscription->name = 'awesome';
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
calendar_update_subscription($subscription);
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\calendar_subscription_updated', $event);
|
||||
$this->assertEquals($subscription->id, $event->objectid);
|
||||
$this->assertEquals($this->course->id, $event->other['courseid']);
|
||||
$this->assertEquals($groupid, $event->other['groupid']);
|
||||
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
|
||||
$this->assertArrayNotHasKey('categoryid', $event->other);
|
||||
$this->assertDebuggingNotCalled();
|
||||
$sink->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for calendar_subscription_deleted event for a site subscription.
|
||||
*/
|
||||
public function test_calendar_subscription_deleted_site() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
$this->resetAfterTest(true);
|
||||
@@ -512,4 +717,102 @@ class core_calendar_events_testcase extends advanced_testcase {
|
||||
$sink->close();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for calendar_subscription_deleted event for a category subscription.
|
||||
*/
|
||||
public function test_calendar_subscription_deleted_category() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$categoryid = $this->course->category;
|
||||
|
||||
// Create a mock subscription.
|
||||
$subscription = new stdClass();
|
||||
$subscription->eventtype = 'category';
|
||||
$subscription->name = 'test';
|
||||
$subscription->categoryid = $categoryid;
|
||||
$subscription->id = calendar_add_subscription($subscription);
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
calendar_delete_subscription($subscription);
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\calendar_subscription_deleted', $event);
|
||||
$this->assertEquals($subscription->id, $event->objectid);
|
||||
$this->assertEquals($categoryid, $event->other['categoryid']);
|
||||
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
|
||||
$this->assertArrayNotHasKey('courseid', $event->other);
|
||||
$this->assertArrayNotHasKey('groupid', $event->other);
|
||||
$this->assertDebuggingNotCalled();
|
||||
$sink->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for calendar_subscription_deleted event for a course.
|
||||
*/
|
||||
public function test_calendar_subscription_deleted_course() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Create a mock subscription.
|
||||
$subscription = new stdClass();
|
||||
$subscription->eventtype = 'course';
|
||||
$subscription->name = 'test';
|
||||
$subscription->courseid = $this->course->id;
|
||||
$subscription->id = calendar_add_subscription($subscription);
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
calendar_delete_subscription($subscription);
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\calendar_subscription_deleted', $event);
|
||||
$this->assertEquals($subscription->id, $event->objectid);
|
||||
$this->assertEquals($this->course->id, $event->other['courseid']);
|
||||
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
|
||||
$this->assertArrayNotHasKey('categoryid', $event->other);
|
||||
$this->assertArrayNotHasKey('groupid', $event->other);
|
||||
$this->assertDebuggingNotCalled();
|
||||
$sink->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for calendar_subscription_deleted event for a group.
|
||||
*/
|
||||
public function test_calendar_subscription_deleted_group() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$courseid = $this->course->id;
|
||||
$groupid = 42;
|
||||
|
||||
// Create a mock subscription.
|
||||
$subscription = new stdClass();
|
||||
$subscription->eventtype = 'group';
|
||||
$subscription->name = 'test';
|
||||
$subscription->groupid = "{$courseid}-{$groupid}";
|
||||
$subscription->id = calendar_add_subscription($subscription);
|
||||
|
||||
// Trigger and capture the event.
|
||||
$sink = $this->redirectEvents();
|
||||
calendar_delete_subscription($subscription);
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\calendar_subscription_deleted', $event);
|
||||
$this->assertEquals($subscription->id, $event->objectid);
|
||||
$this->assertEquals($this->course->id, $event->other['courseid']);
|
||||
$this->assertEquals($groupid, $event->other['groupid']);
|
||||
$this->assertEquals($subscription->eventtype, $event->other['eventtype']);
|
||||
$this->assertArrayNotHasKey('categoryid', $event->other);
|
||||
$this->assertDebuggingNotCalled();
|
||||
$sink->close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2201,4 +2201,208 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
|
||||
|
||||
$this->assertTrue($result['validationerror']);
|
||||
}
|
||||
|
||||
/**
|
||||
* A user should not be able load the calendar monthly view for a course they cannot access.
|
||||
*/
|
||||
public function test_get_calendar_monthly_view_no_course_permission() {
|
||||
global $USER;
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
$user1 = $generator->create_user();
|
||||
$user2 = $generator->create_user();
|
||||
$course = $generator->create_course();
|
||||
$generator->enrol_user($user1->id, $course->id, 'student');
|
||||
$name = 'Course Event (course' . $course->id . ')';
|
||||
$record = new stdClass();
|
||||
$record->courseid = $course->id;
|
||||
$courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record);
|
||||
|
||||
$timestart = new DateTime();
|
||||
// Admin can load the course.
|
||||
$data = external_api::clean_returnvalue(
|
||||
core_calendar_external::get_calendar_monthly_view_returns(),
|
||||
core_calendar_external::get_calendar_monthly_view($timestart->format('n'), $timestart->format('Y'),
|
||||
$course->id, null, false)
|
||||
);
|
||||
$this->assertEquals($data['courseid'], $course->id);
|
||||
// User enrolled in the course can load the course calendar.
|
||||
$this->setUser($user1);
|
||||
$data = external_api::clean_returnvalue(
|
||||
core_calendar_external::get_calendar_monthly_view_returns(),
|
||||
core_calendar_external::get_calendar_monthly_view($timestart->format('n'), $timestart->format('Y'),
|
||||
$course->id, null, false)
|
||||
);
|
||||
$this->assertEquals($data['courseid'], $course->id);
|
||||
// User not enrolled in the course cannot load the course calendar.
|
||||
$this->setUser($user2);
|
||||
$this->expectException('require_login_exception');
|
||||
$data = external_api::clean_returnvalue(
|
||||
core_calendar_external::get_calendar_monthly_view_returns(),
|
||||
core_calendar_external::get_calendar_monthly_view($timestart->format('n'), $timestart->format('Y'),
|
||||
$course->id, null, false)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A user should not be able load the calendar day view for a course they cannot access.
|
||||
*/
|
||||
public function test_get_calendar_day_view_no_course_permission() {
|
||||
global $USER;
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
$user1 = $generator->create_user();
|
||||
$user2 = $generator->create_user();
|
||||
$course = $generator->create_course();
|
||||
$generator->enrol_user($user1->id, $course->id, 'student');
|
||||
$name = 'Course Event (course' . $course->id . ')';
|
||||
$record = new stdClass();
|
||||
$record->courseid = $course->id;
|
||||
$courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record);
|
||||
|
||||
$timestart = new DateTime();
|
||||
// Admin can load the course.
|
||||
$data = external_api::clean_returnvalue(
|
||||
core_calendar_external::get_calendar_day_view_returns(),
|
||||
core_calendar_external::get_calendar_day_view($timestart->format('n'), $timestart->format('Y'),
|
||||
$timestart->format('j'), $course->id, null)
|
||||
);
|
||||
$this->assertEquals($data['courseid'], $course->id);
|
||||
// User enrolled in the course can load the course calendar.
|
||||
$this->setUser($user1);
|
||||
$data = external_api::clean_returnvalue(
|
||||
core_calendar_external::get_calendar_day_view_returns(),
|
||||
core_calendar_external::get_calendar_day_view($timestart->format('n'), $timestart->format('Y'),
|
||||
$timestart->format('j'), $course->id, null)
|
||||
);
|
||||
$this->assertEquals($data['courseid'], $course->id);
|
||||
// User not enrolled in the course cannot load the course calendar.
|
||||
$this->setUser($user2);
|
||||
$this->expectException('require_login_exception');
|
||||
$data = external_api::clean_returnvalue(
|
||||
core_calendar_external::get_calendar_day_view_returns(),
|
||||
core_calendar_external::get_calendar_day_view($timestart->format('n'), $timestart->format('Y'),
|
||||
$timestart->format('j'), $course->id, null)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A user should not be able load the calendar upcoming view for a course they cannot access.
|
||||
*/
|
||||
public function test_get_calendar_upcoming_view_no_course_permission() {
|
||||
global $USER;
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
$user1 = $generator->create_user();
|
||||
$user2 = $generator->create_user();
|
||||
$course = $generator->create_course();
|
||||
$generator->enrol_user($user1->id, $course->id, 'student');
|
||||
$name = 'Course Event (course' . $course->id . ')';
|
||||
$record = new stdClass();
|
||||
$record->courseid = $course->id;
|
||||
$courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record);
|
||||
|
||||
// Admin can load the course.
|
||||
$data = external_api::clean_returnvalue(
|
||||
core_calendar_external::get_calendar_upcoming_view_returns(),
|
||||
core_calendar_external::get_calendar_upcoming_view($course->id, null)
|
||||
);
|
||||
$this->assertEquals($data['courseid'], $course->id);
|
||||
// User enrolled in the course can load the course calendar.
|
||||
$this->setUser($user1);
|
||||
$data = external_api::clean_returnvalue(
|
||||
core_calendar_external::get_calendar_upcoming_view_returns(),
|
||||
core_calendar_external::get_calendar_upcoming_view($course->id, null)
|
||||
);
|
||||
$this->assertEquals($data['courseid'], $course->id);
|
||||
// User not enrolled in the course cannot load the course calendar.
|
||||
$this->setUser($user2);
|
||||
$this->expectException('require_login_exception');
|
||||
$data = external_api::clean_returnvalue(
|
||||
core_calendar_external::get_calendar_upcoming_view_returns(),
|
||||
core_calendar_external::get_calendar_upcoming_view($course->id, null)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A user should not be able load the calendar event for a course they cannot access.
|
||||
*/
|
||||
public function test_get_calendar_event_by_id_no_course_permission() {
|
||||
global $USER;
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
$user1 = $generator->create_user();
|
||||
$user2 = $generator->create_user();
|
||||
$course = $generator->create_course();
|
||||
$generator->enrol_user($user1->id, $course->id, 'student');
|
||||
$name = 'Course Event (course' . $course->id . ')';
|
||||
$record = new stdClass();
|
||||
$record->courseid = $course->id;
|
||||
$courseevent = $this->create_calendar_event($name, $USER->id, 'course', 0, time(), $record);
|
||||
|
||||
// Admin can load the course event.
|
||||
$data = external_api::clean_returnvalue(
|
||||
core_calendar_external::get_calendar_event_by_id_returns(),
|
||||
core_calendar_external::get_calendar_event_by_id($courseevent->id)
|
||||
);
|
||||
$this->assertEquals($data['event']['id'], $courseevent->id);
|
||||
// User enrolled in the course can load the course event.
|
||||
$this->setUser($user1);
|
||||
$data = external_api::clean_returnvalue(
|
||||
core_calendar_external::get_calendar_event_by_id_returns(),
|
||||
core_calendar_external::get_calendar_event_by_id($courseevent->id)
|
||||
);
|
||||
$this->assertEquals($data['event']['id'], $courseevent->id);
|
||||
// User not enrolled in the course cannot load the course event.
|
||||
$this->setUser($user2);
|
||||
$this->expectException('required_capability_exception');
|
||||
$data = external_api::clean_returnvalue(
|
||||
core_calendar_external::get_calendar_event_by_id_returns(),
|
||||
core_calendar_external::get_calendar_event_by_id($courseevent->id)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A user should not be able load the calendar events for a category they cannot see.
|
||||
*/
|
||||
public function test_get_calendar_events_hidden_category() {
|
||||
global $USER;
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
$user1 = $generator->create_user();
|
||||
$category = $generator->create_category(['visible' => 0]);
|
||||
$name = 'Category Event (category: ' . $category->id . ')';
|
||||
$record = new stdClass();
|
||||
$record->categoryid = $category->id;
|
||||
$categoryevent = $this->create_calendar_event($name, $USER->id, 'category', 0, time(), $record);
|
||||
|
||||
$events = [
|
||||
'eventids' => [$categoryevent->id]
|
||||
];
|
||||
$options = [];
|
||||
// Admin can load the category event.
|
||||
$data = external_api::clean_returnvalue(
|
||||
core_calendar_external::get_calendar_events_returns(),
|
||||
core_calendar_external::get_calendar_events($events, $options)
|
||||
);
|
||||
$this->assertEquals($data['events'][0]['id'], $categoryevent->id);
|
||||
// User with no special permission to see hidden categories will not see the event.
|
||||
$this->setUser($user1);
|
||||
$data = external_api::clean_returnvalue(
|
||||
core_calendar_external::get_calendar_events_returns(),
|
||||
core_calendar_external::get_calendar_events($events, $options)
|
||||
);
|
||||
$this->assertCount(0, $data['events']);
|
||||
$this->assertEquals('nopermissions', $data['warnings'][0]['warningcode']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ class core_calendar_lib_testcase extends advanced_testcase {
|
||||
$this->assertEquals($ical->parser_errors, array());
|
||||
|
||||
$sub = calendar_get_subscription($id);
|
||||
calendar_import_icalendar_events($ical, $sub->courseid, $sub->id);
|
||||
calendar_import_icalendar_events($ical, null, $sub->id);
|
||||
$count = $DB->count_records('event', array('subscriptionid' => $sub->id));
|
||||
$this->assertEquals($count, 1);
|
||||
|
||||
@@ -205,7 +205,7 @@ class core_calendar_lib_testcase extends advanced_testcase {
|
||||
$this->assertEquals($ical->parser_errors, array());
|
||||
|
||||
$sub = calendar_get_subscription($id);
|
||||
calendar_import_icalendar_events($ical, $sub->courseid, $sub->id);
|
||||
calendar_import_icalendar_events($ical, null, $sub->id);
|
||||
$count = $DB->count_records('event', array('subscriptionid' => $sub->id));
|
||||
$this->assertEquals($count, 1);
|
||||
|
||||
@@ -222,7 +222,7 @@ class core_calendar_lib_testcase extends advanced_testcase {
|
||||
$this->assertEquals($ical->parser_errors, array());
|
||||
|
||||
$sub = calendar_get_subscription($id);
|
||||
calendar_import_icalendar_events($ical, $sub->courseid, $sub->id);
|
||||
calendar_import_icalendar_events($ical, null, $sub->id);
|
||||
$count = $DB->count_records('event', array('subscriptionid' => $sub->id));
|
||||
$this->assertEquals($count, 1);
|
||||
}
|
||||
@@ -672,4 +672,60 @@ class core_calendar_lib_testcase extends advanced_testcase {
|
||||
$this->assertEquals($group1->id, $typegroups[0]->id);
|
||||
$this->assertEquals($group2->id, $typegroups[1]->id);
|
||||
}
|
||||
|
||||
public function test_calendar_get_default_courses() {
|
||||
global $USER, $CFG;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$course1 = $generator->create_course();
|
||||
$course2 = $generator->create_course();
|
||||
$course3 = $generator->create_course();
|
||||
$context = context_course::instance($course1->id);
|
||||
|
||||
$this->setAdminUser();
|
||||
$admin = clone $USER;
|
||||
|
||||
$teacher = $generator->create_user();
|
||||
$generator->enrol_user($teacher->id, $course1->id, 'teacher');
|
||||
$generator->enrol_user($admin->id, $course1->id, 'teacher');
|
||||
|
||||
$CFG->calendar_adminseesall = false;
|
||||
|
||||
$courses = calendar_get_default_courses();
|
||||
// Only enrolled in one course.
|
||||
$this->assertCount(1, $courses);
|
||||
$courses = calendar_get_default_courses($course2->id);
|
||||
// Enrolled course + current course.
|
||||
$this->assertCount(2, $courses);
|
||||
$CFG->calendar_adminseesall = true;
|
||||
$courses = calendar_get_default_courses();
|
||||
// All courses + SITE.
|
||||
$this->assertCount(4, $courses);
|
||||
$courses = calendar_get_default_courses($course2->id);
|
||||
// All courses + SITE.
|
||||
$this->assertCount(4, $courses);
|
||||
|
||||
$this->setUser($teacher);
|
||||
|
||||
$CFG->calendar_adminseesall = false;
|
||||
|
||||
$courses = calendar_get_default_courses();
|
||||
// Only enrolled in one course.
|
||||
$this->assertCount(1, $courses);
|
||||
$courses = calendar_get_default_courses($course2->id);
|
||||
// Enrolled course only (ignore current).
|
||||
$this->assertCount(1, $courses);
|
||||
// This setting should not affect teachers.
|
||||
$CFG->calendar_adminseesall = true;
|
||||
$courses = calendar_get_default_courses();
|
||||
// Only enrolled in one course.
|
||||
$this->assertCount(1, $courses);
|
||||
$courses = calendar_get_default_courses($course2->id);
|
||||
// Enrolled course only (ignore current).
|
||||
$this->assertCount(1, $courses);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,37 @@ class core_calendar_local_api_testcase extends advanced_testcase {
|
||||
$this->resetAfterTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a feedback activity instance and a calendar event for
|
||||
* that instance.
|
||||
*
|
||||
* @param array $feedbackproperties Properties to set on the feedback activity
|
||||
* @param array $eventproperties Properties to set on the calendar event
|
||||
* @return array The feedback activity and the calendar event
|
||||
*/
|
||||
protected function create_feedback_activity_and_event(array $feedbackproperties = [], array $eventproperties = []) {
|
||||
$generator = $this->getDataGenerator();
|
||||
$course = $generator->create_course();
|
||||
$mapper = container::get_event_mapper();
|
||||
$feedbackgenerator = $generator->get_plugin_generator('mod_feedback');
|
||||
$feedback = $feedbackgenerator->create_instance(array_merge(
|
||||
['course' => $course->id],
|
||||
$feedbackproperties
|
||||
));
|
||||
|
||||
$event = create_event(array_merge(
|
||||
[
|
||||
'courseid' => $course->id,
|
||||
'modulename' => 'feedback',
|
||||
'instance' => $feedback->id
|
||||
],
|
||||
$eventproperties
|
||||
));
|
||||
$event = $mapper->from_legacy_event_to_event($event);
|
||||
|
||||
return [$feedback, $event];
|
||||
}
|
||||
|
||||
/**
|
||||
* Requesting calendar events from a given time should return all events with a sort
|
||||
* time at or after the requested time. All events prior to that time should not
|
||||
@@ -928,4 +959,330 @@ class core_calendar_local_api_testcase extends advanced_testcase {
|
||||
$this->expectException('moodle_exception');
|
||||
$newEvent = \core_calendar\local\api::update_event_start_day($event, $newstartdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updating the start day of an event with no maximum cutoff should
|
||||
* update the corresponding activity property.
|
||||
*
|
||||
* Note: This test uses the feedback activity because it requires
|
||||
* module callbacks to be in place to test.
|
||||
*/
|
||||
public function test_update_event_start_day_activity_event_no_max() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/mod/feedback/lib.php');
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
$timeopen = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
|
||||
$newstartdate = new DateTimeImmutable('2018-02-2T10:00:00+08:00');
|
||||
$expected = new DateTimeImmutable('2018-02-2T15:00:00+08:00');
|
||||
list($feedback, $event) = $this->create_feedback_activity_and_event(
|
||||
[
|
||||
'timeopen' => $timeopen->getTimestamp(),
|
||||
'timeclose' => 0
|
||||
],
|
||||
[
|
||||
'eventtype' => FEEDBACK_EVENT_TYPE_OPEN,
|
||||
'timestart' => $timeopen->getTimestamp()
|
||||
]
|
||||
);
|
||||
$newevent = \core_calendar\local\api::update_event_start_day($event, $newstartdate);
|
||||
$actual = $newevent->get_times()->get_start_time();
|
||||
$feedback = $DB->get_record('feedback', ['id' => $feedback->id]);
|
||||
|
||||
$this->assertEquals($expected->getTimestamp(), $actual->getTimestamp());
|
||||
$this->assertEquals($expected->getTimestamp(), $feedback->timeopen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updating the start day of an event belonging to an activity to a value
|
||||
* less than the maximum cutoff should update the corresponding activity
|
||||
* property.
|
||||
*
|
||||
* Note: This test uses the feedback activity because it requires
|
||||
* module callbacks to be in place to test.
|
||||
*/
|
||||
public function test_update_event_start_day_activity_event_less_than_max() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/mod/feedback/lib.php');
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
$timeopen = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
|
||||
$timeclose = new DateTimeImmutable('2019-01-1T15:00:00+08:00');
|
||||
$newstartdate = new DateTimeImmutable('2018-02-2T10:00:00+08:00');
|
||||
$expected = new DateTimeImmutable('2018-02-2T15:00:00+08:00');
|
||||
list($feedback, $event) = $this->create_feedback_activity_and_event(
|
||||
[
|
||||
'timeopen' => $timeopen->getTimestamp(),
|
||||
'timeclose' => $timeclose->getTimestamp()
|
||||
],
|
||||
[
|
||||
'eventtype' => FEEDBACK_EVENT_TYPE_OPEN,
|
||||
'timestart' => $timeopen->getTimestamp()
|
||||
]
|
||||
);
|
||||
|
||||
$newevent = \core_calendar\local\api::update_event_start_day($event, $newstartdate);
|
||||
$actual = $newevent->get_times()->get_start_time();
|
||||
$feedback = $DB->get_record('feedback', ['id' => $feedback->id]);
|
||||
|
||||
$this->assertEquals($expected->getTimestamp(), $actual->getTimestamp());
|
||||
$this->assertEquals($expected->getTimestamp(), $feedback->timeopen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updating the start day of an event belonging to an activity to a value
|
||||
* equal to the maximum cutoff should update the corresponding activity
|
||||
* property.
|
||||
*
|
||||
* Note: This test uses the feedback activity because it requires
|
||||
* module callbacks to be in place to test.
|
||||
*/
|
||||
public function test_update_event_start_day_activity_event_equal_to_max() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/mod/feedback/lib.php');
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
$timeopen = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
|
||||
$timeclose = new DateTimeImmutable('2018-02-2T15:00:00+08:00');
|
||||
$newstartdate = new DateTimeImmutable('2018-02-2T10:00:00+08:00');
|
||||
list($feedback, $event) = $this->create_feedback_activity_and_event(
|
||||
[
|
||||
'timeopen' => $timeopen->getTimestamp(),
|
||||
'timeclose' => $timeclose->getTimestamp(),
|
||||
],
|
||||
[
|
||||
'eventtype' => FEEDBACK_EVENT_TYPE_OPEN,
|
||||
'timestart' => $timeopen->getTimestamp()
|
||||
]
|
||||
);
|
||||
|
||||
$newevent = \core_calendar\local\api::update_event_start_day($event, $newstartdate);
|
||||
$actual = $newevent->get_times()->get_start_time();
|
||||
$feedback = $DB->get_record('feedback', ['id' => $feedback->id]);
|
||||
|
||||
$this->assertEquals($timeclose->getTimestamp(), $actual->getTimestamp());
|
||||
$this->assertEquals($timeclose->getTimestamp(), $feedback->timeopen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updating the start day of an event belonging to an activity to a value
|
||||
* after the maximum cutoff should not update the corresponding activity
|
||||
* property. Instead it should throw an exception.
|
||||
*
|
||||
* Note: This test uses the feedback activity because it requires
|
||||
* module callbacks to be in place to test.
|
||||
*/
|
||||
public function test_update_event_start_day_activity_event_after_max() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/mod/feedback/lib.php');
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
$timeopen = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
|
||||
$timeclose = new DateTimeImmutable('2017-02-2T15:00:00+08:00');
|
||||
$newstartdate = new DateTimeImmutable('2018-02-2T10:00:00+08:00');
|
||||
list($feedback, $event) = $this->create_feedback_activity_and_event(
|
||||
[
|
||||
'timeopen' => $timeopen->getTimestamp(),
|
||||
'timeclose' => $timeclose->getTimestamp(),
|
||||
],
|
||||
[
|
||||
'eventtype' => FEEDBACK_EVENT_TYPE_OPEN,
|
||||
'timestart' => $timeopen->getTimestamp()
|
||||
]
|
||||
);
|
||||
|
||||
$this->expectException('moodle_exception');
|
||||
$newevent = \core_calendar\local\api::update_event_start_day($event, $newstartdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updating the start day of an event with no minimum cutoff should
|
||||
* update the corresponding activity property.
|
||||
*
|
||||
* Note: This test uses the feedback activity because it requires
|
||||
* module callbacks to be in place to test.
|
||||
*/
|
||||
public function test_update_event_start_day_activity_event_no_min() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/mod/feedback/lib.php');
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
$timeclose = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
|
||||
$newstartdate = new DateTimeImmutable('2016-02-2T10:00:00+08:00');
|
||||
$expected = new DateTimeImmutable('2016-02-2T15:00:00+08:00');
|
||||
list($feedback, $event) = $this->create_feedback_activity_and_event(
|
||||
[
|
||||
'timeopen' => 0,
|
||||
'timeclose' => $timeclose->getTimestamp()
|
||||
],
|
||||
[
|
||||
'eventtype' => FEEDBACK_EVENT_TYPE_OPEN,
|
||||
'timestart' => $timeclose->getTimestamp()
|
||||
]
|
||||
);
|
||||
|
||||
$newevent = \core_calendar\local\api::update_event_start_day($event, $newstartdate);
|
||||
$actual = $newevent->get_times()->get_start_time();
|
||||
$feedback = $DB->get_record('feedback', ['id' => $feedback->id]);
|
||||
|
||||
$this->assertEquals($expected->getTimestamp(), $actual->getTimestamp());
|
||||
$this->assertEquals($expected->getTimestamp(), $feedback->timeopen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updating the start day of an event belonging to an activity to a value
|
||||
* greater than the minimum cutoff should update the corresponding activity
|
||||
* property.
|
||||
*
|
||||
* Note: This test uses the feedback activity because it requires
|
||||
* module callbacks to be in place to test.
|
||||
*/
|
||||
public function test_update_event_start_day_activity_event_greater_than_min() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/mod/feedback/lib.php');
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
$timeopen = new DateTimeImmutable('2016-01-1T15:00:00+08:00');
|
||||
$timeclose = new DateTimeImmutable('2019-01-1T15:00:00+08:00');
|
||||
$newstartdate = new DateTimeImmutable('2018-02-2T10:00:00+08:00');
|
||||
$expected = new DateTimeImmutable('2018-02-2T15:00:00+08:00');
|
||||
list($feedback, $event) = $this->create_feedback_activity_and_event(
|
||||
[
|
||||
'timeopen' => $timeopen->getTimestamp(),
|
||||
'timeclose' => $timeclose->getTimestamp()
|
||||
],
|
||||
[
|
||||
'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE,
|
||||
'timestart' => $timeclose->getTimestamp()
|
||||
]
|
||||
);
|
||||
|
||||
$newevent = \core_calendar\local\api::update_event_start_day($event, $newstartdate);
|
||||
$actual = $newevent->get_times()->get_start_time();
|
||||
$feedback = $DB->get_record('feedback', ['id' => $feedback->id]);
|
||||
|
||||
$this->assertEquals($expected->getTimestamp(), $actual->getTimestamp());
|
||||
$this->assertEquals($expected->getTimestamp(), $feedback->timeclose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updating the start day of an event belonging to an activity to a value
|
||||
* equal to the minimum cutoff should update the corresponding activity
|
||||
* property.
|
||||
*
|
||||
* Note: This test uses the feedback activity because it requires
|
||||
* module callbacks to be in place to test.
|
||||
*/
|
||||
public function test_update_event_start_day_activity_event_equal_to_min() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/mod/feedback/lib.php');
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
$timeopen = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
|
||||
$timeclose = new DateTimeImmutable('2018-02-2T15:00:00+08:00');
|
||||
$newstartdate = new DateTimeImmutable('2017-01-1T10:00:00+08:00');
|
||||
$expected = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
|
||||
list($feedback, $event) = $this->create_feedback_activity_and_event(
|
||||
[
|
||||
'timeopen' => $timeopen->getTimestamp(),
|
||||
'timeclose' => $timeclose->getTimestamp(),
|
||||
],
|
||||
[
|
||||
'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE,
|
||||
'timestart' => $timeclose->getTimestamp()
|
||||
]
|
||||
);
|
||||
|
||||
$newevent = \core_calendar\local\api::update_event_start_day($event, $newstartdate);
|
||||
$actual = $newevent->get_times()->get_start_time();
|
||||
$feedback = $DB->get_record('feedback', ['id' => $feedback->id]);
|
||||
|
||||
$this->assertEquals($expected->getTimestamp(), $actual->getTimestamp());
|
||||
$this->assertEquals($expected->getTimestamp(), $feedback->timeclose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updating the start day of an event belonging to an activity to a value
|
||||
* before the minimum cutoff should not update the corresponding activity
|
||||
* property. Instead it should throw an exception.
|
||||
*
|
||||
* Note: This test uses the feedback activity because it requires
|
||||
* module callbacks to be in place to test.
|
||||
*/
|
||||
public function test_update_event_start_day_activity_event_before_min() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/mod/feedback/lib.php');
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
$timeopen = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
|
||||
$timeclose = new DateTimeImmutable('2017-02-2T15:00:00+08:00');
|
||||
$newstartdate = new DateTimeImmutable('2016-02-2T10:00:00+08:00');
|
||||
list($feedback, $event) = $this->create_feedback_activity_and_event(
|
||||
[
|
||||
'timeopen' => $timeopen->getTimestamp(),
|
||||
'timeclose' => $timeclose->getTimestamp(),
|
||||
],
|
||||
[
|
||||
'eventtype' => FEEDBACK_EVENT_TYPE_CLOSE,
|
||||
'timestart' => $timeclose->getTimestamp()
|
||||
]
|
||||
);
|
||||
|
||||
$this->expectException('moodle_exception');
|
||||
$newevent = \core_calendar\local\api::update_event_start_day($event, $newstartdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updating the start day of an overridden event belonging to an activity
|
||||
* should result in an exception. This is to prevent the drag and drop
|
||||
* of override events.
|
||||
*
|
||||
* Note: This test uses the quiz activity because it requires
|
||||
* module callbacks to be in place and override event support to test.
|
||||
*/
|
||||
public function test_update_event_start_day_activity_event_override() {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->dirroot . '/calendar/lib.php');
|
||||
require_once($CFG->dirroot . '/mod/quiz/lib.php');
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminUser();
|
||||
$mapper = container::get_event_mapper();
|
||||
$timeopen = new DateTimeImmutable('2017-01-1T15:00:00+08:00');
|
||||
$newstartdate = new DateTimeImmutable('2016-02-2T10:00:00+08:00');
|
||||
$generator = $this->getDataGenerator();
|
||||
$user = $generator->create_user();
|
||||
$course = $generator->create_course();
|
||||
$quizgenerator = $generator->get_plugin_generator('mod_quiz');
|
||||
$quiz = $quizgenerator->create_instance([
|
||||
'course' => $course->id,
|
||||
'timeopen' => $timeopen->getTimestamp(),
|
||||
]);
|
||||
$event = create_event([
|
||||
'courseid' => $course->id,
|
||||
'userid' => $user->id,
|
||||
'modulename' => 'quiz',
|
||||
'instance' => $quiz->id,
|
||||
'eventtype' => QUIZ_EVENT_TYPE_OPEN,
|
||||
'timestart' => $timeopen->getTimestamp()
|
||||
]);
|
||||
$event = $mapper->from_legacy_event_to_event($event);
|
||||
$record = (object) [
|
||||
'quiz' => $quiz->id,
|
||||
'userid' => $user->id
|
||||
];
|
||||
|
||||
$DB->insert_record('quiz_overrides', $record);
|
||||
|
||||
$this->expectException('moodle_exception');
|
||||
$newevent = \core_calendar\local\api::update_event_start_day($event, $newstartdate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ information provided here is intended especially for developers.
|
||||
=== 3.4 ===
|
||||
* calendar_get_mini has been deprecated. Please update to use the new
|
||||
exporters and renderers.
|
||||
* added core_calendar_validate_event_timestart and core_calendar_event_timestart_updated callbacks for module events
|
||||
* added core_calendar_get_valid_event_timestart_range and core_calendar_event_timestart_updated callbacks for module events
|
||||
when the update_event_start_day function is used in the local api.
|
||||
|
||||
=== 3.3 ===
|
||||
|
||||
+7
-21
@@ -77,34 +77,20 @@ $url->param('time', $time);
|
||||
|
||||
$PAGE->set_url($url);
|
||||
|
||||
$course = get_course($courseid);
|
||||
|
||||
if ($courseid != SITEID && !empty($courseid)) {
|
||||
// Course ID must be valid and existing.
|
||||
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
|
||||
$courses = array($course->id => $course);
|
||||
navigation_node::override_active_url(new moodle_url('/course/view.php', array('id' => $course->id)));
|
||||
} else if (!empty($categoryid)) {
|
||||
$PAGE->set_category_by_id($categoryid);
|
||||
navigation_node::override_active_url(new moodle_url('/course/index.php', array('categoryid' => $categoryid)));
|
||||
} else {
|
||||
$course = get_site();
|
||||
$courses = calendar_get_default_courses();
|
||||
if ($categoryid) {
|
||||
$PAGE->set_category_by_id($categoryid);
|
||||
} else {
|
||||
$PAGE->set_context(context_system::instance());
|
||||
}
|
||||
if ($PAGE->context->contextlevel === CONTEXT_COURSECAT) {
|
||||
// Restrict to categories, and their parents, and the courses that the user is enrolled in within those
|
||||
// categories.
|
||||
$categories = array_keys($PAGE->categories);
|
||||
$courses = array_filter($courses, function($course) use ($categories) {
|
||||
return array_search($course->category, $categories) !== false;
|
||||
});
|
||||
navigation_node::override_active_url(new moodle_url('/course/index.php', array('categoryid' => $categoryid)));
|
||||
}
|
||||
$PAGE->set_context(context_system::instance());
|
||||
}
|
||||
|
||||
require_login($course, false);
|
||||
|
||||
$calendar = new calendar_information(0, 0, 0, $time);
|
||||
$calendar->set_sources($course, $courses, $PAGE->category);
|
||||
$calendar = calendar_information::create($time, $courseid, $categoryid);
|
||||
|
||||
$pagetitle = '';
|
||||
|
||||
|
||||
@@ -324,7 +324,7 @@ class core_cohort_external extends external_api {
|
||||
* @return array
|
||||
*/
|
||||
public static function search_cohorts($query, $context, $includes = 'parents', $limitfrom = 0, $limitnum = 25) {
|
||||
global $DB, $CFG, $PAGE;
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/cohort/lib.php');
|
||||
|
||||
$params = self::validate_parameters(self::search_cohorts_parameters(), array(
|
||||
@@ -341,7 +341,6 @@ class core_cohort_external extends external_api {
|
||||
$limitnum = $params['limitnum'];
|
||||
|
||||
self::validate_context($context);
|
||||
$output = $PAGE->get_renderer('tool_lp');
|
||||
|
||||
$manager = has_capability('moodle/cohort:manage', $context);
|
||||
if (!$manager) {
|
||||
|
||||
@@ -59,7 +59,10 @@ class api {
|
||||
if (is_object($instanceorid)) {
|
||||
$instance = $instanceorid;
|
||||
} else {
|
||||
$instance = $DB->get_record($modulename, array('id' => $instanceorid), '*', MUST_EXIST);
|
||||
$instance = $DB->get_record($modulename, array('id' => $instanceorid), '*', IGNORE_MISSING);
|
||||
}
|
||||
if (!$instance) {
|
||||
return false;
|
||||
}
|
||||
$course = get_course($instance->course);
|
||||
|
||||
|
||||
@@ -72,6 +72,13 @@ class core_completion_api_testcase extends advanced_testcase {
|
||||
$this->assertEquals(\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED, $event->eventtype);
|
||||
$this->assertEquals($time, $event->timestart);
|
||||
$this->assertEquals($time, $event->timesort);
|
||||
|
||||
require_once($CFG->dirroot . '/course/lib.php');
|
||||
// Delete the module.
|
||||
course_delete_module($assign->cmid);
|
||||
|
||||
// Check we don't get a failure when called on a deleted module.
|
||||
\core_completion\api::update_completion_date_event($assign->cmid, 'assign', null, $time);
|
||||
}
|
||||
|
||||
public function test_update_completion_date_event_update() {
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
"type": "project",
|
||||
"homepage": "https://moodle.org",
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "5.5.*",
|
||||
"phpunit/dbUnit": "1.4.*",
|
||||
"phpunit/phpunit": "6.4.*",
|
||||
"phpunit/dbUnit": "3.0.*",
|
||||
"moodlehq/behat-extension": "3.34.1",
|
||||
"mikey179/vfsStream": "^1.6"
|
||||
}
|
||||
|
||||
Generated
+403
-217
File diff suppressed because it is too large
Load Diff
@@ -69,10 +69,8 @@ class completion_enabled extends \core_analytics\local\indicator\binary {
|
||||
|
||||
$course = $this->retrieve('course', $sampleid);
|
||||
|
||||
$cm = false;
|
||||
if ($sampleorigin === 'course_modules') {
|
||||
$cm = $this->retrieve('course_modules', $sampleid);
|
||||
}
|
||||
// It may not be available, but if it is the indicator checks if completion is enabled for the cm.
|
||||
$cm = $this->retrieve('course_modules', $sampleid);
|
||||
|
||||
$completion = new \completion_info($course);
|
||||
|
||||
|
||||
@@ -268,8 +268,9 @@ class core_course_external extends external_api {
|
||||
|
||||
if (!empty($cm->showdescription) or $cm->modname == 'label') {
|
||||
// We want to use the external format. However from reading get_formatted_content(), $cm->content format is always FORMAT_HTML.
|
||||
$options = array('noclean' => true);
|
||||
list($module['description'], $descriptionformat) = external_format_text($cm->content,
|
||||
FORMAT_HTML, $modcontext->id, $cm->modname, 'intro', $cm->id);
|
||||
FORMAT_HTML, $modcontext->id, $cm->modname, 'intro', $cm->id, $options);
|
||||
}
|
||||
|
||||
//url of the module
|
||||
|
||||
+6
-3
@@ -1404,7 +1404,9 @@ function course_module_update_calendar_events($modulename, $instance = null, $cm
|
||||
if (!isset($cm)) {
|
||||
$cm = get_coursemodule_from_instance($modulename, $instance->id, $instance->course);
|
||||
}
|
||||
course_module_calendar_event_update_process($instance, $cm);
|
||||
if (!empty($cm)) {
|
||||
course_module_calendar_event_update_process($instance, $cm);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1433,8 +1435,9 @@ function course_module_bulk_update_calendar_events($modulename, $courseid = 0) {
|
||||
}
|
||||
|
||||
foreach ($instances as $instance) {
|
||||
$cm = get_coursemodule_from_instance($modulename, $instance->id, $instance->course);
|
||||
course_module_calendar_event_update_process($instance, $cm);
|
||||
if ($cm = get_coursemodule_from_instance($modulename, $instance->id, $instance->course)) {
|
||||
course_module_calendar_event_update_process($instance, $cm);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -25,9 +25,10 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once(__DIR__ . '/../../lib/completionlib.php');
|
||||
require_once(__DIR__ . '/../../completion/criteria/completion_criteria_self.php');
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for core_course indicators.
|
||||
*
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["core/templates","jquery","core/str","core/config","core/notification","core/modal_factory","core/modal_events","core/fragment"],function(a,b,c,d,e,f,g,h){var i=function(a){this.contextid=a.contextid,this.initModal()};return i.prototype.courseid=0,i.prototype.modal=null,i.prototype.initModal=function(){var a=b('.enrolusersbutton.enrol_manual_plugin [type="submit"]'),d=c.get_strings([{key:"enroluserscohorts",component:"enrol_manual"},{key:"enrolusers",component:"enrol_manual"}]),h=d.then(function(a){return a[1]}),i=d.then(function(a){return a[0]});return f.create({type:f.types.SAVE_CANCEL,large:!0,title:h,body:this.getBody()},a).then(function(a){return this.modal=a,this.modal.setSaveButtonText(i),this.modal.getRoot().on(g.hidden,function(){this.modal.setBody(this.getBody())}.bind(this)),this.modal.getRoot().on(g.save,this.submitForm.bind(this)),this.modal.getRoot().on("submit","form",this.submitFormAjax.bind(this)),a}.bind(this)).fail(e.exception)},i.prototype.submitForm=function(a){a.preventDefault(),this.modal.getRoot().find("form").submit()},i.prototype.submitFormAjax=function(a){a.preventDefault();var c=this.modal.getRoot().find("form"),f=c.find(':input[value="_qf__force_multiselect_submission"]');f.each(function(){this.remove()});var g=c.serialize();this.modal.hide();var h={type:"GET",processData:!1,contentType:"application/json"},i=d.wwwroot+"/enrol/manual/ajax.php?"+g;b.ajax(i,h).then(function(a){a.error?e.addNotification({message:a.error,type:"error"}):("undefined"!=typeof window.M.core_formchangechecker&&window.M.core_formchangechecker.reset_form_dirty_state(),window.location.reload())}).fail(e.exception)},i.prototype.getBody=function(){return h.loadFragment("enrol_manual","enrol_users_form",this.contextid,{}).fail(e.exception)},i.prototype.getFooter=function(){return a.render("enrol_manual/enrol_modal_footer",{})},{init:function(a){new i(a)}}});
|
||||
define(["core/templates","jquery","core/str","core/config","core/notification","core/modal_factory","core/modal_events","core/fragment"],function(a,b,c,d,e,f,g,h){var i={COHORTSELECT:"#id_cohortlist",TRIGGERBUTTONS:".enrolusersbutton.enrol_manual_plugin [type='submit']",UNWANTEDHIDDENFIELDS:":input[value='_qf__force_multiselect_submission']"},j=function(a){this.contextid=a.contextid,this.initModal()};return j.prototype.courseid=0,j.prototype.modal=null,j.prototype.initModal=function(){var a=b(i.TRIGGERBUTTONS);b.when(c.get_strings([{key:"enroluserscohorts",component:"enrol_manual"},{key:"enrolusers",component:"enrol_manual"}]),f.create({type:f.types.SAVE_CANCEL,large:!0},a)).then(function(a,c){this.modal=c,c.setTitle(a[1]),c.setSaveButtonText(a[1]),c.getRoot().on(g.save,this.submitForm.bind(this)),c.getRoot().on("submit","form",this.submitFormAjax.bind(this)),c.getRoot().on(g.hidden,function(){c.setBody("")}),c.getRoot().on(g.shown,function(){var d=this.getBody();d.then(function(d){var e=b(d).find(i.COHORTSELECT).length?0:1;c.setSaveButtonText(a[e])}).fail(e.exception),c.setBody(d)}.bind(this))}.bind(this)).fail(e.exception)},j.prototype.submitForm=function(a){a.preventDefault(),this.modal.getRoot().find("form").submit()},j.prototype.submitFormAjax=function(a){a.preventDefault();var c=this.modal.getRoot().find("form"),f=c.find(i.UNWANTEDHIDDENFIELDS);f.each(function(){this.remove()});var g=c.serialize();this.modal.hide();var h={type:"GET",processData:!1,contentType:"application/json"},j=d.wwwroot+"/enrol/manual/ajax.php?"+g;b.ajax(j,h).then(function(a){a.error?e.addNotification({message:a.error,type:"error"}):("undefined"!=typeof window.M.core_formchangechecker&&window.M.core_formchangechecker.reset_form_dirty_state(),window.location.reload())}).fail(e.exception)},j.prototype.getBody=function(){return h.loadFragment("enrol_manual","enrol_users_form",this.contextid,{}).fail(e.exception)},j.prototype.getFooter=function(){return a.render("enrol_manual/enrol_modal_footer",{})},{init:function(a){new j(a)}}});
|
||||
@@ -31,6 +31,13 @@ define(['core/templates',
|
||||
],
|
||||
function(Template, $, Str, Config, Notification, ModalFactory, ModalEvents, Fragment) {
|
||||
|
||||
/** @type {Object} The list of selectors for the quick enrolment modal. */
|
||||
var SELECTORS = {
|
||||
COHORTSELECT: "#id_cohortlist",
|
||||
TRIGGERBUTTONS: ".enrolusersbutton.enrol_manual_plugin [type='submit']",
|
||||
UNWANTEDHIDDENFIELDS: ":input[value='_qf__force_multiselect_submission']"
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@@ -57,41 +64,46 @@ define(['core/templates',
|
||||
* @private
|
||||
*/
|
||||
QuickEnrolment.prototype.initModal = function() {
|
||||
var triggerButtons = $('.enrolusersbutton.enrol_manual_plugin [type="submit"]');
|
||||
var triggerButtons = $(SELECTORS.TRIGGERBUTTONS);
|
||||
|
||||
var stringsPromise = Str.get_strings([
|
||||
{key: 'enroluserscohorts', component: 'enrol_manual'},
|
||||
{key: 'enrolusers', component: 'enrol_manual'},
|
||||
]);
|
||||
|
||||
var titlePromise = stringsPromise.then(function(strings) {
|
||||
return strings[1];
|
||||
});
|
||||
|
||||
var buttonPromise = stringsPromise.then(function(strings) {
|
||||
return strings[0];
|
||||
});
|
||||
|
||||
return ModalFactory.create({
|
||||
type: ModalFactory.types.SAVE_CANCEL,
|
||||
large: true,
|
||||
title: titlePromise,
|
||||
body: this.getBody()
|
||||
}, triggerButtons)
|
||||
.then(function(modal) {
|
||||
$.when(
|
||||
Str.get_strings([
|
||||
{key: 'enroluserscohorts', component: 'enrol_manual'},
|
||||
{key: 'enrolusers', component: 'enrol_manual'},
|
||||
]),
|
||||
ModalFactory.create({
|
||||
type: ModalFactory.types.SAVE_CANCEL,
|
||||
large: true,
|
||||
}, triggerButtons)
|
||||
)
|
||||
.then(function(strings, modal) {
|
||||
this.modal = modal;
|
||||
|
||||
this.modal.setSaveButtonText(buttonPromise);
|
||||
modal.setTitle(strings[1]);
|
||||
modal.setSaveButtonText(strings[1]);
|
||||
|
||||
modal.getRoot().on(ModalEvents.save, this.submitForm.bind(this));
|
||||
modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this));
|
||||
|
||||
// We want the reset the form every time it is opened.
|
||||
this.modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
this.modal.setBody(this.getBody());
|
||||
modal.getRoot().on(ModalEvents.hidden, function() {
|
||||
modal.setBody('');
|
||||
});
|
||||
|
||||
modal.getRoot().on(ModalEvents.shown, function() {
|
||||
var bodyPromise = this.getBody();
|
||||
bodyPromise.then(function(html) {
|
||||
var stringIndex = $(html).find(SELECTORS.COHORTSELECT).length ? 0 : 1;
|
||||
modal.setSaveButtonText(strings[stringIndex]);
|
||||
|
||||
return;
|
||||
})
|
||||
.fail(Notification.exception);
|
||||
|
||||
modal.setBody(bodyPromise);
|
||||
}.bind(this));
|
||||
|
||||
this.modal.getRoot().on(ModalEvents.save, this.submitForm.bind(this));
|
||||
this.modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this));
|
||||
|
||||
return modal;
|
||||
return;
|
||||
}.bind(this))
|
||||
.fail(Notification.exception);
|
||||
};
|
||||
@@ -123,8 +135,8 @@ define(['core/templates',
|
||||
|
||||
// Before send the data through AJAX, we need to parse and remove some unwanted hidden fields.
|
||||
// This hidden fields are added automatically by mforms and when it reaches the AJAX we get an error.
|
||||
var hidden = form.find(':input[value="_qf__force_multiselect_submission"]');
|
||||
hidden.each(function () {
|
||||
var hidden = form.find(SELECTORS.UNWANTEDHIDDENFIELDS);
|
||||
hidden.each(function() {
|
||||
this.remove();
|
||||
});
|
||||
|
||||
|
||||
@@ -102,8 +102,24 @@ class enrol_manual_enrol_users_form extends moodleform {
|
||||
);
|
||||
$mform->addElement('autocomplete', 'userlist', get_string('selectusers', 'enrol_manual'), array(), $options);
|
||||
|
||||
$options = ['contextid' => $context->id, 'multiple' => true];
|
||||
$mform->addElement('cohort', 'cohortlist', get_string('selectcohorts', 'enrol_manual'), $options);
|
||||
// Confirm the user can search for cohorts before displaying select.
|
||||
if (has_capability('moodle/cohort:manage', $context) || has_capability('moodle/cohort:view', $context)) {
|
||||
// Check to ensure there is at least one visible cohort before displaying the select box.
|
||||
// Ideally it would be better to call external_api::call_external_function('core_cohort_search_cohorts')
|
||||
// (which is used to populate the select box) instead of duplicating logic but there is an issue with globals
|
||||
// being borked (in this case $PAGE) when combining the usage of fragments and call_external_function().
|
||||
require_once($CFG->dirroot . '/cohort/lib.php');
|
||||
$availablecohorts = cohort_get_cohorts($context->id, 0, 1, '');
|
||||
$availablecohorts = $availablecohorts['cohorts'];
|
||||
if (!($context instanceof context_system)) {
|
||||
$availablecohorts = array_merge($availablecohorts,
|
||||
cohort_get_available_cohorts($context, COHORT_ALL, 0, 1, ''));
|
||||
}
|
||||
if (!empty($availablecohorts)) {
|
||||
$options = ['contextid' => $context->id, 'multiple' => true];
|
||||
$mform->addElement('cohort', 'cohortlist', get_string('selectcohorts', 'enrol_manual'), $options);
|
||||
}
|
||||
}
|
||||
|
||||
$roles = get_assignable_roles($context);
|
||||
$mform->addElement('select', 'roletoassign', get_string('assignrole', 'enrol_manual'), $roles);
|
||||
|
||||
@@ -100,7 +100,8 @@ class behat_enrol extends behat_base {
|
||||
// We have a div here, not a tr.
|
||||
$this->execute('behat_forms::i_set_the_field_to', array(get_string('selectusers', 'enrol_manual'), $userfullname));
|
||||
|
||||
$this->execute("behat_forms::press_button", get_string('enroluserscohorts', 'enrol_manual'));
|
||||
$enrolusers = get_string('enrolusers', 'enrol_manual');
|
||||
$this->execute('behat_general::i_click_on_in_the', [$enrolusers, 'button', $enrolusers, 'dialogue']);
|
||||
|
||||
} else {
|
||||
$this->execute('behat_forms::i_set_the_field_to', array(get_string('assignrole', 'role'), $rolename));
|
||||
|
||||
@@ -169,5 +169,33 @@ MathJax.Hub.Config({
|
||||
upgrade_plugin_savepoint(true, 2017101200, 'filter', 'mathjaxloader');
|
||||
}
|
||||
|
||||
if ($oldversion < 2017102000) {
|
||||
// Re-add Accessible.js (we should not have removed it).
|
||||
$previousdefault = '
|
||||
MathJax.Hub.Config({
|
||||
config: ["default.js", "MMLorHTML.js", "Safe.js"],
|
||||
errorSettings: { message: ["!"] },
|
||||
skipStartupTypeset: true,
|
||||
messageStyle: "none"
|
||||
});
|
||||
';
|
||||
$newdefault = '
|
||||
MathJax.Hub.Config({
|
||||
config: ["Accessible.js", "Safe.js"],
|
||||
errorSettings: { message: ["!"] },
|
||||
skipStartupTypeset: true,
|
||||
messageStyle: "none"
|
||||
});
|
||||
';
|
||||
|
||||
$mathjaxconfig = get_config('filter_mathjaxloader', 'mathjaxconfig');
|
||||
|
||||
if (empty($mathjaxconfig) || filter_mathjaxloader_upgrade_mathjaxconfig_equal($mathjaxconfig, $previousdefault)) {
|
||||
set_config('mathjaxconfig', $newdefault, 'filter_mathjaxloader');
|
||||
}
|
||||
|
||||
upgrade_plugin_savepoint(true, 2017102000, 'filter', 'mathjaxloader');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,3 @@ Upgrading the default MathJax version
|
||||
3. Check and eventually update the list of language mappings in filter.php.
|
||||
Also see the unit test for the language mappings.
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
* The MathJax 2.7.2 seems to have a bug causing the accessibility extensions
|
||||
fail in web apps using RequireJS (such as Moodle). We had to stop using the
|
||||
Accessible.js config for that reason. See MDL-60209 for details.
|
||||
|
||||
@@ -45,7 +45,7 @@ if ($ADMIN->fulltree) {
|
||||
|
||||
$default = '
|
||||
MathJax.Hub.Config({
|
||||
config: ["default.js", "MMLorHTML.js", "Safe.js"],
|
||||
config: ["Accessible.js", "Safe.js"],
|
||||
errorSettings: { message: ["!"] },
|
||||
skipStartupTypeset: true,
|
||||
messageStyle: "none"
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2017101200;
|
||||
$plugin->version = 2017102000;
|
||||
$plugin->requires = 2017050500; // Requires this Moodle version.
|
||||
$plugin->component= 'filter_mathjaxloader';
|
||||
|
||||
@@ -489,7 +489,7 @@ class grading_manager {
|
||||
* Returns the given method's controller in the gradable area
|
||||
*
|
||||
* @param string $method the method name, eg 'rubric' (must be available)
|
||||
* @return grading_controller
|
||||
* @return gradingform_controller
|
||||
*/
|
||||
public function get_controller($method) {
|
||||
global $CFG, $DB;
|
||||
@@ -534,7 +534,7 @@ class grading_manager {
|
||||
/**
|
||||
* Returns the controller for the active method if it is available
|
||||
*
|
||||
* @return null|grading_controller
|
||||
* @return null|gradingform_controller
|
||||
*/
|
||||
public function get_active_controller() {
|
||||
if ($gradingmethod = $this->get_active_method()) {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Group details page.
|
||||
*
|
||||
* @package core_group
|
||||
* @copyright 2017 Adrian Greeve <adrian@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_group\output;
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
use templatable;
|
||||
use context_course;
|
||||
use moodle_url;
|
||||
|
||||
/**
|
||||
* Group details page class.
|
||||
*
|
||||
* @package core_group
|
||||
* @copyright 2017 Adrian Greeve <adrian@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class group_details implements renderable, templatable {
|
||||
|
||||
/** @var stdClass $group An object with the group information. */
|
||||
protected $group;
|
||||
|
||||
/**
|
||||
* group_details constructor.
|
||||
*
|
||||
* @param int $groupid Group ID to show details of.
|
||||
*/
|
||||
public function __construct($groupid) {
|
||||
$this->group = groups_get_group($groupid, '*', MUST_EXIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the data.
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return stdClass
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
|
||||
if (!empty($this->group->description) || (!empty($this->group->picture) && empty($this->group->hidepicture))) {
|
||||
$context = context_course::instance($this->group->courseid);
|
||||
$description = file_rewrite_pluginfile_urls($this->group->description,
|
||||
'pluginfile.php',
|
||||
$context->id,
|
||||
'group',
|
||||
'description',
|
||||
$this->group->id);
|
||||
|
||||
$descriptionformat = $this->group->descriptionformat ?? FORMAT_MOODLE;
|
||||
$options = [
|
||||
'overflowdiv' => true,
|
||||
'context' => $context
|
||||
];
|
||||
|
||||
$data = new stdClass();
|
||||
$data->name = format_string($this->group->name, true, ['context' => $context]);
|
||||
$data->pictureurl = get_group_picture_url($this->group, $this->group->courseid, true);
|
||||
$data->description = format_text($description, $descriptionformat, $options);
|
||||
|
||||
if (has_capability('moodle/course:managegroups', $context)) {
|
||||
$url = new moodle_url('/group/group.php', ['id' => $this->group->id, 'courseid' => $this->group->courseid]);
|
||||
$data->editurl = $url->out(false);
|
||||
}
|
||||
|
||||
return $data;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,4 +47,15 @@ class renderer extends plugin_renderer_base {
|
||||
$data = $page->export_for_template($this);
|
||||
return parent::render_from_template('core_group/index', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defer to template.
|
||||
*
|
||||
* @param group_details $page Group details page object.
|
||||
* @return string HTML to render the group details.
|
||||
*/
|
||||
public function group_details(group_details $page) {
|
||||
$data = $page->export_for_template($this);
|
||||
return parent::render_from_template('core_group/group_details', $data);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-28
@@ -102,36 +102,11 @@ echo $OUTPUT->heading(get_string('adduserstogroup', 'group').": $groupname", 3);
|
||||
// Store the rows we want to display in the group info.
|
||||
$groupinforow = array();
|
||||
|
||||
// Check if there is a picture to display.
|
||||
if (!empty($group->picture)) {
|
||||
$picturecell = new html_table_cell();
|
||||
$picturecell->attributes['class'] = 'left side picture';
|
||||
$picturecell->text = print_group_picture($group, $course->id, true, true, false);
|
||||
$groupinforow[] = $picturecell;
|
||||
}
|
||||
|
||||
// Check if there is a description to display.
|
||||
$group->description = file_rewrite_pluginfile_urls($group->description, 'pluginfile.php', $context->id, 'group', 'description', $group->id);
|
||||
if (!empty($group->description)) {
|
||||
if (!isset($group->descriptionformat)) {
|
||||
$group->descriptionformat = FORMAT_MOODLE;
|
||||
}
|
||||
|
||||
$options = new stdClass;
|
||||
$options->overflowdiv = true;
|
||||
|
||||
$contentcell = new html_table_cell();
|
||||
$contentcell->attributes['class'] = 'content';
|
||||
$contentcell->text = format_text($group->description, $group->descriptionformat, $options);
|
||||
$groupinforow[] = $contentcell;
|
||||
}
|
||||
|
||||
// Check if we have something to show.
|
||||
if (!empty($groupinforow)) {
|
||||
$groupinfotable = new html_table();
|
||||
$groupinfotable->attributes['class'] = 'groupinfobox';
|
||||
$groupinfotable->data[] = new html_table_row($groupinforow);
|
||||
echo html_writer::table($groupinfotable);
|
||||
$grouprenderer = $PAGE->get_renderer('core_group');
|
||||
$groupdetailpage = new \core_group\output\group_details($groupid);
|
||||
echo $grouprenderer->group_details($groupdetailpage);
|
||||
}
|
||||
|
||||
/// Print the editing form
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core_group/group_details
|
||||
|
||||
Template for the Groups page.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Context variables required for this template:
|
||||
* name string Group Name
|
||||
* pictureurl string Group image url
|
||||
* description string Group description
|
||||
* edit string edit link to edit the group
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"name": "Group Name",
|
||||
"pictureurl": "https://raw.githubusercontent.com/moodle/moodle/master/pix/g/f1.png",
|
||||
"description": "This is the description for Group Name",
|
||||
"editurl": "http://www.moodle.org"
|
||||
}
|
||||
}}
|
||||
{{#name}}
|
||||
<div class="groupinfobox container-fluid p-x-1 p-y-1">
|
||||
{{#pictureurl}}
|
||||
<div class="group-image"><img class="grouppicture" src="{{{pictureurl}}}" alt="{{{name}}}" title="{{{name}}}"/></div>
|
||||
{{/pictureurl}}
|
||||
{{#editurl}}
|
||||
<div class="group-edit"><a href="{{editurl}}">{{#pix}}t/edit, core, {{#str}}editgroupprofile{{/str}}{{/pix}}</a></div>
|
||||
{{/editurl}}
|
||||
<h3 class="">{{{name}}}</h3>
|
||||
<div class="group-description">{{{description}}}</div>
|
||||
</div>
|
||||
{{/name}}
|
||||
@@ -0,0 +1,104 @@
|
||||
@core @core_group
|
||||
Feature: The description of a group can be viewed by students and teachers
|
||||
In order to view the description of a group
|
||||
As a teacher
|
||||
I need to create groups and add descriptions to them.
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | format |
|
||||
| Course 1 | C1 | topics |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
| student2 | Student | 2 | student2@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
|
||||
@javascript
|
||||
Scenario: A student can see the group description when visible groups are set. Teachers can see group details.
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| Group mode | Visible groups |
|
||||
And I press "Save and display"
|
||||
And I navigate to "Users > Groups" in current page administration
|
||||
And I press "Create group"
|
||||
And I set the following fields to these values:
|
||||
| Group name | Group A |
|
||||
| Group description | Description for Group A |
|
||||
And I press "Save changes"
|
||||
And I press "Create group"
|
||||
And I set the following fields to these values:
|
||||
| Group name | Group B |
|
||||
And I press "Save changes"
|
||||
And I add "Student 1 (student1@example.com)" user to "Group A" group members
|
||||
And I add "Student 2 (student2@example.com)" user to "Group B" group members
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to course participants
|
||||
And I open the autocomplete suggestions list
|
||||
And I click on "Group: Group A" item in the autocomplete list
|
||||
And I should see "Description for Group A"
|
||||
And ".groupinfobox" "css_element" should exist
|
||||
And I should see "Description for Group A"
|
||||
And I click on "Group: Group A" "autocomplete_selection"
|
||||
And I open the autocomplete suggestions list
|
||||
And I click on "Group: Group B" item in the autocomplete list
|
||||
And ".groupinfobox" "css_element" should not exist
|
||||
And I log out
|
||||
When I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to course participants
|
||||
Then I should see "Description for Group A"
|
||||
And I log out
|
||||
And I log in as "student2"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to course participants
|
||||
And ".groupinfobox" "css_element" should not exist
|
||||
|
||||
@javascript
|
||||
Scenario: A student can not see the group description when separate groups are set. Teachers can see group details.
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Edit settings" in current page administration
|
||||
And I set the following fields to these values:
|
||||
| Group mode | Separate groups |
|
||||
And I press "Save and display"
|
||||
And I navigate to "Users > Groups" in current page administration
|
||||
And I press "Create group"
|
||||
And I set the following fields to these values:
|
||||
| Group name | Group A |
|
||||
| Group description | Description for Group A |
|
||||
And I press "Save changes"
|
||||
And I press "Create group"
|
||||
And I set the following fields to these values:
|
||||
| Group name | Group B |
|
||||
And I press "Save changes"
|
||||
And I add "Student 1 (student1@example.com)" user to "Group A" group members
|
||||
And I add "Student 2 (student2@example.com)" user to "Group B" group members
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to course participants
|
||||
And I open the autocomplete suggestions list
|
||||
And I click on "Group: Group A" item in the autocomplete list
|
||||
And I should see "Description for Group A"
|
||||
And ".groupinfobox" "css_element" should exist
|
||||
And I click on "Group: Group A" "autocomplete_selection"
|
||||
And I open the autocomplete suggestions list
|
||||
And I click on "Group: Group B" item in the autocomplete list
|
||||
And ".groupinfobox" "css_element" should not exist
|
||||
And I log out
|
||||
When I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to course participants
|
||||
Then I should not see "Description for Group A"
|
||||
And ".groupinfobox" "css_element" should not exist
|
||||
And I log out
|
||||
And I log in as "student2"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to course participants
|
||||
And ".groupinfobox" "css_element" should not exist
|
||||
@@ -36,7 +36,7 @@ $string['chooselanguagehead'] = 'Sprache wählen';
|
||||
$string['chooselanguagesub'] = 'Wählen Sie eine Sprache, die Sie während der Installation verwenden wollen. Die ausgewählte Sprache wird nach der Installation als Standardsprache der Instanz benutzt, aber Sie dürfen die Sprache jederzeit ändern.';
|
||||
$string['clialreadyconfigured'] = 'Die Datei config.php existiert bereits. Bitte benutzen Sie admin/cli/install_database.php, wenn Sie diese Site installieren möchten.';
|
||||
$string['clialreadyinstalled'] = 'Die Datei config.php existiert bereits. Bitte benutzen Sie admin/cli/install_database.php, wenn Sie diese Site aktualisieren möchten.';
|
||||
$string['cliinstallheader'] = 'Installation von Moodle {$a} über die Kommandozeile';
|
||||
$string['cliinstallheader'] = 'Installation von Moodle {$a} über die Befehlszeile';
|
||||
$string['databasehost'] = 'Datenbank-Server';
|
||||
$string['databasename'] = 'Datenbank-Name';
|
||||
$string['databasetypehead'] = 'Datenbank-Treiber wählen';
|
||||
|
||||
@@ -37,6 +37,9 @@ $string['cliincorrectvalueerror'] = 'Грешка, погрешна вредно
|
||||
$string['cliincorrectvalueretry'] = 'Неточни вредност, ве молиме обидете се повторно';
|
||||
$string['clitypevalue'] = 'впишете вредност';
|
||||
$string['clitypevaluedefault'] = 'впишете вредност, притиснете Enter за да го користите стандардната вредност ({$a})';
|
||||
$string['cliunknowoption'] = 'Непрепознаена опција:
|
||||
{$a}
|
||||
Ве молиме употребете --help опција.';
|
||||
$string['cliyesnoprompt'] = 'впишете y (значи да) или n (значи не)';
|
||||
$string['environmentrequireinstall'] = 'потребно е да се инсталира/овозможи';
|
||||
$string['environmentrequireversion'] = 'верзијата {$a->needed} е потребна, а вие извршувате {$a->current}';
|
||||
|
||||
@@ -72,5 +72,11 @@ $string['welcomep20'] = 'Vedeți această pagină deoarece ați instalat și lan
|
||||
$string['welcomep30'] = 'Lansarea <strong>{$a->installername}</strong> include aplicațiile
|
||||
pentru a crea un mediu în care <strong>Moodle</strong> va funcționa, și anume:';
|
||||
$string['welcomep40'] = 'Pachetul include și <strong>Moodle {$a->moodlerelease} ({$a->moodleversion})</strong>.';
|
||||
$string['welcomep50'] = 'Utilizarea tuturor aplicațiilor din acest pachet este determinată de respectivele lor
|
||||
licențe. Pachetul complet <strong>{$a->installername}</strong> este
|
||||
<a href="http://www.opensource.org/docs/definition_plain.html">open source</a> și este distribuit sub licența <a href="http://www.gnu.org/copyleft/gpl.html">GPL</a>.';
|
||||
$string['welcomep60'] = 'Următoarele pagini vă oferă pași ușor de urmat pentru a
|
||||
configura și seta <strong>Moodle</strong> în computerul dumneavoastră. Puteți accepta setările implicite
|
||||
sau, opțional, să le modificați pentru a corespunde nevoilor dumneavoastră.';
|
||||
$string['welcomep70'] = 'Click pe butonul "Next" de mai jos pentru a continua setarea <strong>Moodle</strong>.';
|
||||
$string['wwwroot'] = 'Adresă Web';
|
||||
|
||||
+4
-10
@@ -581,7 +581,7 @@ $string['gravatardefaulturl_help'] = 'Gravatar needs a default image to display
|
||||
$string['gradeexport'] = 'Primary grade export methods';
|
||||
$string['guestroleid'] = 'Role for guest';
|
||||
$string['guestroleid_help'] = 'This role is automatically assigned to the guest user. It is also temporarily assigned to not enrolled users that enter the course via guest enrolment plugin.';
|
||||
$string['helpadminseesall'] = 'Do admins see all calendar events or just those that apply to themselves?';
|
||||
$string['helpadminseesall'] = 'In the site calendar, do admins see and filter events from all course calendars or just those from courses they are enrolled in? Regardless of the chosen option, admins will always be able to manage events for each course calendar by navigating to the course first, and then accessing the course calendar directly.';
|
||||
$string['helpcalendarcustomexport'] = 'Enable custom date range export option in calendar exports. Calendar exports must be enabled before this is effective.';
|
||||
$string['helpexportlookahead'] = 'How many days in the future does the calendar look for events during export for the custom export option?';
|
||||
$string['helpexportlookback'] = 'How many days in the past does the calendar look for events during export for the custom export option?';
|
||||
@@ -616,6 +616,7 @@ $string['installhijacked'] = 'Installation must be finished from the original IP
|
||||
$string['installsessionerror'] = 'Can not initialise PHP session, please verify that your browser accepts cookies.';
|
||||
$string['intlrecommended'] = 'Intl extension is used to improve internationalization support, such as locale aware sorting.';
|
||||
$string['intlrequired'] = 'Intl extension is required to improve internationalization support, such as locale aware sorting and international domain names.';
|
||||
$string['invalidforgottenpasswordurl'] = 'The forgotten password URL is not a valid URL.';
|
||||
$string['invalidsection'] = 'Invalid section.';
|
||||
$string['invaliduserchangeme'] = 'Username "changeme" is reserved -- you cannot create an account with it.';
|
||||
$string['ipblocked'] = 'This site is not available currently.';
|
||||
@@ -939,15 +940,7 @@ $string['quizattemptsupgradedmessage'] = 'In Moodle 2.1 there was a major upgrad
|
||||
$string['recaptchaprivatekey'] = 'ReCAPTCHA secret key';
|
||||
$string['recaptchapublickey'] = 'ReCAPTCHA site key';
|
||||
$string['register'] = 'Register your site';
|
||||
$string['registermoodlenet'] = 'We\'d love to stay in touch for important things for your Moodle site!
|
||||
|
||||
By registering,
|
||||
|
||||
* You are contributing to our collective knowledge about the users of Moodle which helps us improve Moodle and all our community services
|
||||
* You’ll be one of the first to find out about important notifications such as security alerts and new Moodle releases.
|
||||
* You can access and activate mobile push notifications from your Moodle site through our free [Moodle Mobile app](https://download.moodle.org/mobile/)
|
||||
* Optionally, your site can be included as a proud member and supporter of the Moodle community on the [list of registered sites](https://moodle.net/stats).
|
||||
';
|
||||
$string['registermoodlenet'] = '<p>We\'d love to stay in touch for important things for your Moodle site!</p><p>By registering,</p><ul><li>You are contributing to our collective knowledge about the users of Moodle which helps us improve Moodle and all our community services.</li><li>You’ll be one of the first to find out about important notifications such as security alerts and new Moodle releases.</li><li>You can access and activate mobile push notifications from your Moodle site through our free <a href="https://download.moodle.org/mobile/">Moodle Mobile app</a>.</li><li>Optionally, your site can be included as a proud member and supporter of the Moodle community on the <a href="https://moodle.net/stats">list of registered sites</a>.</li></ul>';
|
||||
$string['registermoodleorg'] = 'When you register your site';
|
||||
$string['registermoodleorgli1'] = 'You are added to a low-volume mailing list for important notifications such as security alerts and new releases of Moodle.';
|
||||
$string['registermoodleorgli2'] = 'Statistics about your site will be added to the {$a} of the worldwide Moodle community.';
|
||||
@@ -1175,6 +1168,7 @@ $string['unsupporteddbstorageengine'] = 'The database storage engine being used
|
||||
$string['unsupporteddbtablerowformat'] = 'Your database has tables using Antelope as the file format. You are recommended to convert the tables to the Barracuda file format. See the documentation <a href="https://docs.moodle.org/en/cli">Administration via command line</a> for details of a tool for converting InnoDB tables to Barracuda.';
|
||||
$string['unsupportedphpversion7'] = 'PHP version 7 is not supported.';
|
||||
$string['unsupportedphpversion71'] = 'PHP version 7.1 is not supported.';
|
||||
$string['unsupportedphpversion72'] = 'PHP version 7.2 is not supported.';
|
||||
$string['unsuspenduser'] = 'Activate user account';
|
||||
$string['updateaccounts'] = 'Update existing accounts';
|
||||
$string['updatecomponent'] = 'Update component';
|
||||
|
||||
@@ -108,6 +108,7 @@ $string['eventrepeat'] = 'Repeats';
|
||||
$string['eventsall'] = 'All events';
|
||||
$string['eventsdeleted'] = 'Events deleted';
|
||||
$string['eventsimported'] = 'Events imported: {$a}';
|
||||
$string['eventsource'] = 'Event source';
|
||||
$string['eventsupdated'] = 'Events updated: {$a}';
|
||||
$string['eventsfor'] = '{$a} events';
|
||||
$string['eventskey'] = 'Events key';
|
||||
|
||||
@@ -159,3 +159,7 @@ privacy,core_hub
|
||||
privacy_help,core_hub
|
||||
configloginhttps,core_admin
|
||||
loginhttps,core_admin
|
||||
groupaddnewnote,core_notes
|
||||
selectnotestate,core_notes
|
||||
extendenrol,core
|
||||
groupextendenrol,core
|
||||
|
||||
@@ -111,6 +111,10 @@ $string['send'] = 'Send';
|
||||
$string['sendingvia'] = 'Sending "{$a->provider}" via "{$a->processor}"';
|
||||
$string['sendingviawhen'] = 'Sending "{$a->provider}" via "{$a->processor}" when {$a->state}';
|
||||
$string['sendmessage'] = 'Send message';
|
||||
$string['sendbulkmessage'] = 'Send message to {$a} people';
|
||||
$string['sendbulkmessagesingle'] = 'Send message to 1 person';
|
||||
$string['sendbulkmessagesent'] = 'Message sent to {$a} people';
|
||||
$string['sendbulkmessagesentsingle'] = 'Message sent to 1 person';
|
||||
$string['sendmessageto'] = 'Send message to {$a}';
|
||||
$string['sendmessagetopopup'] = 'Send message to {$a} - new window';
|
||||
$string['settings'] = 'Settings';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user