# process (which uses our internal CI system) this file is here for the benefit
# of community developers git clones - see MDL-51458.
-sudo: false
+sudo: required
# We currently disable Travis notifications entirely until https://github.com/travis-ci/travis-ci/issues/4976
# is fixed.
php:
# We only run the highest and lowest supported versions to reduce the load on travis-ci.org.
- 7.1
- - 5.6
+ - 7.0
addons:
postgresql: "9.3"
+ packages:
+ - mysql-server-5.6
+ - mysql-client-core-5.6
+ - mysql-client-5.6
services:
- redis-server
# Exclude it on all versions except for 7.1
- env: DB=mysqli TASK=PHPUNIT
- php: 5.6
-
- # Moodle 2.7 is not compatible with PHP 7 for the upgrade test.
- - env: DB=pgsql TASK=UPGRADE
- php: 7.1
+ php: 7.0
cache:
directories:
- $HOME/.npm
install:
+ - sudo apt-get -y install haveged
+ - sudo service haveged start
+ - >
+ if [ "$DB" = 'mysqli' ];
+ then
+ sudo mkdir /mnt/ramdisk
+ sudo mount -t tmpfs -o size=1024m tmpfs /mnt/ramdisk
+ sudo stop mysql
+ sudo mv /var/lib/mysql /mnt/ramdisk
+ sudo ln -s /mnt/ramdisk/mysql /var/lib/mysql
+ sudo start mysql
+ fi
+ - >
+ if [ "$DB" = 'pgsql' ];
+ then
+ sudo mkdir /mnt/ramdisk
+ sudo mount -t tmpfs -o size=1024m tmpfs /mnt/ramdisk
+ sudo service postgresql stop
+ sudo mv /var/lib/postgresql /mnt/ramdisk
+ sudo ln -s /mnt/ramdisk/postgresql /var/lib/postgresql
+ sudo service postgresql start 9.3
+ fi
- >
if [ "$TASK" = 'PHPUNIT' ];
then
# We need the official upstream.
git remote add upstream https://github.com/moodle/moodle.git;
- # Checkout 27 STABLE branch.
- git fetch upstream MOODLE_27_STABLE;
- git checkout MOODLE_27_STABLE;
+ # Checkout 30 STABLE branch (the first version compatible with PHP 7.x)
+ git fetch upstream MOODLE_30_STABLE;
+ git checkout MOODLE_30_STABLE;
# Perform the upgrade
php admin/cli/install_database.php --agree-license --adminpass=Password --adminemail=admin@example.com --fullname="Upgrade test" --shortname=Upgrade;
$savebutton = false;
$outputhtml = '';
foreach ($settingspage->children as $childpage) {
- if ($childpage->is_hidden()) {
+ if ($childpage->is_hidden() || !$childpage->check_access()) {
continue;
}
if ($childpage instanceof admin_externalpage) {
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Build and store theme CSS.
+ *
+ * @package core
+ * @subpackage cli
+ * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require(__DIR__.'/../../config.php');
+require_once("$CFG->libdir/clilib.php");
+require_once("$CFG->libdir/csslib.php");
+require_once("$CFG->libdir/outputlib.php");
+
+$longparams = [
+ 'themes' => null,
+ 'direction' => null,
+ 'help' => false,
+ 'verbose' => false
+];
+
+$shortmappings = [
+ 't' => 'themes',
+ 'd' => 'direction',
+ 'h' => 'help',
+ 'v' => 'verbose'
+];
+
+// Get CLI params.
+list($options, $unrecognized) = cli_get_params($longparams, $shortmappings);
+
+if ($unrecognized) {
+ $unrecognized = implode("\n ", $unrecognized);
+ cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
+}
+
+if ($options['help']) {
+ echo
+"Compile the CSS for one or more installed themes.
+Existing CSS caches will replaced.
+By default all themes will be recompiled unless otherwise specified.
+
+Options:
+-t, --themes A comma separated list of themes to be compiled
+-d, --direction Only compile a single direction (either ltr or rtl)
+-v, --verbose Print info comments to stdout
+-h, --help Print out this help
+
+Example:
+\$ sudo -u www-data /usr/bin/php admin/cli/build_theme_css.php --themes=boost --direction=ltr
+";
+
+ die;
+}
+
+if (empty($options['verbose'])) {
+ $trace = new null_progress_trace();
+} else {
+ $trace = new text_progress_trace();
+}
+
+cli_heading('Build theme css');
+
+// Determine which themes we need to build.
+$themenames = [];
+if (is_null($options['themes'])) {
+ $trace->output('No themes specified. Finding all installed themes.');
+ $themenames = array_keys(core_component::get_plugin_list('theme'));
+} else {
+ if (is_string($options['themes'])) {
+ $themenames = explode(',', $options['themes']);
+ } else {
+ cli_error('--themes must be a comma separated list of theme names');
+ }
+}
+
+$trace->output('Checking that each theme is correctly installed...');
+$themeconfigs = [];
+foreach ($themenames as $themename) {
+ if (is_null(theme_get_config_file_path($themename))) {
+ cli_error("Unable to find theme config for {$themename}");
+ }
+
+ // Load the config for the theme.
+ $themeconfigs[] = theme_config::load($themename);
+}
+
+$directions = ['ltr', 'rtl'];
+
+if (!is_null($options['direction'])) {
+ if (!in_array($options['direction'], $directions)) {
+ cli_error("--direction must be either ltr or rtl");
+ }
+
+ $directions = [$options['direction']];
+}
+
+$trace->output('Building CSS for themes: ' . implode(', ', $themenames));
+theme_build_css_for_themes($themeconfigs, $directions);
+
+exit(0);
define('IGNORE_COMPONENT_CACHE', true);
-// Check that PHP is of a sufficient version
-if (version_compare(phpversion(), "5.6.5") < 0) {
- $phpversion = phpversion();
- // do NOT localise - lang strings would not work here and we CAN NOT move it after installib
- fwrite(STDERR, "Moodle 3.2 or later requires at least PHP 5.6.5 (currently using version $phpversion).\n");
- fwrite(STDERR, "Please upgrade your server software or install older Moodle version.\n");
- exit(1);
-}
+// Check that PHP is of a sufficient version as soon as possible.
+require_once(__DIR__.'/../../lib/phpminimumversionlib.php');
+moodle_require_minimum_php_version();
// set up configuration
global $CFG;
\$sudo -u www-data /usr/bin/php admin/cli/install_database.php --lang=cs --adminpass=soMePass123 --agree-license
";
-// Check that PHP is of a sufficient version
-if (version_compare(phpversion(), "5.6.5") < 0) {
- $phpversion = phpversion();
- // do NOT localise - lang strings would not work here and we CAN NOT move it after installib
- fwrite(STDERR, "Moodle 3.2 or later requires at least PHP 5.6.5 (currently using version $phpversion).\n");
- fwrite(STDERR, "Please upgrade your server software or install older Moodle version.\n");
- exit(1);
-}
+// Check that PHP is of a sufficient version as soon as possible.
+require_once(__DIR__.'/../../lib/phpminimumversionlib.php');
+moodle_require_minimum_php_version();
// Nothing to do if config.php does not exist
$configfile = __DIR__.'/../../config.php';
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
+ <MOODLE version="3.4" requires="3.0">
+ <UNICODE level="required">
+ <FEEDBACK>
+ <ON_ERROR message="unicoderequired" />
+ </FEEDBACK>
+ </UNICODE>
+ <DATABASE level="required">
+ <VENDOR name="mariadb" version="5.5.31" />
+ <VENDOR name="mysql" version="5.5.31" />
+ <VENDOR name="postgres" version="9.3" />
+ <VENDOR name="mssql" version="10.0" />
+ <VENDOR name="oracle" version="10.2" />
+ </DATABASE>
+ <PHP version="7.0.0" level="required">
+ </PHP>
+ <PCREUNICODE level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="pcreunicodewarning" />
+ </FEEDBACK>
+ </PCREUNICODE>
+ <PHP_EXTENSIONS>
+ <PHP_EXTENSION name="iconv" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="iconvrequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="mbstring" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="mbstringrecommended" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="curl" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="curlrequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="openssl" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="opensslrequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="tokenizer" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="tokenizerrecommended" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="xmlrpc" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="xmlrpcrecommended" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="soap" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="soaprecommended" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="ctype" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="ctyperequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="zip" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="ziprequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="zlib" level="required">
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="gd" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="gdrequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="simplexml" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="simplexmlrequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="spl" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="splrequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="pcre" level="required">
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="dom" level="required">
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="xml" level="required">
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="xmlreader" level="required">
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="intl" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="intlrequired" />
+ </FEEDBACK>
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="json" level="required">
+ </PHP_EXTENSION>
+ <PHP_EXTENSION name="hash" level="required"/>
+ <PHP_EXTENSION name="fileinfo" level="required"/>
+ </PHP_EXTENSIONS>
+ <PHP_SETTINGS>
+ <PHP_SETTING name="memory_limit" value="96M" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="settingmemorylimit" />
+ </FEEDBACK>
+ </PHP_SETTING>
+ <PHP_SETTING name="file_uploads" value="1" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="settingfileuploads" />
+ </FEEDBACK>
+ </PHP_SETTING>
+ <PHP_SETTING name="opcache.enable" value="1" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="opcacherecommended" />
+ </FEEDBACK>
+ </PHP_SETTING>
+ </PHP_SETTINGS>
+ <CUSTOM_CHECKS>
+ <CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="unsupporteddbstorageengine" />
+ </FEEDBACK>
+ </CUSTOM_CHECK>
+ <CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="quizattemptsupgradedmessage" />
+ </FEEDBACK>
+ </CUSTOM_CHECK>
+ <CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="slashargumentswarning" />
+ </FEEDBACK>
+ </CUSTOM_CHECK>
+ <CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="unsupporteddbtablerowformat" />
+ </FEEDBACK>
+ </CUSTOM_CHECK>
+ <CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="unoconvwarning" />
+ </FEEDBACK>
+ </CUSTOM_CHECK>
+ <CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="libcurlwarning" />
+ </FEEDBACK>
+ </CUSTOM_CHECK>
+ <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="unsupporteddbfileformat" />
+ </FEEDBACK>
+ </CUSTOM_CHECK>
+ <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="unsupporteddbfilepertable" />
+ </FEEDBACK>
+ </CUSTOM_CHECK>
+ <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
+ <FEEDBACK>
+ <ON_ERROR message="unsupporteddblargeprefix" />
+ </FEEDBACK>
+ </CUSTOM_CHECK>
+ <CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="ishttpswarning" />
+ </FEEDBACK>
+ </CUSTOM_CHECK>
+ <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
+ <FEEDBACK>
+ <ON_CHECK message="incompleteunicodesupport" />
+ </FEEDBACK>
+ </CUSTOM_CHECK>
+ </CUSTOM_CHECKS>
+ </MOODLE>
</COMPATIBILITY_MATRIX>
die();
}
-// Check that PHP is of a sufficient version as soon as possible
-if (version_compare(phpversion(), '5.6.5') < 0) {
- $phpversion = phpversion();
- // do NOT localise - lang strings would not work here and we CAN NOT move it to later place
- echo "Moodle 3.2 or later requires at least PHP 5.6.5 (currently using version $phpversion).<br />";
- echo "Please upgrade your server software or install older Moodle version.";
- die();
-}
+// Check that PHP is of a sufficient version as soon as possible.
+require_once(__DIR__.'/../lib/phpminimumversionlib.php');
+moodle_require_minimum_php_version();
// make sure iconv is available and actually works
if (!function_exists('iconv')) {
$temp->add(new admin_setting_configcheckbox('forceloginforprofiles', new lang_string('forceloginforprofiles', 'admin'), new lang_string('configforceloginforprofiles', 'admin'), 1));
$temp->add(new admin_setting_configcheckbox('forceloginforprofileimage', new lang_string('forceloginforprofileimage', 'admin'), new lang_string('forceloginforprofileimage_help', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('opentogoogle', new lang_string('opentogoogle', 'admin'), new lang_string('configopentogoogle', 'admin'), 0));
+ $temp->add(new admin_setting_configselect('allowindexing', new lang_string('allowindexing', 'admin'), new lang_string('allowindexing_desc', 'admin'),
+ 0,
+ array(0 => new lang_string('allowindexingexceptlogin', 'admin'),
+ 1 => new lang_string('allowindexingeverywhere', 'admin'),
+ 2 => new lang_string('allowindexingnowhere', 'admin'))));
$temp->add(new admin_setting_pickroles('profileroles',
new lang_string('profileroles','admin'),
new lang_string('configprofileroles', 'admin'),
function xmldb_tool_customlang_upgrade($oldversion) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
$string['deletea'] = 'Delete {$a}';
$string['deletefiletypes'] = 'Delete a file type';
$string['description'] = 'Custom description';
-$string['description_help'] = 'Simple file type description, e.g. ‘Kindle ebook’. If your site supports multiple languages and uses the multi-language filter, you can enter multi-language tags in this field to supply a description in different languages.';
+$string['description_help'] = 'Simple file type description, e.g. \'Kindle ebook\'. If your site supports multiple languages and uses the multi-language filter, you can enter multi-language tags in this field to supply a description in different languages.';
$string['descriptiontype'] = 'Description type';
$string['descriptiontype_help'] = 'There are three possible ways to specify a description.
$string['error_extension'] = 'The file type extension <strong>{$a}</strong> already exists or is invalid. File extensions must be unique and must not contain special characters.';
$string['error_notfound'] = 'The file type with extension {$a} cannot be found.';
$string['extension'] = 'Extension';
-$string['extension_help'] = 'File name extension without the dot, e.g. ‘mobi’';
+$string['extension_help'] = 'File name extension without the dot, e.g. \'mobi\'';
$string['groups'] = 'Type groups';
-$string['groups_help'] = 'Optional list of file type groups that this type belongs to. These are generic categories such as ‘document’ and ‘image’.';
+$string['groups_help'] = 'Optional list of file type groups that this type belongs to. These are generic categories such as \'document\' and \'image\'.';
$string['icon'] = 'File icon';
$string['icon_help'] = 'Icon filename.
The list of icons is taken from the /pix/f directory inside your Moodle installation. You can add custom icons to this folder if required.';
$string['mimetype'] = 'MIME type';
-$string['mimetype_help'] = 'MIME type associated with this file type, e.g. ‘application/x-mobipocket-ebook’';
+$string['mimetype_help'] = 'MIME type associated with this file type, e.g. \'application/x-mobipocket-ebook\'';
$string['pluginname'] = 'File types';
$string['revert'] = 'Restore {$a} to Moodle defaults';
$string['revert_confirmation'] = 'Are you sure you want to restore <strong>.{$a}</strong> to Moodle defaults, discarding your changes?';
function xmldb_tool_log_upgrade($oldversion) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
$dboptions['dbport'] = $this->get_config('dbport', '');
$dboptions['dbschema'] = $this->get_config('dbschema', '');
$dboptions['dbcollation'] = $this->get_config('dbcollation', '');
+ $dboptions['dbhandlesoptions'] = $this->get_config('dbhandlesoptions', false);
try {
$db->connect($this->get_config('dbhost'), $this->get_config('dbuser'), $this->get_config('dbpass'),
$this->get_config('dbname'), false, $dboptions);
function xmldb_logstore_database_upgrade($oldversion) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
$string['databasepersist'] = 'Persistent database connections';
$string['databaseschema'] = 'Database schema';
$string['databasecollation'] = 'Database collation';
+$string['databasehandlesoptions'] = 'Database handles options';
+$string['databasehandlesoptions_help'] = 'Does the remote database handle its own options.';
$string['databasetable'] = 'Database table';
$string['databasetable_help'] = 'Name of the table where logs will be stored. This table should have a structure identical to the one used by logstore_standard (mdl_logstore_standard_log).';
$string['includeactions'] = 'Include actions of these types';
'logstore_database'), '', ''));
$settings->add(new admin_setting_configtext('logstore_database/dbcollation', get_string('databasecollation',
'logstore_database'), '', ''));
+ $settings->add(new admin_setting_configcheckbox('logstore_database/dbhandlesoptions', get_string('databasehandlesoptions',
+ 'logstore_database'), get_string('databasehandlesoptions_help', 'logstore_database'), '0'));
$settings->add(new admin_setting_configtext('logstore_database/buffersize', get_string('buffersize',
'logstore_database'), get_string('buffersize_help', 'logstore_database'), 50));
$dboptions['dbport'] = get_config('logstore_database', 'dbport');
$dboptions['dbschema'] = get_config('logstore_database', 'dbschema');
$dboptions['dbcollation'] = get_config('logstore_database', 'dbcollation');
+$dboptions['dbhandlesoptions'] = get_config('logstore_database', 'dbhandlesoptions');
try {
$db->connect(get_config('logstore_database', 'dbhost'), get_config('logstore_database', 'dbuser'),
} else {
set_config('dbcollation', '', 'logstore_database');
}
+ if (!empty($CFG->dboptions['dbhandlesoptions'])) {
+ set_config('dbhandlesoptions', $CFG->dboptions['dbhandlesoptions'], 'logstore_database');
+ } else {
+ set_config('dbhandlesoptions', false, 'logstore_database');
+ }
// Enable logging plugin.
set_config('enabled_stores', 'logstore_database', 'tool_log');
--- /dev/null
+This files describes API changes in the logstore_database code.
+
+=== 3.4 ===
+* PostgreSQL connections now use advanced options to reduce connection overhead. These options are not compatible
+ with some connection poolers. The dbhandlesoptions parameter has been added to allow the database to configure the
+ required defaults. The parameters that are required in the database are;
+ ALTER DATABASE moodle SET client_encoding = UTF8;
+ ALTER DATABASE moodle SET standard_conforming_strings = on;
+ ALTER DATABASE moodle SET search_path = 'moodle,public'; -- Optional, if you wish to use a custom schema.
+ You can set these options against the database or the moodle user who connects.
+
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017051500; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version = 2017062600; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2017050500; // Requires this Moodle version.
$plugin->component = 'logstore_database'; // Full name of the plugin (used for diagnostics).
defined('MOODLE_INTERNAL') || die();
function xmldb_logstore_standard_upgrade($oldversion) {
- global $CFG, $DB;
-
- $dbman = $DB->get_manager();
-
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
- if ($oldversion < 2016041200) {
- // This could take a long time. Unfortunately, no way to know how long, and no way to do progress, so setting for 1 hour.
- upgrade_set_timeout(3600);
-
- // Define key contextid (foreign) to be added to logstore_standard_log.
- $table = new xmldb_table('logstore_standard_log');
- $key = new xmldb_key('contextid', XMLDB_KEY_FOREIGN, array('contextid'), 'context', array('id'));
-
- // Launch add key contextid.
- $dbman->add_key($table, $key);
-
- // Standard savepoint reached.
- upgrade_plugin_savepoint(true, 2016041200, 'logstore', 'standard');
- }
+ global $CFG;
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
</div>
</div>
<div data-region="footer" class="pull-xs-right">
- <input type="button" data-action="rate" value="{{#str}}rate, tool_lp{{/str}}" class="btn btn-primary">
+ <button data-action="rate" class="btn btn-primary">{{#str}}rate, tool_lp{{/str}}</button>
<button data-action="cancel" class="btn btn-secondary">{{#str}}cancel{{/str}}</button>
</div>
<div class="clearfix"></div>
<div data-region="rule-base" class="form">
<div data-region="rule-outcome" class="form-group">
<label>{{#str}}outcome, tool_lp{{/str}}</label>
- <select name="outcome" class="custom-select" ng-label="{{#str}}outcome, tool_lp{{/str}}">
+ <select name="outcome" class="custom-select">
{{#outcomes}}
<option value="{{code}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/outcomes}}
</div>
<div data-region="rule-type" class="form-group">
<label>{{#str}}when, tool_lp{{/str}}</label>
- <select name="rule" class="custom-select" ng-label="{{#str}}when, tool_lp{{/str}}">
+ <select name="rule" class="custom-select">
<option value="-1">{{#str}}choosedots{{/str}}</option>
{{#rules}}
<option value="{{type}}" {{#selected}}selected{{/selected}}>{{name}}</option>
$string['loginintheembeddedbrowser'] = 'Via an embedded browser (for SSO plugins)';
$string['mainmenu'] = 'Main menu';
$string['mobileapp'] = 'Mobile app';
+$string['mobileappconnected'] = 'Mobile app connected';
+$string['mobileappenabled'] = 'This site has mobile app access enabled.<br /><a href="{$a}">Download the mobile app</a>.';
$string['mobileappearance'] = 'Mobile appearance';
$string['mobileauthentication'] = 'Mobile authentication';
$string['mobilecssurl'] = 'CSS';
$string['mobilefeatures'] = 'Mobile features';
$string['mobilesettings'] = 'Mobile settings';
$string['pluginname'] = 'Moodle Mobile tools';
+$string['setuplink'] = 'App download page';
+$string['setuplink_desc'] = 'URL of page with links to download the mobile app from the App Store and Google Play.';
$string['smartappbanners'] = 'App Banners';
$string['pluginnotenabledorconfigured'] = 'Plugin not enabled or configured.';
$string['remoteaddons'] = 'Remote add-ons';
$string['typeoflogin'] = 'Type of login';
$string['typeoflogin_desc'] = 'If the site uses a SSO authentication method, then select via a browser window or via an embedded browser. An embedded browser provides a better user experience, though it doesn\'t work with all SSO plugins. If using SSO, autologinguests should be disabled.';
+$string['getmoodleonyourmobile'] = 'Get Moodle on your mobile';
}
return $output;
}
+
+/**
+ * Generate the app download url to promote moodle mobile.
+ *
+ * @return moodle_url|void App download moodle_url object or return if setuplink is not set.
+ */
+function tool_mobile_create_app_download_url() {
+ global $CFG;
+
+ $mobilesettings = get_config('tool_mobile');
+
+ if (empty($mobilesettings->setuplink)) {
+ return;
+ }
+
+ $downloadurl = new moodle_url($mobilesettings->setuplink);
+ $downloadurl->param('version', $CFG->version);
+ $downloadurl->param('lang', current_language());
+
+ if (!empty($mobilesettings->iosappid)) {
+ $downloadurl->param('iosappid', $mobilesettings->iosappid);
+ }
+
+ if (!empty($mobilesettings->androidappid)) {
+ $downloadurl->param('androidappid', $mobilesettings->androidappid);
+ }
+
+ return $downloadurl;
+}
+
+/**
+ * User profile page callback.
+ *
+ * Used add a section about the moodle mobile app.
+ *
+ * @param \core_user\output\myprofile\tree $tree My profile tree where the setting will be added.
+ * @param stdClass $user The user object.
+ * @param bool $iscurrentuser Is this the current user viewing
+ * @return void Return if the mobile web services setting is disabled or if not the current user.
+ */
+function tool_mobile_myprofile_navigation(\core_user\output\myprofile\tree $tree, $user, $iscurrentuser) {
+ global $CFG, $DB;
+
+ if (empty($CFG->enablemobilewebservice)) {
+ return;
+ }
+
+ if (!$iscurrentuser) {
+ return;
+ }
+
+ if (!$url = tool_mobile_create_app_download_url()) {
+ return;
+ }
+
+ $sql = "SELECT 1
+ FROM {external_tokens} t, {external_services} s
+ WHERE t.externalserviceid = s.id
+ AND s.enabled = 1
+ AND s.shortname IN ('moodle_mobile_app', 'local_mobile')
+ AND t.userid = ?";
+ $userhastoken = $DB->record_exists_sql($sql, [$user->id]);
+
+ $mobilecategory = new core_user\output\myprofile\category('mobile', get_string('mobileapp', 'tool_mobile'),
+ 'loginactivity');
+ $tree->add_category($mobilecategory);
+
+ if ($userhastoken) {
+ $mobilestr = get_string('mobileappconnected', 'tool_mobile');
+ } else {
+ $mobilestr = get_string('mobileappenabled', 'tool_mobile', $url->out());
+ }
+
+ $node = new core_user\output\myprofile\node('mobile', 'mobileappnode', $mobilestr, null);
+ $tree->add_node($node);
+}
+
+/**
+ * Callback to add footer elements.
+ *
+ * @return str valid html footer content
+ * @since Moodle 3.4
+ */
+function tool_mobile_standard_footer_html() {
+ global $CFG;
+ $output = '';
+ if (!empty($CFG->enablemobilewebservice) && $url = tool_mobile_create_app_download_url()) {
+ $output .= html_writer::link($url, get_string('getmoodleonyourmobile', 'tool_mobile'));
+ }
+ return $output;
+}
$temp->add(new admin_setting_configtext('tool_mobile/androidappid', new lang_string('androidappid', 'tool_mobile'),
new lang_string('androidappid_desc', 'tool_mobile'), 'com.moodle.moodlemobile', PARAM_NOTAGS));
+ $temp->add(new admin_setting_configtext('tool_mobile/setuplink', new lang_string('setuplink', 'tool_mobile'),
+ new lang_string('setuplink_desc', 'tool_mobile'), 'https://download.moodle.org/mobile', PARAM_URL));
+
$ADMIN->add('mobileapp', $temp);
// Features related settings.
* @return bool always true
*/
function xmldb_tool_monitor_upgrade($oldversion) {
- global $DB;
+ global $CFG, $DB;
$dbman = $DB->get_manager();
- if ($oldversion < 2014102000) {
-
- // Define field lastnotificationsent to be added to tool_monitor_subscriptions.
- $table = new xmldb_table('tool_monitor_subscriptions');
- $field = new xmldb_field('lastnotificationsent', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'timecreated');
-
- // Conditionally launch add field lastnotificationsent.
- if (!$dbman->field_exists($table, $field)) {
- $dbman->add_field($table, $field);
- }
-
- // Monitor savepoint reached.
- upgrade_plugin_savepoint(true, 2014102000, 'tool', 'monitor');
- }
-
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
$mform->addElement('checkbox', 'showonloginpage', get_string('issuershowonloginpage', 'tool_oauth2'));
$mform->addHelpButton('showonloginpage', 'issuershowonloginpage', 'tool_oauth2');
+ // Require confirmation email for new accounts.
+ $mform->addElement('advcheckbox', 'requireconfirmation', get_string('issuerrequireconfirmation', 'tool_oauth2'));
+ $mform->addHelpButton('requireconfirmation', 'issuerrequireconfirmation', 'tool_oauth2');
+
$mform->addElement('hidden', 'sortorder');
$mform->setType('sortorder', PARAM_INT);
$string['issuerloginparams'] = 'Additional parameters included in a login request.';
$string['issuerloginparams_help'] = 'Some systems require additional parameters for a login request in order to read the user\'s basic profile.';
$string['issuerloginparamsoffline'] = 'Additional parameters included in a login request for offline access.';
-$string['issuerloginparamsoffline_help'] = 'Each OAuth system defines a different way to request offline access. E.g. Google requires the additional params: "access_type=offline&prompt=consent" these parameters should be in url query parameter format.';
-$string['issuerloginscopes_help'] = 'Some systems require additional scopes for a login request in order to read the users basic profile. The standard scopes for an OpenID Connect compliant system are "openid profile email".';
-$string['issuerloginscopesoffline_help'] = 'Each OAuth system defines a different way to request offline access. E.g. Microsoft requires an additional scope "offline_access"';
+$string['issuerloginparamsoffline_help'] = 'Each OAuth system defines a different way to request offline access. E.g. Google requires the additional params: "access_type=offline&prompt=consent". These parameters should be in URL query parameter format.';
+$string['issuerloginscopes_help'] = 'Some systems require additional scopes for a login request in order to read the user\'s basic profile. The standard scopes for an OpenID Connect compliant system are "openid profile email".';
+$string['issuerloginscopesoffline_help'] = 'Each OAuth system defines a different way to request offline access. E.g. Microsoft requires an additional scope "offline_access".';
$string['issuerloginscopesoffline'] = 'Scopes included in a login request for offline access.';
$string['issuerloginscopes'] = 'Scopes included in a login request.';
$string['issuername_help'] = 'Name of the identity issuer. May be displayed on login page.';
$string['issuername'] = 'Name';
-$string['issuershowonloginpage_help'] = 'If the OpenID Connect Authentication plugin is enabled, this login issuer will be listed on the login page to allow users to log in with accounts from this issuer.';
-$string['issuershowonloginpage'] = 'Show on login page.';
+$string['issuershowonloginpage_help'] = 'If the OAuth 2 authentication plugin is enabled, this login issuer will be listed on the login page to allow users to log in with accounts from this issuer.';
+$string['issuershowonloginpage'] = 'Show on login page';
+$string['issuerrequireconfirmation_help'] = 'Require that all users verify their email address before they can log in with OAuth. This applies to newly created accounts as part of the login process, or when an existing Moodle account is connected to an OAuth login via matching email addresses.';
+$string['issuerrequireconfirmation'] = 'Require email verification';
$string['issuers'] = 'Issuers';
$string['loginissuer'] = 'Allow login';
$string['notconfigured'] = 'Not configured';
$string['disabled'] = 'Disabled';
$string['disabled_help'] = 'Disabled scheduled tasks are not executed from cron, however they can still be executed manually via the CLI tool.';
$string['edittaskschedule'] = 'Edit task schedule: {$a}';
-$string['enablerunnow'] = 'Allow ‘Run now’ for scheduled tasks';
+$string['enablerunnow'] = 'Allow \'Run now\' for scheduled tasks';
$string['enablerunnow_desc'] = 'Allows administrators to run a single scheduled task immediately, rather than waiting for it to run as scheduled. The task runs on the web server, so some sites may wish to disable this feature to avoid potential performance issues.';
$string['faildelay'] = 'Fail delay';
$string['lastruntime'] = 'Last run';
$string['resettasktodefaults'] = 'Reset task schedule to defaults';
$string['resettasktodefaults_help'] = 'This will discard any local changes and revert the schedule for this task back to its original settings.';
$string['runnow'] = 'Run now';
-$string['runnow_confirm'] = 'Are you sure you want to run this task ‘{$a}’ now? The task will run on the web server and may take some time to complete.';
+$string['runnow_confirm'] = 'Are you sure you want to run this task \'{$a}\' now? The task will run on the web server and may take some time to complete.';
$string['scheduledtasks'] = 'Scheduled tasks';
$string['scheduledtaskchangesdisabled'] = 'Modifications to the list of scheduled tasks have been prevented in Moodle configuration';
$string['taskdisabled'] = 'Task disabled';
/** @var array fields allowed as course data. */
static protected $validfields = array('fullname', 'shortname', 'idnumber', 'category', 'visible', 'startdate', 'enddate',
'summary', 'format', 'theme', 'lang', 'newsitems', 'showgrades', 'showreports', 'legacyfiles', 'maxbytes',
- 'groupmode', 'groupmodeforce', 'groupmodeforce', 'enablecompletion');
+ 'groupmode', 'groupmodeforce', 'enablecompletion');
/** @var array fields required on course creation. */
static protected $mandatoryfields = array('fullname', 'category');
return false;
}
+ // TODO MDL-59259 allow to set course format options for the current course format.
+
+ // Special case, 'numsections' is not a course format option any more but still should apply from defaults.
+ if (!$exists || !array_key_exists('numsections', $coursedata)) {
+ if (isset($this->rawdata['numsections']) && is_numeric($this->rawdata['numsections'])) {
+ $coursedata['numsections'] = (int)$this->rawdata['numsections'];
+ } else {
+ $coursedata['numsections'] = get_config('moodlecourse', 'numsections');
+ }
+ }
+
// Saving data.
$this->data = $coursedata;
$this->enrolmentdata = tool_uploadcourse_helper::get_enrolment_data($this->rawdata);
$mform->addHelpButton('defaults[groupmodeforce]', 'groupmodeforce', 'group');
$mform->setDefault('defaults[groupmodeforce]', $courseconfig->groupmodeforce);
+ // Completion tracking.
+ if (!empty($CFG->enablecompletion)) {
+ $mform->addElement('selectyesno', 'defaults[enablecompletion]', get_string('enablecompletion', 'completion'));
+ $mform->setDefault('defaults[enablecompletion]', $courseconfig->enablecompletion);
+ $mform->addHelpButton('defaults[enablecompletion]', 'enablecompletion', 'completion');
+ }
+
// Hidden fields.
$mform->addElement('hidden', 'importid');
$mform->setType('importid', PARAM_INT);
$defaults['groupmodeforce'] = $courseconfig->groupmodeforce;
$defaults['visible'] = $courseconfig->visible;
$defaults['lang'] = $courseconfig->lang;
+$defaults['enablecompletion'] = $courseconfig->enablecompletion;
// Course template.
if (isset($options['templatecourse'])) {
$this->assertTrue($DB->record_exists('course', array('shortname' => 'c2')));
}
+ public function test_create_with_sections() {
+ global $DB;
+ $this->resetAfterTest(true);
+ $updatemode = tool_uploadcourse_processor::UPDATE_NOTHING;
+ $defaultnumsections = get_config('moodlecourse', 'numsections');
+
+ // Add new course, make sure default number of sections is created.
+ $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
+ $data = array('shortname' => 'newcourse1', 'fullname' => 'New course1', 'format' => 'topics', 'category' => 1);
+ $co = new tool_uploadcourse_course($mode, $updatemode, $data);
+ $this->assertTrue($co->prepare());
+ $co->proceed();
+ $courseid = $DB->get_field('course', 'id', array('shortname' => 'newcourse1'));
+ $this->assertNotEmpty($courseid);
+ $this->assertEquals($defaultnumsections + 1,
+ $DB->count_records('course_sections', ['course' => $courseid]));
+
+ // Add new course specifying number of sections.
+ $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
+ $data = array('shortname' => 'newcourse2', 'fullname' => 'New course2', 'format' => 'topics', 'category' => 1,
+ 'numsections' => 15);
+ $co = new tool_uploadcourse_course($mode, $updatemode, $data);
+ $this->assertTrue($co->prepare());
+ $co->proceed();
+ $courseid = $DB->get_field('course', 'id', array('shortname' => 'newcourse2'));
+ $this->assertNotEmpty($courseid);
+ $this->assertEquals(15 + 1,
+ $DB->count_records('course_sections', ['course' => $courseid]));
+ }
+
public function test_delete() {
global $DB;
$this->resetAfterTest(true);
false/*$no_response*/, true/*$bad_response*/, $text_response
);
$result = false;
+ } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) {
+ // authentication failed, extract the error code and message and throw exception
+ $auth_fail_list = $tree_response
+ ->getElementsByTagName("authenticationFailure");
+ throw new CAS_AuthenticationException(
+ $this, 'Ticket not validated', $validate_url,
+ false/*$no_response*/, false/*$bad_response*/,
+ $text_response,
+ $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/,
+ trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/
+ );
+ $result = false;
} else if ($tree_response->getElementsByTagName("authenticationSuccess")->length != 0) {
// authentication succeded, extract the user name
$success_elements = $tree_response
$result = true;
}
}
- } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) {
- // authentication succeded, extract the error code and message
- $auth_fail_list = $tree_response
- ->getElementsByTagName("authenticationFailure");
- throw new CAS_AuthenticationException(
- $this, 'Ticket not validated', $validate_url,
- false/*$no_response*/, false/*$bad_response*/,
- $text_response,
- $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/,
- trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/
- );
- $result = false;
} else {
throw new CAS_AuthenticationException(
$this, 'Ticket not validated', $validate_url,
* downloaded from http://downloads.jasig.org/cas-clients/php/current/
+* MDL-59456 phpCAS library has been patched because of an authentication bypass security vulnerability.
\ No newline at end of file
* @return bool result
*/
function xmldb_auth_cas_upgrade($oldversion) {
- global $CFG, $DB;
-
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- if ($oldversion < 2014111001) {
- // From now on the default LDAP objectClass setting for AD has been changed, from 'user' to '(samaccounttype=805306368)'.
- if (is_enabled_auth('cas')
- && ($DB->get_field('config_plugins', 'value', array('name' => 'user_type', 'plugin' => 'auth/cas')) === 'ad')
- && ($DB->get_field('config_plugins', 'value', array('name' => 'objectclass', 'plugin' => 'auth/cas')) === '')) {
- // Save the backwards-compatible default setting.
- set_config('objectclass', 'user', 'auth/cas');
- }
-
- upgrade_plugin_savepoint(true, 2014111001, 'auth', 'cas');
- }
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
+ global $CFG;
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
)
);
}
+
+ /**
+ * Describes the parameters for request_password_reset.
+ *
+ * @return external_function_parameters
+ * @since Moodle 3.4
+ */
+ public static function request_password_reset_parameters() {
+ return new external_function_parameters(
+ array(
+ 'username' => new external_value(core_user::get_property_type('username'), 'User name', VALUE_DEFAULT, ''),
+ 'email' => new external_value(core_user::get_property_type('email'), 'User email', VALUE_DEFAULT, ''),
+ )
+ );
+ }
+
+ /**
+ * Requests a password reset.
+ *
+ * @param string $username user name
+ * @param string $email user email
+ * @return array warnings and success status (including notices and errors while processing)
+ * @since Moodle 3.4
+ * @throws moodle_exception
+ */
+ public static function request_password_reset($username = '', $email = '') {
+ global $CFG, $PAGE;
+ require_once($CFG->dirroot . '/login/lib.php');
+
+ $warnings = array();
+ $params = self::validate_parameters(
+ self::request_password_reset_parameters(),
+ array(
+ 'username' => $username,
+ 'email' => $email,
+ )
+ );
+
+ $context = context_system::instance();
+ $PAGE->set_context($context); // Needed by format_string calls.
+
+ // Check if an alternate forgotten password method is set.
+ if (!empty($CFG->forgottenpasswordurl)) {
+ throw new moodle_exception('cannotmailconfirm');
+ }
+
+ $errors = core_login_validate_forgot_password_data($params);
+ if (!empty($errors)) {
+ $status = 'dataerror';
+ $notice = '';
+
+ foreach ($errors as $itemname => $message) {
+ $warnings[] = array(
+ 'item' => $itemname,
+ 'itemid' => 0,
+ 'warningcode' => 'fielderror',
+ 'message' => s($message)
+ );
+ }
+ } else {
+ list($status, $notice, $url) = core_login_process_password_reset($params['username'], $params['email']);
+ }
+
+ return array(
+ 'status' => $status,
+ 'notice' => $notice,
+ 'warnings' => $warnings,
+ );
+ }
+
+ /**
+ * Describes the request_password_reset return value.
+ *
+ * @return external_single_structure
+ * @since Moodle 3.4
+ */
+ public static function request_password_reset_returns() {
+
+ return new external_single_structure(
+ array(
+ 'status' => new external_value(PARAM_ALPHANUMEXT, 'The returned status of the process:
+ dataerror: Error in the sent data (username or email). More information in warnings field.
+ emailpasswordconfirmmaybesent: Email sent or not (depends on user found in database).
+ emailpasswordconfirmnotsent: Failure, user not found.
+ emailpasswordconfirmnoemail: Failure, email not found.
+ emailalreadysent: Email already sent.
+ emailpasswordconfirmsent: User pending confirmation.
+ emailresetconfirmsent: Email sent.
+ '),
+ 'notice' => new external_value(PARAM_RAW, 'Important information for the user about the process.'),
+ 'warnings' => new external_warnings(),
+ )
+ );
+ }
}
* @return bool result
*/
function xmldb_auth_ldap_upgrade($oldversion) {
- global $CFG, $DB;
-
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- if ($oldversion < 2014111001) {
- // From now on the default LDAP objectClass setting for AD has been changed, from 'user' to '(samaccounttype=805306368)'.
- if (is_enabled_auth('ldap')
- && ($DB->get_field('config_plugins', 'value', array('name' => 'user_type', 'plugin' => 'auth/ldap')) === 'ad')
- && ($DB->get_field('config_plugins', 'value', array('name' => 'objectclass', 'plugin' => 'auth/ldap')) === '')) {
- // Save the backwards-compatible default setting.
- set_config('objectclass', 'user', 'auth/ldap');
- }
-
- upgrade_plugin_savepoint(true, 2014111001, 'auth', 'ldap');
- }
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
+ global $CFG;
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
* @return bool result
*/
function xmldb_auth_manual_upgrade($oldversion) {
- global $CFG, $DB;
-
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
+ global $CFG;
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
* @return bool result
*/
function xmldb_auth_mnet_upgrade($oldversion) {
- global $CFG, $DB;
-
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
+ global $CFG;
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+ /**
+ * Create an account with a linked login that is already confirmed.
+ *
+ * @param array $userinfo as returned from an oauth client.
+ * @param \core\oauth2\issuer $issuer
+ * @return bool
+ */
+ public static function create_new_confirmed_account($userinfo, $issuer) {
+ global $CFG, $DB;
+ require_once($CFG->dirroot.'/user/profile/lib.php');
+ require_once($CFG->dirroot.'/user/lib.php');
+
+ $user = new stdClass();
+ $user->username = $userinfo['username'];
+ $user->email = $userinfo['email'];
+ $user->auth = 'oauth2';
+ $user->mnethostid = $CFG->mnet_localhost_id;
+ $user->lastname = isset($userinfo['lastname']) ? $userinfo['lastname'] : '';
+ $user->firstname = isset($userinfo['firstname']) ? $userinfo['firstname'] : '';
+ $user->url = isset($userinfo['url']) ? $userinfo['url'] : '';
+ $user->alternatename = isset($userinfo['alternatename']) ? $userinfo['alternatename'] : '';
+ $user->secret = random_string(15);
+
+ $user->password = '';
+ // This user is confirmed.
+ $user->confirmed = 1;
+
+ $user->id = user_create_user($user, false, true);
+
+ // The linked account is pre-confirmed.
+ $record = new stdClass();
+ $record->issuerid = $issuer->get('id');
+ $record->username = $userinfo['username'];
+ $record->userid = $user->id;
+ $record->email = $userinfo['email'];
+ $record->confirmtoken = '';
+ $record->confirmtokenexpires = 0;
+
+ $linkedlogin = new linked_login(0, $record);
+ $linkedlogin->create();
+
+ return $user;
+ }
+
/**
* Send an email with a link to confirm creating this account.
*
* Complete the login process after oauth handshake is complete.
* @param \core\oauth2\client $client
* @param string $redirecturl
- * @return none Either redirects or throws an exception
+ * @return void Either redirects or throws an exception
*/
public function complete_login(client $client, $redirecturl) {
global $CFG, $SESSION, $PAGE;
if (!$userinfo) {
// Trigger login failed event.
$failurereason = AUTH_LOGIN_NOUSER;
- $event = \core\event\user_login_failed::create(['other' => ['username' => $userinfo['username'],
+ $event = \core\event\user_login_failed::create(['other' => ['username' => 'unknown',
'reason' => $failurereason]]);
$event->trigger();
if (empty($userinfo['username']) || empty($userinfo['email'])) {
// Trigger login failed event.
$failurereason = AUTH_LOGIN_NOUSER;
- $event = \core\event\user_login_failed::create(['other' => ['username' => $userinfo['username'],
+ $event = \core\event\user_login_failed::create(['other' => ['username' => 'unknown',
'reason' => $failurereason]]);
$event->trigger();
$moodleuser = \core_user::get_user_by_email($userinfo['email']);
if (!empty($moodleuser)) {
- $PAGE->set_url('/auth/oauth2/confirm-link-login.php');
- $PAGE->set_context(context_system::instance());
-
- \auth_oauth2\api::send_confirm_link_login_email($userinfo, $issuer, $moodleuser->id);
- // Request to link to existing account.
- $emailconfirm = get_string('emailconfirmlink', 'auth_oauth2');
- $message = get_string('emailconfirmlinksent', 'auth_oauth2', $moodleuser->email);
- $this->print_confirm_required($emailconfirm, $message);
- exit();
+ if ($issuer->get('requireconfirmation')) {
+ $PAGE->set_url('/auth/oauth2/confirm-link-login.php');
+ $PAGE->set_context(context_system::instance());
+
+ \auth_oauth2\api::send_confirm_link_login_email($userinfo, $issuer, $moodleuser->id);
+ // Request to link to existing account.
+ $emailconfirm = get_string('emailconfirmlink', 'auth_oauth2');
+ $message = get_string('emailconfirmlinksent', 'auth_oauth2', $moodleuser->email);
+ $this->print_confirm_required($emailconfirm, $message);
+ exit();
+ } else {
+ \auth_oauth2\api::link_login($userinfo, $issuer, $moodleuser->id, true);
+ $userinfo = get_complete_user_data('id', $moodleuser->id);
+ // No redirect, we will complete this login.
+ }
} else {
// This is a new account.
redirect(new moodle_url($CFG->httpswwwroot . '/login/index.php'));
}
- $PAGE->set_url('/auth/oauth2/confirm-account.php');
- $PAGE->set_context(context_system::instance());
+ if ($issuer->get('requireconfirmation')) {
+ $PAGE->set_url('/auth/oauth2/confirm-account.php');
+ $PAGE->set_context(context_system::instance());
+
+ // Create a new (unconfirmed account) and send an email to confirm it.
+ $user = \auth_oauth2\api::send_confirm_account_email($userinfo, $issuer);
- // Create a new (unconfirmed account) and send an email to confirm it.
- $user = \auth_oauth2\api::send_confirm_account_email($userinfo, $issuer);
+ $this->update_picture($user);
+ $emailconfirm = get_string('emailconfirm');
+ $message = get_string('emailconfirmsent', '', $userinfo['email']);
+ $this->print_confirm_required($emailconfirm, $message);
+ exit();
+ } else {
+ // Create a new confirmed account.
+ $newuser = \auth_oauth2\api::create_new_confirmed_account($userinfo, $issuer);
+ $userinfo = get_complete_user_data('id', $newuser->id);
- $this->update_picture($user);
- $emailconfirm = get_string('emailconfirm');
- $message = get_string('emailconfirmsent', '', $userinfo['email']);
- $this->print_confirm_required($emailconfirm, $message);
- exit();
+ // No redirect, we will complete this login.
+ }
}
}
$this->assertCount(1, $linkedlogins);
}
+ /**
+ * Test auto-confirming linked logins.
+ */
+ public function test_linked_logins() {
+ $this->resetAfterTest();
+
+ $this->setAdminUser();
+ $issuer = \core\oauth2\api::create_standard_issuer('google');
+
+ $user = $this->getDataGenerator()->create_user();
+
+ $info = [];
+ $info['username'] = 'banana';
+ $info['email'] = 'banana@example.com';
+
+ \auth_oauth2\api::link_login($info, $issuer, $user->id, false);
+
+ // Try and match a user with a linked login.
+ $match = \auth_oauth2\api::match_username_to_user('banana', $issuer);
+
+ $this->assertEquals($user->id, $match->get('userid'));
+ $linkedlogins = \auth_oauth2\api::get_linked_logins($user->id, $issuer);
+ \auth_oauth2\api::delete_linked_login($linkedlogins[0]->get('id'));
+
+ $match = \auth_oauth2\api::match_username_to_user('banana', $issuer);
+ $this->assertFalse($match);
+
+ $info = [];
+ $info['username'] = 'apple';
+ $info['email'] = 'apple@example.com';
+ $info['firstname'] = 'Apple';
+ $info['lastname'] = 'Fruit';
+ $info['url'] = 'http://apple.com/';
+ $info['alternamename'] = 'Beatles';
+
+ $newuser = \auth_oauth2\api::create_new_confirmed_account($info, $issuer);
+
+ $match = \auth_oauth2\api::match_username_to_user('apple', $issuer);
+
+ $this->assertEquals($newuser->id, $match->get('userid'));
+ }
+
}
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * This script is used to configure and execute the backup proccess.
+ *
+ * @package core
+ * @subpackage backup
+ * @copyright Moodle
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('NO_OUTPUT_BUFFERING', true);
+
require_once('../config.php');
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/moodle2/backup_plan_builder.class.php');
<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This script is used to configure and execute the import proccess.
+ *
+ * @package core
+ * @subpackage backup
+ * @copyright Moodle
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('NO_OUTPUT_BUFFERING', true);
+
// Require both the backup and restore libs
require_once('../config.php');
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
// Define each element separated
$block = new backup_nested_element('block', array('id', 'contextid', 'version'), array(
- 'blockname', 'parentcontextid', 'showinsubcontexts', 'pagetypepattern',
- 'subpagepattern', 'defaultregion', 'defaultweight', 'configdata'));
+ 'blockname', 'parentcontextid', 'showinsubcontexts', 'pagetypepattern',
+ 'subpagepattern', 'defaultregion', 'defaultweight', 'configdata',
+ 'timecreated', 'timemodified'));
$positions = new backup_nested_element('block_positions');
$data->configdata = base64_encode(serialize((object)$configdata));
}
+ // Set timecreated, timemodified if not included (older backup).
+ if (empty($data->timecreated)) {
+ $data->timecreated = time();
+ }
+ if (empty($data->timemodified)) {
+ $data->timemodified = $data->timecreated;
+ }
+
// Create the block instance
$newitemid = $DB->insert_record('block_instances', $data);
// Save the mapping (with restorefiles support)
*
* @param stdClass $course Course object to backup
* @param int $newdate If non-zero, specifies custom date for new course
+ * @param callable|null $inbetween If specified, function that is called before restore
* @return int ID of newly restored course
*/
- protected function backup_and_restore($course, $newdate = 0) {
+ protected function backup_and_restore($course, $newdate = 0, $inbetween = null) {
global $USER, $CFG;
// Turn off file logging, otherwise it can't delete the file (Windows).
$bc->execute_plan();
$bc->destroy();
+ if ($inbetween) {
+ $inbetween($backupid);
+ }
+
// Do restore to new course with default settings.
$newcourseid = restore_dbops::create_new_course(
$course->fullname, $course->shortname . '_2', $course->category);
$enrolment = reset($enrolments);
$this->assertEquals('self', $enrolment->enrol);
}
+
+ /**
+ * Test the block instance time fields (timecreated, timemodified) through a backup and restore.
+ */
+ public function test_block_instance_times_backup() {
+ global $DB;
+ $this->resetAfterTest();
+
+ $this->setAdminUser();
+ $generator = $this->getDataGenerator();
+
+ // Create course and add HTML block.
+ $course = $generator->create_course();
+ $context = context_course::instance($course->id);
+ $page = new moodle_page();
+ $page->set_context($context);
+ $page->set_course($course);
+ $page->set_pagelayout('standard');
+ $page->set_pagetype('course-view');
+ $page->blocks->load_blocks();
+ $page->blocks->add_block_at_end_of_default_region('html');
+
+ // Update (hack in database) timemodified and timecreated to specific values for testing.
+ $blockdata = $DB->get_record('block_instances',
+ ['blockname' => 'html', 'parentcontextid' => $context->id]);
+ $originalblockid = $blockdata->id;
+ $blockdata->timecreated = 12345;
+ $blockdata->timemodified = 67890;
+ $DB->update_record('block_instances', $blockdata);
+
+ // Do backup and restore.
+ $newcourseid = $this->backup_and_restore($course);
+
+ // Confirm that values were transferred correctly into HTML block on new course.
+ $newcontext = context_course::instance($newcourseid);
+ $blockdata = $DB->get_record('block_instances',
+ ['blockname' => 'html', 'parentcontextid' => $newcontext->id]);
+ $this->assertEquals(12345, $blockdata->timecreated);
+ $this->assertEquals(67890, $blockdata->timemodified);
+
+ // Simulate what happens with an older backup that doesn't have those fields, by removing
+ // them from the backup before doing a restore.
+ $before = time();
+ $newcourseid = $this->backup_and_restore($course, 0, function($backupid) use($originalblockid) {
+ global $CFG;
+ $path = $CFG->dataroot . '/temp/backup/' . $backupid . '/course/blocks/html_' .
+ $originalblockid . '/block.xml';
+ $xml = file_get_contents($path);
+ $xml = preg_replace('~<timecreated>.*?</timemodified>~s', '', $xml);
+ file_put_contents($path, $xml);
+ });
+ $after = time();
+
+ // The fields not specified should default to current time.
+ $newcontext = context_course::instance($newcourseid);
+ $blockdata = $DB->get_record('block_instances',
+ ['blockname' => 'html', 'parentcontextid' => $newcontext->id]);
+ $this->assertTrue($before <= $blockdata->timecreated && $after >= $blockdata->timecreated);
+ $this->assertTrue($before <= $blockdata->timemodified && $after >= $blockdata->timemodified);
+ }
}
<?php
- //This script is used to configure and execute the restore proccess.
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This script is used to configure and execute the restore proccess.
+ *
+ * @package core
+ * @subpackage backup
+ * @copyright Moodle
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('NO_OUTPUT_BUFFERING', true);
require_once('../config.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
* @param object $block
*/
function xmldb_block_badges_upgrade($oldversion, $block) {
- global $DB;
-
- if ($oldversion < 2014062600) {
- // Add this block the default blocks on /my.
- $blockname = 'badges';
-
- // Do not try to add the block if we cannot find the default my_pages entry.
- // Private => 1 refers to MY_PAGE_PRIVATE.
- if ($systempage = $DB->get_record('my_pages', array('userid' => null, 'private' => 1))) {
- $page = new moodle_page();
- $page->set_context(context_system::instance());
-
- // Check to see if this block is already on the default /my page.
- $criteria = array(
- 'blockname' => $blockname,
- 'parentcontextid' => $page->context->id,
- 'pagetypepattern' => 'my-index',
- 'subpagepattern' => $systempage->id,
- );
-
- if (!$DB->record_exists('block_instances', $criteria)) {
- // Add the block to the default /my.
- $page->blocks->add_region(BLOCK_POS_RIGHT);
- $page->blocks->add_block($blockname, BLOCK_POS_RIGHT, 0, false, 'my-index', $systempage->id);
- }
- }
-
- upgrade_block_savepoint(true, 2014062600, $blockname);
- }
-
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
+ global $CFG;
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
* @param object $block
*/
function xmldb_block_calendar_month_upgrade($oldversion, $block) {
- global $DB;
-
- if ($oldversion < 2014062600) {
- // Add this block the default blocks on /my.
- $blockname = 'calendar_month';
-
- // Do not try to add the block if we cannot find the default my_pages entry.
- // Private => 1 refers to MY_PAGE_PRIVATE.
- if ($systempage = $DB->get_record('my_pages', array('userid' => null, 'private' => 1))) {
- $page = new moodle_page();
- $page->set_context(context_system::instance());
-
- // Check to see if this block is already on the default /my page.
- $criteria = array(
- 'blockname' => $blockname,
- 'parentcontextid' => $page->context->id,
- 'pagetypepattern' => 'my-index',
- 'subpagepattern' => $systempage->id,
- );
-
- if (!$DB->record_exists('block_instances', $criteria)) {
- // Add the block to the default /my.
- $page->blocks->add_region(BLOCK_POS_RIGHT);
- $page->blocks->add_block($blockname, BLOCK_POS_RIGHT, 0, false, 'my-index', $systempage->id);
- }
- }
-
- upgrade_block_savepoint(true, 2014062600, $blockname);
- }
-
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
+ global $CFG;
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
* @param object $block
*/
function xmldb_block_calendar_upcoming_upgrade($oldversion, $block) {
- global $DB;
-
- if ($oldversion < 2014062600) {
- // Add this block the default blocks on /my.
- $blockname = 'calendar_upcoming';
-
- // Do not try to add the block if we cannot find the default my_pages entry.
- // Private => 1 refers to MY_PAGE_PRIVATE.
- if ($systempage = $DB->get_record('my_pages', array('userid' => null, 'private' => 1))) {
- $page = new moodle_page();
- $page->set_context(context_system::instance());
-
- // Check to see if this block is already on the default /my page.
- $criteria = array(
- 'blockname' => $blockname,
- 'parentcontextid' => $page->context->id,
- 'pagetypepattern' => 'my-index',
- 'subpagepattern' => $systempage->id,
- );
-
- if (!$DB->record_exists('block_instances', $criteria)) {
- // Add the block to the default /my.
- $page->blocks->add_region(BLOCK_POS_RIGHT);
- $page->blocks->add_block($blockname, BLOCK_POS_RIGHT, 0, false, 'my-index', $systempage->id);
- }
- }
-
- upgrade_block_savepoint(true, 2014062600, $blockname);
- }
-
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
+ global $CFG;
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_block_community_upgrade($oldversion) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_block_completionstatus_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_block_course_summary_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Search area for block_html blocks
+ *
+ * @package block_html
+ * @copyright 2017 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_html\search;
+
+use core_search\moodle_recordset;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Search area for block_html blocks
+ *
+ * @package block_html
+ * @copyright 2017 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class content extends \core_search\base_block {
+
+ public function get_document($record, $options = array()) {
+ // Create empty document.
+ $doc = \core_search\document_factory::instance($record->id,
+ $this->componentname, $this->areaname);
+
+ // Get stdclass object with data from DB.
+ $data = unserialize(base64_decode($record->configdata));
+
+ // Get content.
+ $content = content_to_text($data->text, $data->format);
+ $doc->set('content', $content);
+
+ if (isset($data->title)) {
+ // If there is a title, use it as title.
+ $doc->set('title', content_to_text($data->title, false));
+ } else {
+ // If there is no title, use the content text again.
+ $doc->set('title', shorten_text($content));
+ }
+
+ // Set standard fields.
+ $doc->set('contextid', $record->contextid);
+ $doc->set('type', \core_search\manager::TYPE_TEXT);
+ $doc->set('courseid', $record->courseid);
+ $doc->set('modified', $record->timemodified);
+ $doc->set('owneruserid', \core_search\manager::NO_OWNER_ID);
+
+ // Mark document new if appropriate.
+ if (isset($options['lastindexedtime']) &&
+ ($options['lastindexedtime'] < $record->timecreated)) {
+ // If the document was created after the last index time, it must be new.
+ $doc->set_is_new(true);
+ }
+
+ return $doc;
+ }
+
+ public function uses_file_indexing() {
+ return true;
+ }
+
+ public function attach_files($document) {
+ $fs = get_file_storage();
+
+ $context = \context::instance_by_id($document->get('contextid'));
+
+ $files = $fs->get_area_files($context->id, 'block_html', 'content');
+ foreach ($files as $file) {
+ $document->add_stored_file($file);
+ }
+ }
+}
function xmldb_block_html_upgrade($oldversion) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
$string['leaveblanktohide'] = 'leave blank to hide the title';
$string['newhtmlblock'] = '(new HTML block)';
$string['pluginname'] = 'HTML';
+$string['search:content'] = 'HTML block content';
$config = unserialize(base64_decode($instance->configdata));
if (isset($config->text) and is_string($config->text)) {
$config->text = str_replace($search, $replace, $config->text);
- $DB->set_field('block_instances', 'configdata', base64_encode(serialize($config)), array('id' => $instance->id));
+ $DB->update_record('block_instances', ['id' => $instance->id,
+ 'configdata' => base64_encode(serialize($config)), 'timemodified' => time()]);
}
}
$instances->close();
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit test for search indexing.
+ *
+ * @package block_html
+ * @copyright 2017 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_html;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Unit test for search indexing.
+ *
+ * @package block_html
+ * @copyright 2017 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class search_content_testcase extends \advanced_testcase {
+
+ /**
+ * Creates an HTML block on a course.
+ *
+ * @param \stdClass $course Course object
+ * @return \block_html Block instance object
+ */
+ protected function create_block($course) {
+ $page = self::construct_page($course);
+ $page->blocks->add_block_at_end_of_default_region('html');
+
+ // Load the block.
+ $page = self::construct_page($course);
+ $page->blocks->load_blocks();
+ $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region());
+ $block = end($blocks);
+ return $block;
+ }
+
+ /**
+ * Constructs a page object for the test course.
+ *
+ * @param \stdClass $course Moodle course object
+ * @return \moodle_page Page object representing course view
+ */
+ protected static function construct_page($course) {
+ $context = \context_course::instance($course->id);
+ $page = new \moodle_page();
+ $page->set_context($context);
+ $page->set_course($course);
+ $page->set_pagelayout('standard');
+ $page->set_pagetype('course-view');
+ $page->blocks->load_blocks();
+ return $page;
+ }
+
+ /**
+ * Tests all functionality in the search area.
+ */
+ public function test_search_area() {
+ global $CFG, $USER, $DB;
+ require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ // Create course and add HTML block.
+ $generator = $this->getDataGenerator();
+ $course = $generator->create_course();
+ $before = time();
+ $block = $this->create_block($course);
+
+ // Change block settings to add some text and a file.
+ $itemid = file_get_unused_draft_itemid();
+ $fs = get_file_storage();
+ $usercontext = \context_user::instance($USER->id);
+ $fs->create_file_from_string(['component' => 'user', 'filearea' => 'draft',
+ 'contextid' => $usercontext->id, 'itemid' => $itemid, 'filepath' => '/',
+ 'filename' => 'file.txt'], 'File content');
+ $data = (object)['title' => 'Block title', 'text' => ['text' => 'Block text',
+ 'itemid' => $itemid, 'format' => FORMAT_HTML]];
+ $block->instance_config_save($data);
+ $after = time();
+
+ // Set up fake search engine so we can create documents.
+ \testable_core_search::instance();
+
+ // Do indexing query.
+ $area = new \block_html\search\content();
+ $this->assertEquals('html', $area->get_block_name());
+ $rs = $area->get_recordset_by_timestamp();
+ $count = 0;
+ foreach ($rs as $record) {
+ $count++;
+
+ $this->assertEquals($course->id, $record->courseid);
+
+ // Check context is correct.
+ $blockcontext = \context::instance_by_id($record->contextid);
+ $this->assertInstanceOf('\context_block', $blockcontext);
+ $coursecontext = $blockcontext->get_parent_context();
+ $this->assertEquals($course->id, $coursecontext->instanceid);
+
+ // Check created and modified times are correct.
+ $this->assertTrue($record->timecreated >= $before && $record->timecreated <= $after);
+ $this->assertTrue($record->timemodified >= $before && $record->timemodified <= $after);
+
+ // Get config data.
+ $data = unserialize(base64_decode($record->configdata));
+ $this->assertEquals('Block title', $data->title);
+ $this->assertEquals('Block text', $data->text);
+ $this->assertEquals(FORMAT_HTML, $data->format);
+
+ // Check the get_document function 'new' flag.
+ $doc = $area->get_document($record, ['lastindexedtime' => 1]);
+ $this->assertTrue($doc->get_is_new());
+ $doc = $area->get_document($record, ['lastindexedtime' => time() + 1]);
+ $this->assertFalse($doc->get_is_new());
+
+ // Check the attach_files function results in correct list of associated files.
+ $this->assertCount(0, $doc->get_files());
+ $area->attach_files($doc);
+ $files = $doc->get_files();
+ $this->assertCount(2, $files);
+ foreach ($files as $file) {
+ if ($file->is_directory()) {
+ continue;
+ }
+ $this->assertEquals('file.txt', $file->get_filename());
+ $this->assertEquals('File content', $file->get_content());
+ }
+
+ // Check the document fields are all as expected.
+ $this->assertEquals('Block title', $doc->get('title'));
+ $this->assertEquals('Block text', $doc->get('content'));
+ $this->assertEquals($blockcontext->id, $doc->get('contextid'));
+ $this->assertEquals(\core_search\manager::TYPE_TEXT, $doc->get('type'));
+ $this->assertEquals($course->id, $doc->get('courseid'));
+ $this->assertEquals($record->timemodified, $doc->get('modified'));
+ $this->assertEquals(\core_search\manager::NO_OWNER_ID, $doc->get('owneruserid'));
+
+ // Also check getting the doc url and context url.
+ $url = new \moodle_url('/course/view.php', ['id' => $course->id], 'inst' . $record->id);
+ $this->assertTrue($url->compare($area->get_doc_url($doc)));
+ $this->assertTrue($url->compare($area->get_context_url($doc)));
+ }
+ $rs->close();
+
+ // Should only be one HTML block systemwide.
+ $this->assertEquals(1, $count);
+
+ // If we run the query starting from 1 second after now, there should be no results.
+ $rs = $area->get_recordset_by_timestamp($after + 1);
+ $count = 0;
+ foreach ($rs as $record) {
+ $count++;
+ }
+ $rs->close();
+ $this->assertEquals(0, $count);
+
+ // Create another block, but this time leave it empty (no data set). Hack the time though.
+ $block = $this->create_block($course);
+ $DB->set_field('block_instances', 'timemodified',
+ $after + 10, ['id' => $block->instance->id]);
+ $rs = $area->get_recordset_by_timestamp($after + 10);
+ $count = 0;
+ foreach ($rs as $record) {
+ // Because there is no configdata we don't index it.
+ $count++;
+ }
+ $rs->close();
+ $this->assertEquals(0, $count);
+ }
+}
+
$this->content->text .= '<div class="potentialidplist">';
foreach ($potentialidps as $idp) {
$this->content->text .= '<div class="potentialidp">';
- $this->content->text .= '<a class="btn btn-secondary btn-block" ';
+ $this->content->text .= '<a class="btn btn-default btn-block" ';
$this->content->text .= 'href="' . $idp['url']->out() . '" title="' . s($idp['name']) . '">';
if (!empty($idp['iconurl'])) {
$this->content->text .= '<img src="' . s($idp['iconurl']) . '" width="24" height="24" class="m-r-1"/>';
*/
function instance_config_save($data, $nolongerused = false) {
global $DB;
- $DB->set_field('block_instances', 'configdata', base64_encode(serialize($data)),
- array('id' => $this->instance->id));
+ $DB->update_record('block_instances', ['id' => $this->instance->id,
+ 'configdata' => base64_encode(serialize($data)), 'timemodified' => time()]);
}
/**
* @return array
*/
public function export_for_template(renderer_base $output) {
+ global $CFG;
+ require_once($CFG->dirroot.'/course/lib.php');
+
// Build courses view data structure.
$coursesview = [
'hascourses' => !empty($this->courses)
*/
$string['defaulttab'] = 'Default tab';
-$string['defaulttab_desc'] = 'This is the default tab that will be shown to a user.';
+$string['defaulttab_desc'] = 'The tab that will be displayed when a user first views their course overview. When returning to their course overview, the user\'s active tab is remembered.';
$string['future'] = 'Future';
$string['inprogress'] = 'In progress';
$string['morecourses'] = 'More courses';
function xmldb_block_navigation_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
And I am on "Course 1" course homepage
And I click on "Participants" "link" in the "People" "block"
Then I should see "All participants" in the "#page-content" "css_element"
- And the "My courses" select box should contain "C101"
Scenario: Student without permission can not view participants link
Given the following "permission overrides" exist:
* @param object $block
*/
function xmldb_block_quiz_results_upgrade($oldversion, $block) {
- global $DB, $CFG;
-
- if ($oldversion < 2015022200) {
- // Only migrate if the block_activity_results is installed.
- if (is_dir($CFG->dirroot . '/blocks/activity_results')) {
-
- // Migrate all instances of block_quiz_results to block_activity_results.
- $records = $DB->get_records('block_instances', array('blockname' => 'quiz_results'));
- foreach ($records as $record) {
- $configdata = '';
-
- // The block was configured.
- if (!empty($record->configdata)) {
-
- $config = unserialize(base64_decode($record->configdata));
- $config->activityparent = 'quiz';
- $config->activityparentid = isset($config->quizid) ? $config->quizid : 0;
- $config->gradeformat = isset($config->gradeformat) ? $config->gradeformat : 1;
-
- // Set the decimal valuue as appropriate.
- if ($config->gradeformat == 1) {
- // This block is using percentages, do not display any decimal places.
- $config->decimalpoints = 0;
- } else {
- // Get the decimal value from the corresponding quiz.
- $config->decimalpoints = $DB->get_field('quiz', 'decimalpoints', array('id' => $config->activityparentid));
- }
-
- // Get the grade_items record to set the activitygradeitemid.
- $info = $DB->get_record('grade_items',
- array('iteminstance' => $config->activityparentid, 'itemmodule' => $config->activityparent));
- $config->activitygradeitemid = 0;
- if ($info) {
- $config->activitygradeitemid = $info->id;
- }
-
- unset($config->quizid);
- $configdata = base64_encode(serialize($config));
- }
-
- // Save the new configuration and update the record.
- $record->configdata = $configdata;
- $record->blockname = 'activity_results';
- $DB->update_record('block_instances', $record);
- }
-
- // Disable the Quiz_results block.
- if ($block = $DB->get_record("block", array("name" => "quiz_results"))) {
- $DB->set_field("block", "visible", "0", array("id" => $block->id));
- }
-
- }
- upgrade_block_savepoint(true, 2015022200, 'quiz_results');
- }
-
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
+ global $CFG;
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
// Put any upgrade step following this.
return true;
-}
\ No newline at end of file
+}
function xmldb_block_recent_activity_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
* @return boolean
*/
function xmldb_block_rss_client_upgrade($oldversion) {
- global $DB;
- $dbman = $DB->get_manager();
-
- if ($oldversion < 2015071700) {
- // Support for skipping RSS feeds for a while when they fail.
- $table = new xmldb_table('block_rss_client');
- // How many seconds we are currently ignoring this RSS feed for (due to an error).
- $field = new xmldb_field('skiptime', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'url');
- if (!$dbman->field_exists($table, $field)) {
- $dbman->add_field($table, $field);
- }
- // When to next update this RSS feed.
- $field = new xmldb_field('skipuntil', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'skiptime');
- if (!$dbman->field_exists($table, $field)) {
- $dbman->add_field($table, $field);
- }
- upgrade_block_savepoint(true, 2015071700, 'rss_client');
- }
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
+ global $CFG;
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_block_section_links_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_block_selfcompletion_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_block_settings_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
This files describes API changes in /blocks/* - activity modules,
information provided here is intended especially for developers.
+=== 3.4 ===
+
+* The block_instances table now contains fields timecreated and timemodified. If third-party code
+ creates or updates these rows (without using the standard API), it should be modified to set
+ these fields as appropriate.
+* Blocks can now be included in Moodle global search, with some limitations (at present, the search
+ works only for blocks located directly on course pages or site home page). See the HTML block for
+ an example.
+
=== 3.3 ===
* block_manager::get_required_by_theme_block_types() is no longer static.
return $this->cachesfromdefinitions;
}
+ /**
+ * Gets all adhoc caches that have been used within this request.
+ *
+ * @return cache_store[] Caches currently in use
+ */
+ public function get_adhoc_caches_in_use() {
+ return $this->cachesfromparams;
+ }
+
/**
* Creates a cache config instance with the ability to write if required.
*
foreach ($config->get_all_stores() as $store) {
self::purge_store($store['name'], $config);
}
+ foreach ($factory->get_adhoc_caches_in_use() as $cache) {
+ $cache->purge();
+ }
}
/**
}
}
+ /**
+ * Tests that ad-hoc caches are correctly purged with a purge_all call.
+ */
+ public function test_purge_all_with_adhoc_caches() {
+ $cache = \cache::make_from_params(\cache_store::MODE_REQUEST, 'core_cache', 'test');
+ $cache->set('test', 123);
+ cache_helper::purge_all();
+ $this->assertFalse($cache->get('test'));
+ }
+
/**
* Test that the default stores all support searching.
*/
--- /dev/null
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * A javascript module to calendar events.
+ *
+ * @module core_calendar/calendar
+ * @package core_calendar
+ * @copyright 2017 Simey Lameze <simey@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/ajax', 'core/str', 'core/templates', 'core/notification', 'core/custom_interaction_events',
+ 'core/modal_factory', 'core_calendar/summary_modal', 'core_calendar/calendar_repository'],
+ function($, Ajax, Str, Templates, Notification, CustomEvents, ModalFactory, SummaryModal, CalendarRepository) {
+
+ var SELECTORS = {
+ ROOT: "[data-region='calendar']",
+ EVENT_LINK: "[data-action='view-event']",
+ };
+
+ var modalPromise = null;
+
+ /**
+ * Get the event type lang string.
+ *
+ * @param {String} eventType The event type.
+ * @return {String} The lang string of the event type.
+ */
+ var getEventType = function(eventType) {
+ var lang = 'type' + eventType;
+ return Str.get_string(lang, 'core_calendar').then(function(langStr) {
+ return langStr;
+ }).fail(Notification.exception);
+ };
+
+ /**
+ * Render the event summary modal.
+ *
+ * @param {Number} eventId The calendar event id.
+ * @return {promise} The summary modal promise.
+ */
+ var renderEventSummaryModal = function(eventId) {
+
+ var promise = CalendarRepository.getEventById(eventId);
+
+ return promise.then(function(result) {
+ if (!result.event) {
+ promise.fail(Notification.exception);
+ } else {
+ return result.event;
+ }
+ }).then(function(eventdata) {
+ return getEventType(eventdata.eventtype).then(function(langStr) {
+ eventdata.eventtype = langStr;
+ return eventdata;
+ });
+ }).then(function(eventdata) {
+ return modalPromise.done(function(modal) {
+ modal.setTitle(eventdata.name);
+ modal.setBody(Templates.render('core_calendar/event_summary_body', eventdata));
+
+ // Hide edit and delete buttons if I don't have permission.
+ if (eventdata.caneditevent == false) {
+ modal.setFooter('');
+ }
+
+ modal.show();
+ });
+ });
+ };
+
+ /**
+ * Register event listeners for the module.
+ *
+ * @param {object} root The root element.
+ */
+ var registerEventListeners = function(root) {
+ root = $(root);
+
+ var loading = false;
+ root.on('click', SELECTORS.EVENT_LINK, function(e) {
+ if (!loading) {
+ loading = true;
+ e.preventDefault();
+
+ var eventElement = $(e.target).closest(SELECTORS.EVENT_LINK);
+ var eventId = eventElement.attr('data-event-id');
+
+ renderEventSummaryModal(eventId).done(function() {
+ loading = false;
+ });
+ }
+ });
+ };
+
+ return {
+ init: function() {
+ modalPromise = ModalFactory.create({
+ type: SummaryModal.TYPE
+ });
+
+ registerEventListeners(SELECTORS.ROOT);
+ }
+ };
+ });
--- /dev/null
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * A javascript module to handle calendar ajax actions.
+ *
+ * @module core_calendar/calendar_repository
+ * @class repository
+ * @package core_calendar
+ * @copyright 2017 Simey Lameze <lameze@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/ajax'], function($, Ajax) {
+
+
+ /**
+ * Get a calendar event by id.
+ *
+ * @method getEventById
+ * @param {int} eventId The event id.
+ * @return {promise} Resolved with requested calendar event
+ */
+ var getEventById = function(eventId) {
+
+ var request = {
+ methodname: 'core_calendar_get_calendar_event_by_id',
+ args: {
+ eventid: eventId
+ }
+ };
+
+ return Ajax.call([request])[0];
+ };
+
+ return {
+ getEventById: getEventById
+ };
+});
--- /dev/null
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * A javascript module to handle summary modal.
+ *
+ * @module core_calendar/summary_modal
+ * @package core_calendar
+ * @copyright 2017 Simey Lameze <simey@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/modal', 'core/modal_registry'],
+ function($, Notification, CustomEvents, Modal, ModalRegistry) {
+
+ var registered = false;
+ var SELECTORS = {
+ EDIT_BUTTON: '[data-action="edit"]',
+ DELETE_BUTTON: '[data-action="delete"]',
+ EVENT_LINK: '[data-action="event-link"]'
+ };
+
+ /**
+ * Constructor for the Modal.
+ *
+ * @param {object} root The root jQuery element for the modal
+ */
+ var ModalEventSummary = function(root) {
+ Modal.call(this, root);
+
+ if (!this.getFooter().find(SELECTORS.EDIT_BUTTON).length) {
+ Notification.exception({message: 'No edit button found'});
+ }
+
+ if (!this.getFooter().find(SELECTORS.DELETE_BUTTON).length) {
+ Notification.exception({message: 'No delete button found'});
+ }
+ };
+
+ ModalEventSummary.TYPE = 'core_calendar-event_summary';
+ ModalEventSummary.prototype = Object.create(Modal.prototype);
+ ModalEventSummary.prototype.constructor = ModalEventSummary;
+
+ // Automatically register with the modal registry the first time this module is imported so that you can create modals
+ // of this type using the modal factory.
+ if (!registered) {
+ ModalRegistry.register(ModalEventSummary.TYPE, ModalEventSummary, 'core_calendar/event_summary_modal');
+ registered = true;
+ }
+
+ return ModalEventSummary;
+
+});
\ No newline at end of file
// have that capability set on the "Authenticated User" role rather than
// on "Student" role, which means uservisible returns true even when the user
// is no longer enrolled in the course.
- $modulecontext = \context_module::instance($cm->id);
- // A user with the 'moodle/course:view' capability is able to see courses
- // that they are not a participant in.
- $canseecourse = (has_capability('moodle/course:view', $modulecontext) || is_enrolled($modulecontext));
+ // So, with the following we are checking -
+ // 1) Only process modules if $cm->uservisible is true.
+ // 2) Only process modules for courses a user has the capability to view OR they are enrolled in.
+ // 3) Only process modules for courses that are visible OR if the course is not visible, the user
+ // has the capability to view hidden courses.
+ $coursecontext = \context_course::instance($dbrow->courseid);
+ $canseecourse = has_capability('moodle/course:view', $coursecontext) || is_enrolled($coursecontext);
+ $canseecourse = $canseecourse &&
+ ($cm->get_course()->visible || has_capability('moodle/course:viewhiddencourses', $coursecontext));
if (!$cm->uservisible || !$canseecourse) {
return true;
}
}
if ($courseid != SITEID && !empty($courseid)) {
- $course = $DB->get_record('course', array('id' => $courseid));
+ // Course ID must be valid and existing.
+ $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
$courses = array($course->id => $course);
$issite = false;
} else {
$courses = calendar_get_default_courses();
$issite = true;
}
-require_course_login($course);
+require_login($course, false);
$url = new moodle_url('/calendar/export.php', array('time' => $time));
)
);
}
+
+ /**
+ * Returns description of method parameters.
+ *
+ * @return external_function_parameters
+ */
+ public static function get_calendar_event_by_id_parameters() {
+ return new external_function_parameters(
+ array(
+ 'eventid' => new external_value(PARAM_INT, 'The event id to be retrieved'),
+ )
+ );
+ }
+ /**
+ * Get calendar event by id.
+ *
+ * @param int $eventid The calendar event id to be retrieved.
+ * @return array Array of event details
+ */
+ public static function get_calendar_event_by_id($eventid) {
+ global $CFG;
+ require_once($CFG->dirroot."/calendar/lib.php");
+
+ // Parameter validation.
+ $params = ['eventid' => $eventid];
+ $params = self::validate_parameters(self::get_calendar_event_by_id_parameters(), $params);
+
+ $warnings = array();
+
+ // We need to get events asked for eventids.
+ $event = calendar_get_events_by_id([$eventid]);
+ $eventobj = calendar_event::load($eventid);
+ $event[$eventid]->caneditevent = calendar_edit_event_allowed($eventobj);
+
+ return array('event' => $event[$eventid], 'warnings' => $warnings);
+ }
+
+ /**
+ * Returns description of method result value
+ *
+ * @return external_description
+ */
+ public static function get_calendar_event_by_id_returns() {
+
+ return new external_single_structure(array(
+ 'event' => new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_INT, 'event id'),
+ 'name' => new external_value(PARAM_TEXT, 'event name'),
+ 'description' => new external_value(PARAM_RAW, 'Description', VALUE_OPTIONAL, null, NULL_ALLOWED),
+ 'format' => new external_format_value('description'),
+ 'courseid' => new external_value(PARAM_INT, 'course id'),
+ 'groupid' => new external_value(PARAM_INT, 'group id'),
+ 'userid' => new external_value(PARAM_INT, 'user id'),
+ 'repeatid' => new external_value(PARAM_INT, 'repeat id'),
+ 'modulename' => new external_value(PARAM_TEXT, 'module name', VALUE_OPTIONAL, null, NULL_ALLOWED),
+ 'instance' => new external_value(PARAM_INT, 'instance id'),
+ 'eventtype' => new external_value(PARAM_TEXT, 'Event type'),
+ 'timestart' => new external_value(PARAM_INT, 'timestart'),
+ 'timeduration' => new external_value(PARAM_INT, 'time duration'),
+ 'visible' => new external_value(PARAM_INT, 'visible'),
+ 'uuid' => new external_value(PARAM_TEXT, 'unique id of ical events', VALUE_OPTIONAL, null, NULL_NOT_ALLOWED),
+ 'sequence' => new external_value(PARAM_INT, 'sequence'),
+ 'timemodified' => new external_value(PARAM_INT, 'time modified'),
+ 'subscriptionid' => new external_value(PARAM_INT, 'Subscription id', VALUE_OPTIONAL, null, NULL_ALLOWED),
+ 'caneditevent' => new external_value(PARAM_BOOL, 'Whether the user can edit the event'),
+ ),
+ 'event'
+ ),
+ 'warnings' => new external_warnings()
+ )
+ );
+ }
}
$name = format_string($event->name, true);
}
}
+ // Include course's shortname into the event name, if applicable.
+ if (!empty($event->courseid) && $event->courseid !== SITEID) {
+ $course = get_course($event->courseid);
+ $eventnameparams = (object)[
+ 'name' => $name,
+ 'course' => format_string($course->shortname, true, array('context' => $event->context))
+ ];
+ $name = get_string('eventnameandcourse', 'calendar', $eventnameparams);
+ }
$popupcontent .= \html_writer::link($dayhref, $name);
$popupcontent .= \html_writer::end_tag('div');
}
$PAGE->navbar->add(get_string('managesubscriptions', 'calendar'));
if ($courseid != SITEID && !empty($courseid)) {
- $course = $DB->get_record('course', array('id' => $courseid));
+ // Course ID must be valid and existing.
+ $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
$courses = array($course->id => $course);
} else {
$course = get_site();
$courses = calendar_get_default_courses();
}
-require_course_login($course);
+require_login($course, false);
+
if (!calendar_user_can_add_event($course)) {
print_error('errorcannotimport', 'calendar');
}
* @return string
*/
public function start_layout() {
- return html_writer::start_tag('div', array('class'=>'maincalendar'));
+ return html_writer::start_tag('div', ['data-region' => 'calendar', 'class' => 'maincalendar']);
}
/**
$attributes['class'] = $events[$eventindex]->class;
}
$dayhref->set_anchor('event_'.$events[$eventindex]->id);
- $link = html_writer::link($dayhref, format_string($events[$eventindex]->name, true));
+
+ $eventcontext = $events[$eventindex]->context;
+ $eventformatopts = array('context' => $eventcontext);
+ // Get event name.
+ $eventname = format_string($events[$eventindex]->name, true, $eventformatopts);
+ // Include course's shortname into the event name, if applicable.
+ $courseid = $events[$eventindex]->courseid;
+ if (!empty($courseid) && $courseid !== SITEID) {
+ $course = get_course($courseid);
+ $eventnameparams = (object)[
+ 'name' => $eventname,
+ 'course' => format_string($course->shortname, true, $eventformatopts)
+ ];
+ $eventname = get_string('eventnameandcourse', 'calendar', $eventnameparams);
+ }
+ $link = html_writer::link($dayhref, $eventname, ['data-action' => 'view-event',
+ 'data-event-id' => $events[$eventindex]->id]);
$cell->text .= html_writer::tag('li', $link, $attributes);
}
$cell->text .= html_writer::end_tag('ul');
--- /dev/null
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+ @template core_calendar/event_summary_body
+
+ This template renders the body of calendar events summary modal.
+
+ Example context (json):
+ {
+ "timestart": 1490320388,
+ "description": "An random event description",
+ "eventtype": "open",
+ }
+}}
+<div class="container">
+ <h4>{{#str}} when, core_calendar {{/str}}</h4>
+ {{#userdate}} {{timestart}}, {{#str}} strftimerecentfull {{/str}} {{/userdate}}
+ <br>
+ <h4>{{#str}} description {{/str}}</h4>
+ {{{description}}}
+ <h4>{{#str}} eventtype, core_calendar {{/str}}</h4>
+ {{eventtype}}
+</div>
--- /dev/null
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+ @template core_calendar/event_summary_modal
+
+ This template renders the calendar events summary modal.
+
+ Example context (json):
+ {
+ "title": "Assignment due 1",
+ }
+}}
+{{< core/modal }}
+{{$footer}}
+<button type="button" class="btn btn-secondary pull-xs-left" data-action="delete">{{#str}} delete {{/str}}</button>
+<button type="button" class="btn btn-primary" data-action="edit">{{#str}} edit {{/str}}</button>
+{{/footer}}
+{{/ core/modal }}
$this->assertNull($event);
}
+ /**
+ * Test that the event factory deals with invisible courses as an admin.
+ *
+ * @dataProvider get_event_factory_testcases()
+ * @param \stdClass $dbrow Row from the "database".
+ */
+ public function test_event_factory_when_course_visibility_is_toggled_as_admin($dbrow) {
+ $legacyevent = $this->create_event($dbrow);
+ $factory = \core_calendar\local\event\container::get_event_factory();
+
+ // Create a hidden course with an assignment.
+ $course = $this->getDataGenerator()->create_course(['visible' => 0]);
+ $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+ $moduleinstance = $generator->create_instance(['course' => $course->id]);
+
+ $dbrow->id = $legacyevent->id;
+ $dbrow->courseid = $course->id;
+ $dbrow->instance = $moduleinstance->id;
+ $dbrow->modulename = 'assign';
+ $event = $factory->create_instance($dbrow);
+
+ // Module is still visible to admins even if the course is invisible.
+ $this->assertInstanceOf(event_interface::class, $event);
+ }
+
+ /**
+ * Test that the event factory deals with invisible courses as a student.
+ *
+ * @dataProvider get_event_factory_testcases()
+ * @param \stdClass $dbrow Row from the "database".
+ */
+ public function test_event_factory_when_course_visibility_is_toggled_as_student($dbrow) {
+ $legacyevent = $this->create_event($dbrow);
+ $factory = \core_calendar\local\event\container::get_event_factory();
+
+ // Create a hidden course with an assignment.
+ $course = $this->getDataGenerator()->create_course(['visible' => 0]);
+ $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+ $moduleinstance = $generator->create_instance(['course' => $course->id]);
+
+ // Enrol a student into this course.
+ $student = $this->getDataGenerator()->create_user();
+ $this->getDataGenerator()->enrol_user($student->id, $course->id);
+
+ // Set the user to the student.
+ $this->setUser($student);
+
+ $dbrow->id = $legacyevent->id;
+ $dbrow->courseid = $course->id;
+ $dbrow->instance = $moduleinstance->id;
+ $dbrow->modulename = 'assign';
+ $event = $factory->create_instance($dbrow);
+
+ // Module is invisible to students if the course is invisible.
+ $this->assertNull($event);
+ }
+
/**
* Test that the event factory deals with completion related events properly.
*/
$this->assertInstanceOf(event_interface::class, $factory->create_instance($event));
}
+ /**
+ * Test that when course module is deleted all events are also deleted.
+ */
+ public function test_delete_module_delete_events() {
+ global $DB;
+ $user = $this->getDataGenerator()->create_user();
+ // Create the course we will be using.
+ $course = $this->getDataGenerator()->create_course();
+ $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
+
+ foreach (core_component::get_plugin_list('mod') as $modname => $unused) {
+ try {
+ $generator = $this->getDataGenerator()->get_plugin_generator('mod_'.$modname);
+ } catch (coding_exception $e) {
+ // Module generator is not implemented.
+ continue;
+ }
+ $module = $generator->create_instance(['course' => $course->id]);
+
+ // Create bunch of events of different type (user override, group override, module event).
+ $this->create_event(['userid' => $user->id, 'modulename' => $modname, 'instance' => $module->id]);
+ $this->create_event(['groupid' => $group->id, 'modulename' => $modname, 'instance' => $module->id]);
+ $this->create_event(['modulename' => $modname, 'instance' => $module->id]);
+ $this->create_event(['modulename' => $modname, 'instance' => $module->id, 'courseid' => $course->id]);
+
+ // Delete module and make sure all events are deleted.
+ course_delete_module($module->cmid);
+ $this->assertEmpty($DB->get_record('event', ['modulename' => $modname, 'instance' => $module->id]));
+ }
+ }
+
/**
* Test getting the event mapper.
*/
$PAGE->set_url($url);
if ($courseid != SITEID && !empty($courseid)) {
- $course = $DB->get_record('course', array('id' => $courseid));
+ // Course ID must be valid and existing.
+ $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
$courses = array($course->id => $course);
$issite = false;
navigation_node::override_active_url(new moodle_url('/course/view.php', array('id' => $course->id)));
$issite = true;
}
-require_course_login($course);
+require_login($course, false);
$calendar = new calendar_information(0, 0, 0, $time);
$calendar->prepare_for_view($course, $courses);
echo $OUTPUT->container_end();
echo html_writer::end_tag('div');
echo $renderer->complete_layout();
+$PAGE->requires->js_call_amd('core_calendar/calendar', 'init');
echo $OUTPUT->footer();
*
* @param int $cmid The course module id
* @param string $modulename The name of the module (eg. assign, quiz)
- * @param int $instanceid The instance idLOL
+ * @param stdClass|int $instanceorid The instance object or ID.
* @param int|null $completionexpectedtime The time completion is expected, null if not set
* @return bool
*/
- public static function update_completion_date_event($cmid, $modulename, $instanceid, $completionexpectedtime) {
+ public static function update_completion_date_event($cmid, $modulename, $instanceorid, $completionexpectedtime) {
global $CFG, $DB;
// Required for calendar constant CALENDAR_EVENT_TYPE_ACTION.
require_once($CFG->dirroot . '/calendar/lib.php');
- $instance = $DB->get_record($modulename, array('id' => $instanceid), '*', MUST_EXIST);
- $course = $DB->get_record('course', array('id' => $instance->course), '*', MUST_EXIST);
+ $instance = null;
+ if (is_object($instanceorid)) {
+ $instance = $instanceorid;
+ } else {
+ $instance = $DB->get_record($modulename, array('id' => $instanceorid), '*', MUST_EXIST);
+ }
+ $course = get_course($instance->course);
$completion = new \completion_info($course);
$needreset[] = $cm->id;
}
}
+ // Update completion calendar events.
+ $completionexpected = ($data['completionexpected']) ? $data['completionexpected'] : null;
+ \core_completion\api::update_completion_date_event($cm->id, $cm->modname, $cm->instance, $completionexpected);
}
if ($updated) {
// Now that modules are fully updated, also update completion data if required.
// Create the completion event.
$CFG->enablecompletion = true;
- \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign->id, $time);
+ \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time);
// Check that there is now an event in the database.
$events = $DB->get_records('event');
// Create the event.
$CFG->enablecompletion = true;
- \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign->id, $time);
+ \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time);
// Call it again, but this time with a different time.
- \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign->id, $time + DAYSECS);
+ \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time + DAYSECS);
// Check that there is still only one event in the database.
$events = $DB->get_records('event');
// Create the event.
$CFG->enablecompletion = true;
- \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign->id, $time);
+ \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time);
// Call it again, but the time specified as null.
- \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign->id, null);
+ \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, null);
// Check that there is no event in the database.
$this->assertEquals(0, $DB->count_records('event'));
// Try and create the completion event with completion disabled.
$CFG->enablecompletion = false;
- \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign->id, $time);
+ \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time);
// Check that there is no event in the database.
$this->assertEquals(0, $DB->count_records('event'));
// Create the completion event.
$CFG->enablecompletion = true;
- \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign->id, $time);
+ \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time);
// Disable completion.
$CFG->enablecompletion = false;
// Try and update the completion date.
- \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign->id, $time + DAYSECS);
+ \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time + DAYSECS);
// Check that there is an event in the database.
$events = $DB->get_records('event');
// Create the completion event.
$CFG->enablecompletion = true;
- \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign->id, $time);
+ \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, $time);
// Disable completion.
$CFG->enablecompletion = false;
// Should still be able to delete completion events even when completion is disabled.
- \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign->id, null);
+ \core_completion\api::update_completion_date_event($assign->cmid, 'assign', $assign, null);
// Check that there is now no event in the database.
$this->assertEquals(0, $DB->count_records('event'));
grade_update_mod_grades($grademodule);
// Update calendar events with the new name.
- $refresheventsfunction = $cm->modname . '_refresh_events';
- if (function_exists($refresheventsfunction)) {
- call_user_func($refresheventsfunction, $cm->course);
- }
+ course_module_update_calendar_events($cm->modname, $grademodule, $cm);
return true;
}
// Delete events from calendar.
if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename))) {
+ $coursecontext = context_course::instance($cm->course);
foreach($events as $event) {
- $calendarevent = calendar_event::load($event->id);
+ $event->context = $coursecontext;
+ $calendarevent = calendar_event::load($event);
$calendarevent->delete();
}
}
return false;
}
+/**
+ * This function updates the calendar events from the information stored in the module table and the course
+ * module table.
+ *
+ * @param string $modulename Module name
+ * @param stdClass $instance Module object. Either the $instance or the $cm must be supplied.
+ * @param stdClass $cm Course module object. Either the $instance or the $cm must be supplied.
+ * @return bool Returns true if calendar events are updated.
+ * @since Moodle 3.3.4
+ */
+function course_module_update_calendar_events($modulename, $instance = null, $cm = null) {
+ global $DB;
+
+ if (isset($instance) || isset($cm)) {
+
+ if (!isset($instance)) {
+ $instance = $DB->get_record($modulename, array('id' => $cm->instance), '*', MUST_EXIST);
+ }
+ if (!isset($cm)) {
+ $cm = get_coursemodule_from_instance($modulename, $instance->id, $instance->course);
+ }
+ course_module_calendar_event_update_process($instance, $cm);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Update all instances through out the site or in a course.
+ *
+ * @param string $modulename Module type to update.
+ * @param integer $courseid Course id to update events. 0 for the whole site.
+ * @return bool Returns True if the update was successful.
+ * @since Moodle 3.3.4
+ */
+function course_module_bulk_update_calendar_events($modulename, $courseid = 0) {
+ global $DB;
+
+ $instances = null;
+ if ($courseid) {
+ if (!$instances = $DB->get_records($modulename, array('course' => $courseid))) {
+ return false;
+ }
+ } else {
+ if (!$instances = $DB->get_records($modulename)) {
+ return false;
+ }
+ }
+
+ foreach ($instances as $instance) {
+ $cm = get_coursemodule_from_instance($modulename, $instance->id, $instance->course);
+ course_module_calendar_event_update_process($instance, $cm);
+ }
+ return true;
+}
+
+/**
+ * Calendar events for a module instance are updated.
+ *
+ * @param stdClass $instance Module instance object.
+ * @param stdClass $cm Course Module object.
+ * @since Moodle 3.3.4
+ */
+function course_module_calendar_event_update_process($instance, $cm) {
+ // We need to call *_refresh_events() first because some modules delete 'old' events at the end of the code which
+ // will remove the completion events.
+ $refresheventsfunction = $cm->modname . '_refresh_events';
+ if (function_exists($refresheventsfunction)) {
+ call_user_func($refresheventsfunction, $cm->course, $instance, $cm);
+ }
+ $completionexpected = (!empty($cm->completionexpected)) ? $cm->completionexpected : null;
+ \core_completion\api::update_completion_date_event($cm->id, $cm->modname, $instance, $completionexpected);
+}
+
/**
* Moves a section within a course, from a position to another.
* Be very careful: $section and $destination refer to section number,
$data->visibleold = $data->visible;
$data->lang = $courseconfig->lang;
$data->enablecompletion = $courseconfig->enablecompletion;
+ $data->numsections = $courseconfig->numsections;
$course = create_course($data);
$context = context_course::instance($course->id, MUST_EXIST);
moveto_module($cm, $section, $newcm);
// Update calendar events with the duplicated module.
- $refresheventsfunction = $newcm->modname . '_refresh_events';
- if (function_exists($refresheventsfunction)) {
- call_user_func($refresheventsfunction, $newcm->course);
- }
+ // The following line is to be removed in MDL-58906.
+ course_module_update_calendar_events($newcm->modname, null, $newcm);
// Trigger course module created event. We can trigger the event only if we know the newcmid.
$event = \core\event\course_module_created::create_from_cm($newcm);
// //
///////////////////////////////////////////////////////////////////////////
-/*
+/**
+ * This page display the publication backup form
+ *
* @package course
* @subpackage publish
* @author Jerome Mouneyrac <jerome@mouneyrac.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
* @copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com
- *
- * This page display the publication backup form
*/
+define('NO_OUTPUT_BUFFERING', true);
+
require_once('../../config.php');
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/moodle2/backup_plan_builder.class.php');
public function course_section_cm_completion($course, &$completioninfo, cm_info $mod, $displayoptions = array()) {
global $CFG;
$output = '';
- if (!$mod->is_visible_on_course_page()) {
+ if (!empty($displayoptions['hidecompletion']) || !isloggedin() || isguestuser() || !$mod->uservisible) {
return $output;
}
if ($completioninfo === null) {
foreach ($roles as $key => $role) {
$url = new moodle_url('/course/switchrole.php', array('id' => $id, 'switchrole' => $key, 'returnurl' => $returnurl));
- echo $OUTPUT->container($OUTPUT->single_button($url, $role), 'm-x-3 m-b-1');
+ // Button encodes special characters, apply htmlspecialchars_decode() to avoid double escaping.
+ echo $OUTPUT->container($OUTPUT->single_button($url, htmlspecialchars_decode($role)), 'm-x-3 m-b-1');
}
$url = new moodle_url($returnurl);
$this->resetAfterTest(true);
+ $this->setAdminUser();
+
$CFG->enablecompletion = true;
$this->setTimezone('UTC');
$this->assertEquals(COURSE_TIMELINE_PAST, course_classify_for_timeline($completedcourse));
$this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($inprogresscourse));
}
+
+ /**
+ * Test the main function for updating all calendar events for a module.
+ */
+ public function test_course_module_calendar_event_update_process() {
+ global $DB;
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ $completionexpected = time();
+ $duedate = time();
+
+ $course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
+ $assign = $this->getDataGenerator()->create_module('assign', [
+ 'course' => $course,
+ 'completionexpected' => $completionexpected,
+ 'duedate' => $duedate
+ ]);
+
+ $cm = get_coursemodule_from_instance('assign', $assign->id, $course->id);
+ $events = $DB->get_records('event', ['courseid' => $course->id, 'instance' => $assign->id]);
+ // Check that both events are using the expected dates.
+ foreach ($events as $event) {
+ if ($event->eventtype == \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) {
+ $this->assertEquals($completionexpected, $event->timestart);
+ }
+ if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) {
+ $this->assertEquals($duedate, $event->timestart);
+ }
+ }
+
+ // We have to manually update the module and the course module.
+ $newcompletionexpected = time() + DAYSECS * 60;
+ $newduedate = time() + DAYSECS * 45;
+ $newmodulename = 'Assign - new name';
+
+ $moduleobject = (object)array('id' => $assign->id, 'duedate' => $newduedate, 'name' => $newmodulename);
+ $DB->update_record('assign', $moduleobject);
+ $cmobject = (object)array('id' => $cm->id, 'completionexpected' => $newcompletionexpected);
+ $DB->update_record('course_modules', $cmobject);
+
+ $assign = $DB->get_record('assign', ['id' => $assign->id]);
+ $cm = get_coursemodule_from_instance('assign', $assign->id, $course->id);
+
+ course_module_calendar_event_update_process($assign, $cm);
+
+ $events = $DB->get_records('event', ['courseid' => $course->id, 'instance' => $assign->id]);
+ // Now check that the details have been updated properly from the function.
+ foreach ($events as $event) {
+ if ($event->eventtype == \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) {
+ $this->assertEquals($newcompletionexpected, $event->timestart);
+ $this->assertEquals(get_string('completionexpectedfor', 'completion', (object)['instancename' => $newmodulename]),
+ $event->name);
+ }
+ if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) {
+ $this->assertEquals($newduedate, $event->timestart);
+ $this->assertEquals($newmodulename, $event->name);
+ }
+ }
+ }
+
+ /**
+ * Test the higher level checks for updating calendar events for an instance.
+ */
+ public function test_course_module_update_calendar_events() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ $completionexpected = time();
+ $duedate = time();
+
+ $course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
+ $assign = $this->getDataGenerator()->create_module('assign', [
+ 'course' => $course,
+ 'completionexpected' => $completionexpected,
+ 'duedate' => $duedate
+ ]);
+
+ $cm = get_coursemodule_from_instance('assign', $assign->id, $course->id);
+
+ // Both the instance and cm objects are missing.
+ $this->assertFalse(course_module_update_calendar_events('assign'));
+ // Just using the assign instance.
+ $this->assertTrue(course_module_update_calendar_events('assign', $assign));
+ // Just using the course module object.
+ $this->assertTrue(course_module_update_calendar_events('assign', null, $cm));
+ // Using both the assign instance and the course module object.
+ $this->assertTrue(course_module_update_calendar_events('assign', $assign, $cm));
+ }
+
+ /**
+ * Test the higher level checks for updating calendar events for a module.
+ */
+ public function test_course_module_bulk_update_calendar_events() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ $completionexpected = time();
+ $duedate = time();
+
+ $course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
+ $course2 = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
+ $assign = $this->getDataGenerator()->create_module('assign', [
+ 'course' => $course,
+ 'completionexpected' => $completionexpected,
+ 'duedate' => $duedate
+ ]);
+
+ // No assign instances in this course.
+ $this->assertFalse(course_module_bulk_update_calendar_events('assign', $course2->id));
+ // No book instances for the site.
+ $this->assertFalse(course_module_bulk_update_calendar_events('book'));
+ // Update all assign instances.
+ $this->assertTrue(course_module_bulk_update_calendar_events('assign'));
+ // Update the assign instances for this course.
+ $this->assertTrue(course_module_bulk_update_calendar_events('assign', $course->id));
+ }
}
core_course_external::update_categories($categories);
}
+ /**
+ * Test create_courses numsections
+ */
+ public function test_create_course_numsections() {
+ global $DB;
+
+ $this->resetAfterTest(true);
+
+ // Set the required capabilities by the external function.
+ $contextid = context_system::instance()->id;
+ $roleid = $this->assignUserCapability('moodle/course:create', $contextid);
+ $this->assignUserCapability('moodle/course:visibility', $contextid, $roleid);
+
+ $numsections = 10;
+ $category = self::getDataGenerator()->create_category();
+
+ // Create base categories.
+ $course1['fullname'] = 'Test course 1';
+ $course1['shortname'] = 'Testcourse1';
+ $course1['categoryid'] = $category->id;
+ $course1['courseformatoptions'][] = array('name' => 'numsections', 'value' => $numsections);
+
+ $courses = array($course1);
+
+ $createdcourses = core_course_external::create_courses($courses);
+ foreach ($createdcourses as $createdcourse) {
+ $existingsections = $DB->get_records('course_sections', array('course' => $createdcourse['id']));
+ $modinfo = get_fast_modinfo($createdcourse['id']);
+ $sections = $modinfo->get_section_info_all();
+ $this->assertEquals(count($sections), $numsections + 1); // Includes generic section.
+ $this->assertEquals(count($existingsections), $numsections + 1); // Includes generic section.
+ }
+ }
+
/**
* Test create_courses
*/
public $extension = ".html";
/**
- * Write the start of the format
- *
- * @param array $columns
+ * Write the start of the output
*/
- public function write_header($columns) {
+ public function start_output() {
echo "<!DOCTYPE html><html>";
echo \html_writer::tag('title', $this->filename);
echo "<style>
margin: auto;
}
</style>
-<body>
-<table border=1 cellspacing=0 cellpadding=3>
-";
+<body>";
+ }
+
+ /**
+ * Write the start of the sheet we will be adding data to.
+ *
+ * @param array $columns
+ */
+ public function start_sheet($columns) {
+ echo "<table border=1 cellspacing=0 cellpadding=3>";
echo \html_writer::start_tag('tr');
foreach ($columns as $k => $v) {
echo \html_writer::tag('th', $v);
}
/**
- * Write the end of the format
+ * Write the end of the sheet containing the data.
*
* @param array $columns
*/
- public function write_footer($columns) {
- echo "</table></body></html>";
+ public function close_sheet($columns) {
+ echo "</table>";
}
+ /**
+ * Write the end of the sheet containing the data.
+ */
+ public function close_output() {
+ echo "</body></html>";
+ }
}
/** @var $extension */
public $extension = ".json";
+ /** @var $sheetstarted */
+ public $sheetstarted = false;
+
+ /** @var $sheetdatadded */
+ public $sheetdatadded = false;
+
+ /**
+ * Write the start of the file.
+ */
+ public function start_output() {
+ echo "[";
+ }
+
/**
- * Write the start of the format
+ * Write the start of the sheet we will be adding data to.
*
* @param array $columns
*/
- public function write_header($columns) {
+ public function start_sheet($columns) {
+ if ($this->sheetstarted) {
+ echo ",";
+ } else {
+ $this->sheetstarted = true;
+ }
+ $this->sheetdatadded = false;
echo "[";
}
* @param int $rownum
*/
public function write_record($record, $rownum) {
- if ($rownum) {
+ if ($this->sheetdatadded) {
echo ",";
}
+
echo json_encode($record);
+
+ $this->sheetdatadded = true;
}
/**
- * Write the end of the format
+ * Write the end of the sheet containing the data.
*
* @param array $columns
*/
- public function write_footer($columns) {
+ public function close_sheet($columns) {
echo "]";
}
+ /**
+ * Write the end of the file.
+ */
+ public function close_output() {
+ echo "]";
+ }
}
This files describes API changes in /dataformat/ download system,
information provided here is intended especially for developers.
-=== 3.1 ===
-* Added new plugin system with low memory support for csv, ods, xls and json
+=== 3.4 ===
+* In order to allow multiple sheets in an exported file the functions write_header() and write_footer() have
+ been removed from core dataformat plugins and have been replaced.
+ - write_header() has been replaced with the two functions start_output() and start_sheet().
+ - write_footer() has been replaced with the two functions close_output() and close_sheet().
+ For backwards compatibility write_header() and write_footer() will continue to work but if used will
+ trigger the function error_log().
+=== 3.1 ===
+* Added new plugin system with low memory support for csv, ods, xls and json
function xmldb_enrol_database_upgrade($oldversion) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_enrol_flatfile_upgrade($oldversion) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_enrol_guest_upgrade($oldversion) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
$heading = $plugin->get_instance_name($instance);
$mform->addElement('header', 'guestheader', $heading);
- $mform->addElement('passwordunmask', 'guestpassword', get_string('password', 'enrol_guest'));
+ $mform->addElement('password', 'guestpassword', get_string('password', 'enrol_guest'));
$this->add_action_buttons(false, get_string('submit'));
function xmldb_enrol_imsenterprise_upgrade($oldversion) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
return;
}
- $ldap_pagedresults = ldap_paged_results_supported($this->get_config('ldap_version'));
+ $ldap_pagedresults = ldap_paged_results_supported($this->get_config('ldap_version'), $this->ldapconnection);
// we may need a lot of memory here
core_php_time_limit::raise();
// Get all contexts and look for first matching user
$ldap_contexts = explode(';', $ldap_contexts);
- $ldap_pagedresults = ldap_paged_results_supported($this->get_config('ldap_version'));
+ $ldap_pagedresults = ldap_paged_results_supported($this->get_config('ldap_version'), $this->ldapconnection);
foreach ($ldap_contexts as $context) {
$context = trim($context);
if (empty($context)) {
function xmldb_enrol_manual_upgrade($oldversion) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- if ($oldversion < 2015091500) {
- // We keep today as default enrolment start time on upgrades.
- set_config('enrolstart', 3, 'enrol_manual');
- upgrade_plugin_savepoint(true, 2015091500, 'enrol', 'manual');
- }
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_enrol_mnet_upgrade($oldversion) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_enrol_paypal_upgrade($oldversion) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
defined('MOODLE_INTERNAL') || die();
function xmldb_enrol_self_upgrade($oldversion) {
- global $CFG;
-
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
+ global $CFG, $DB;
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2016052301) {
- global $DB;
// Get roles with manager archetype.
$managerroles = get_archetype_roles('manager');
if (!empty($managerroles)) {
if ($instance->password) {
// Change the id of self enrolment key input as there can be multiple self enrolment methods.
- $mform->addElement('passwordunmask', 'enrolpassword', get_string('password', 'enrol_self'),
+ $mform->addElement('password', 'enrolpassword', get_string('password', 'enrol_self'),
array('id' => 'enrolpassword_'.$instance->id));
$context = context_course::instance($this->instance->courseid);
$keyholders = get_users_by_capability($context, 'enrol/self:holdkey', user_picture::fields('u'));
* @return bool result
*/
function xmldb_filter_mathjaxloader_upgrade($oldversion) {
- global $CFG, $DB;
+ global $CFG;
require_once($CFG->dirroot . '/filter/mathjaxloader/db/upgradelib.php');
- $dbman = $DB->get_manager();
-
- if ($oldversion < 2014081100) {
-
- $sslcdnurl = get_config('filter_mathjaxloader', 'httpsurl');
- if ($sslcdnurl === "https://c328740.ssl.cf1.rackcdn.com/mathjax/2.3-latest/MathJax.js") {
- set_config('httpsurl', 'https://cdn.mathjax.org/mathjax/2.3-latest/MathJax.js', 'filter_mathjaxloader');
- }
-
- upgrade_plugin_savepoint(true, 2014081100, 'filter', 'mathjaxloader');
- }
-
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- if ($oldversion < 2015021200) {
-
- $httpurl = get_config('filter_mathjaxloader', 'httpurl');
- // Don't change the config if it has been manually changed to something besides the default setting value.
- if ($httpurl === "http://cdn.mathjax.org/mathjax/2.3-latest/MathJax.js") {
- set_config('httpurl', 'http://cdn.mathjax.org/mathjax/2.5-latest/MathJax.js', 'filter_mathjaxloader');
- }
-
- $httpsurl = get_config('filter_mathjaxloader', 'httpsurl');
- // Don't change the config if it has been manually changed to something besides the default setting value.
- if ($httpsurl === "https://cdn.mathjax.org/mathjax/2.3-latest/MathJax.js") {
- set_config('httpsurl', 'https://cdn.mathjax.org/mathjax/2.5-latest/MathJax.js', 'filter_mathjaxloader');
- }
-
- upgrade_plugin_savepoint(true, 2015021200, 'filter', 'mathjaxloader');
- }
-
- if ($oldversion < 2015021700) {
-
- $oldconfig = get_config('filter_mathjaxloader', 'mathjaxconfig');
- $olddefault = 'MathJax.Hub.Config({
- config: ["MMLorHTML.js", "Safe.js"],
- jax: ["input/TeX","input/MathML","output/HTML-CSS","output/NativeMML"],
- extensions: ["tex2jax.js","mml2jax.js","MathMenu.js","MathZoom.js"],
- TeX: {
- extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"]
- },
- menuSettings: {
- zoom: "Double-Click",
- mpContext: true,
- mpMouse: true
- },
- errorSettings: { message: ["!"] },
- skipStartupTypeset: true,
- messageStyle: "none"
-});
-';
- $newdefault = '
-MathJax.Hub.Config({
- config: ["Accessible.js", "Safe.js"],
- errorSettings: { message: ["!"] },
- skipStartupTypeset: true,
- messageStyle: "none"
-});
-';
-
- // Ignore white space changes.
- $oldconfig = trim(preg_replace('/\s+/', ' ', $oldconfig));
- $olddefault = trim(preg_replace('/\s+/', ' ', $olddefault));
-
- // Update the default config for mathjax only if it has not been customised.
-
- if ($oldconfig == $olddefault) {
- set_config('mathjaxconfig', $newdefault, 'filter_mathjaxloader');
- }
-
- upgrade_plugin_savepoint(true, 2015021700, 'filter', 'mathjaxloader');
- }
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
if ($oldversion < 2016032200) {
$httpurl = get_config('filter_mathjaxloader', 'httpurl');
function xmldb_filter_mediaplugin_upgrade($oldversion) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_filter_tex_upgrade($oldversion) {
global $CFG;
- // Moodle v2.8.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v2.9.0 release upgrade line.
- // Put any upgrade step following this.
-
- // Moodle v3.0.0 release upgrade line.
- // Put any upgrade step following this.
-
// Moodle v3.1.0 release upgrade line.
// Put any upgrade step following this.
/// Make sure they can even access this course
if ($courseid) {
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
$context = context_course::instance($course->id);
/// Make sure they can even access this course
if ($courseid) {
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
$context = context_course::instance($course->id);
$heading = get_string('addscale', 'grades');
/// adding new scale from course
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
$scale_rec = new stdClass();
$scale_rec->standard = 0;
/// Make sure they can even access this course
if ($courseid) {
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
$context = context_course::instance($course->id);
$PAGE->set_pagelayout('admin');
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
$context = context_course::instance($course->id);
/// Make sure they can even access this course
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
$context = context_course::instance($course->id);
$PAGE->set_url($url);
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
array('id'=>$courseid)));
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
$PAGE->set_url($url);
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
$PAGE->set_pagelayout('incourse');
/// Make sure they can even access this course
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
array('id'=>$courseid)));
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
array('id'=>$courseid)));
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
$PAGE->set_url('/grade/export/keymanager.php', array('id' => $id));
if (!$course = $DB->get_record('course', array('id'=>$id))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
$onlyactive = optional_param('export_onlyactive', 0, PARAM_BOOL);
if (!$course = $DB->get_record('course', array('id'=>$id))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_user_key_login('grade/export', $id); // we want different keys for each course
$PAGE->set_url('/grade/export/ods/export.php', array('id'=>$id));
if (!$course = $DB->get_record('course', array('id'=>$id))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
$PAGE->set_url('/grade/export/ods/index.php', array('id'=>$id));
if (!$course = $DB->get_record('course', array('id'=>$id))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
$onlyactive = optional_param('export_onlyactive', 0, PARAM_BOOL);
if (!$course = $DB->get_record('course', array('id'=>$id))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_user_key_login('grade/export', $id); // we want different keys for each course
$PAGE->set_url('/grade/export/txt/export.php', array('id'=>$id));
if (!$course = $DB->get_record('course', array('id'=>$id))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
$PAGE->set_url('/grade/export/txt/index.php', array('id'=>$id));
if (!$course = $DB->get_record('course', array('id'=>$id))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_login($course);
$onlyactive = optional_param('export_onlyactive', 0, PARAM_BOOL);
if (!$course = $DB->get_record('course', array('id'=>$id))) {
- print_error('nocourseid');
+ print_error('invalidcourseid');
}
require_user_key_login('grade/export', $id); // we want different keys for each course
$PAGE->set_url('/grade/export/xls/export.php', array('id'=>$id));
if (!$course = $DB->get_record('course', array('id'=>$id))) {
- print_error('nocourseid');
+ print_error('