**/yui/build/** -diff
**/amd/build/** -diff
+lib/dml/oci_native_moodle_package.sql text eol=lf
theme/bootstrapbase/style/editor.css -diff
theme/bootstrapbase/style/moodle.css -diff
- mysql-client-core-5.6
- mysql-client-5.6
-services:
- - redis-server
+# Redis tests are currently failing on php 7.2 due to https://bugs.php.net/bug.php?id=75628
+# services:
+# - redis-server
env:
# Although we want to run these jobs and see failures as quickly as possible, we also want to get the slowest job to
fi
# Enable Redis.
- echo 'extension="redis.so"' > /tmp/redis.ini
- phpenv config-add /tmp/redis.ini
+ # Redis tests are currently failing on php 7.2 due to https://bugs.php.net/bug.php?id=75628
+ # echo 'extension="redis.so"' > /tmp/redis.ini
+ # phpenv config-add /tmp/redis.ini
# Install composer dependencies.
# We need --no-interaction in case we hit API limits for composer. This causes it to fall back to a standard clone.
mkdir -p "$HOME"/roots/phpunit
# The phpunit dataroot and prefix..
+ # Redis tests are currently failing on php 7.2 due to https://bugs.php.net/bug.php?id=75628
+ # -e "/require_once/i \\define('TEST_SESSION_REDIS_HOST', '127.0.0.1');" \
sed -i \
-e "/require_once/i \\\$CFG->phpunit_dataroot = '\/home\/travis\/roots\/phpunit';" \
-e "/require_once/i \\\$CFG->phpunit_prefix = 'p_';" \
- -e "/require_once/i \\define('TEST_SESSION_REDIS_HOST', '127.0.0.1');" \
config.php ;
# Initialise PHPUnit for Moodle.
// If custom field then pick name from database.
$fieldshortname = str_replace('profile_field_', '', $fieldname);
$fieldname = $customfieldname[$fieldshortname]->name;
+ if (core_text::strlen($fieldshortname) > 67) {
+ // If custom profile field name is longer than 67 characters we will not be able to store the setting
+ // such as 'field_updateremote_profile_field_NOTSOSHORTSHORTNAME' in the database because the character
+ // limit for the setting name is 100.
+ continue;
+ }
} elseif ($fieldname == 'url') {
$fieldname = get_string('webpage');
} else {
$errormsg = '';
if ($data = data_submitted() and confirm_sesskey()) {
- if (admin_write_settings($data)) {
- $statusmsg = get_string('changessaved');
- }
-
+ $count = admin_write_settings($data);
if (empty($adminroot->errors)) {
- switch ($return) {
- case 'site': redirect("$CFG->wwwroot/");
- case 'admin': redirect("$CFG->wwwroot/$CFG->admin/");
+ // No errors. Did we change any setting? If so, then indicate success.
+ if ($count) {
+ $statusmsg = get_string('changessaved');
+ } else {
+ switch ($return) {
+ case 'site': redirect("$CFG->wwwroot/");
+ case 'admin': redirect("$CFG->wwwroot/$CFG->admin/");
+ }
}
} else {
$errormsg = get_string('errorwithsettings', 'admin');
echo "Redeleting user $user->id: $user->username ($user->email)\n";
delete_user($user);
}
+$rs->close();
cli_heading('Deleting all leftovers');
</CUSTOM_CHECK>
</CUSTOM_CHECKS>
</MOODLE>
+ <MOODLE version="3.5" requires="3.1">
+ <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>
admin_externalpage_setup('registrationmoodleorg');
if ($url !== HUB_MOODLEORGHUBURL) {
+ // Allow other plugins to confirm registration on hubs other than moodle.net . Plugins implementing this
+ // callback need to redirect or exit. See https://docs.moodle.org/en/Hub_registration .
+ $callbacks = get_plugins_with_function('hub_registration');
+ foreach ($callbacks as $plugintype => $plugins) {
+ foreach ($plugins as $plugin => $callback) {
+ $callback('confirm');
+ }
+ }
throw new moodle_exception('errorotherhubsnotsupported', 'hub');
}
admin_externalpage_setup('registrationmoodleorg');
if ($url !== HUB_MOODLEORGHUBURL) {
+ // Allow other plugins to renew registration on hubs other than moodle.net . Plugins implementing this
+ // callback need to redirect or exit. See https://docs.moodle.org/en/Hub_registration .
+ $callbacks = get_plugins_with_function('hub_registration');
+ foreach ($callbacks as $plugintype => $plugins) {
+ foreach ($plugins as $plugin => $callback) {
+ $callback('renew');
+ }
+ }
throw new moodle_exception('errorotherhubsnotsupported', 'hub');
}
$classformode = array(
'assign' => 'core_role_allow_assign_page',
'override' => 'core_role_allow_override_page',
- 'switch' => 'core_role_allow_switch_page'
+ 'switch' => 'core_role_allow_switch_page',
+ 'view' => 'core_role_allow_view_page'
);
if (!isset($classformode[$mode])) {
print_error('invalidmode', '', '', $mode);
case 'switch':
$event = \core\event\role_allow_switch_updated::create(array('context' => $syscontext));
break;
+ case 'view':
+ $event = \core\event\role_allow_view_updated::create(array('context' => $syscontext));
+ break;
}
if ($event) {
$event->trigger();
}
protected function set_allow($fromroleid, $targetroleid) {
- allow_assign($fromroleid, $targetroleid);
+ core_role_set_assign_allowed($fromroleid, $targetroleid);
}
protected function get_cell_tooltip($fromrole, $targetrole) {
}
protected function set_allow($fromroleid, $targetroleid) {
- allow_override($fromroleid, $targetroleid);
+ core_role_set_override_allowed($fromroleid, $targetroleid);
}
protected function get_cell_tooltip($fromrole, $targetrole) {
}
protected function set_allow($fromroleid, $targetroleid) {
- allow_switch($fromroleid, $targetroleid);
+ core_role_set_switch_allowed($fromroleid, $targetroleid);
}
protected function is_allowed_target($targetroleid) {
--- /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/>.
+
+/**
+ * Role view matrix.
+ *
+ * @package core_role
+ * @copyright 2016 onwards Andrew Hancox <andrewdchancox@googlemail.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Subclass of role_allow_role_page for the Allow views tab.
+ *
+ * @package core_role
+ * @copyright 2016 onwards Andrew Hancox <andrewdchancox@googlemail.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_role_allow_view_page extends core_role_allow_role_page {
+ /** @var array */
+ protected $allowedtargetroles;
+
+ /**
+ * core_role_allow_view_page constructor.
+ */
+ public function __construct() {
+ parent::__construct('role_allow_view', 'allowview');
+ }
+
+
+ /**
+ * Allow from role to view target role.
+ * @param int $fromroleid
+ * @param int $targetroleid
+ */
+ protected function set_allow($fromroleid, $targetroleid) {
+ core_role_set_view_allowed($fromroleid, $targetroleid);
+ }
+
+ /**
+ * Get tool tip for cell.
+ * @param string $fromrole
+ * @param string $targetrole
+ * @return string
+ * @throws \coding_exception
+ */
+ protected function get_cell_tooltip($fromrole, $targetrole) {
+ $a = new stdClass;
+ $a->fromrole = $fromrole->localname;
+ $a->targetrole = $targetrole->localname;
+ return get_string('allowroletoview', 'core_role', $a);
+ }
+
+ /**
+ * Get intro text for role allow view page.
+ * @return string
+ * @throws \coding_exception
+ */
+ public function get_intro_text() {
+ return get_string('configallowview', 'core_admin');
+ }
+}
protected $allowassign;
protected $allowoverride;
protected $allowswitch;
+ protected $allowview;
public function __construct($context, $roleid) {
$this->roleid = $roleid;
$this->allowassign = array_keys($this->get_allow_roles_list('assign'));
$this->allowoverride = array_keys($this->get_allow_roles_list('override'));
$this->allowswitch = array_keys($this->get_allow_roles_list('switch'));
+ $this->allowview = array_keys($this->get_allow_roles_list('view'));
} else {
$this->role = new stdClass;
$this->allowassign = array();
$this->allowoverride = array();
$this->allowswitch = array();
+ $this->allowview = array();
}
parent::load_current_permissions();
}
if (!is_null($allow)) {
$this->allowswitch = $allow;
}
+ $allow = optional_param_array('allowview', null, PARAM_INT);
+ if (!is_null($allow)) {
+ $this->allowview = $allow;
+ }
// Now read the permissions for each capability.
parent::read_submitted_permissions();
* @param int $roleid role id or 0 for no role
* @param array $options array with following keys:
* 'name', 'shortname', 'description', 'permissions', 'archetype',
- * 'contextlevels', 'allowassign', 'allowoverride', 'allowswitch'
+ * 'contextlevels', 'allowassign', 'allowoverride', 'allowswitch',
+ * 'allowview'
*/
public function force_duplicate($roleid, array $options) {
global $DB;
if ($options['allowswitch']) {
$this->allowswitch = array();
}
+ if ($options['allowview']) {
+ $this->allowview = array();
+ }
if ($options['permissions']) {
foreach ($this->capabilities as $capid => $cap) {
if ($options['allowswitch']) {
$this->allowswitch = array_keys($this->get_allow_roles_list('switch', $roleid));
}
+ if ($options['allowview']) {
+ $this->allowview = array_keys($this->get_allow_roles_list('view', $roleid));
+ }
if ($options['permissions']) {
$this->permissions = $DB->get_records_menu('role_capabilities',
* @param string $archetype
* @param array $options array with following keys:
* 'name', 'shortname', 'description', 'permissions', 'archetype',
- * 'contextlevels', 'allowassign', 'allowoverride', 'allowswitch'
+ * 'contextlevels', 'allowassign', 'allowoverride', 'allowswitch',
+ * 'allowview'
*/
public function force_archetype($archetype, array $options) {
$archetypes = get_role_archetypes();
if ($options['allowswitch']) {
$this->allowswitch = get_default_role_archetype_allows('switch', $archetype);
}
+ if ($options['allowview']) {
+ $this->allowview = get_default_role_archetype_allows('view', $archetype);
+ }
if ($options['permissions']) {
$defaultpermissions = get_default_capabilities($archetype);
* @param string $xml
* @param array $options array with following keys:
* 'name', 'shortname', 'description', 'permissions', 'archetype',
- * 'contextlevels', 'allowassign', 'allowoverride', 'allowswitch'
+ * 'contextlevels', 'allowassign', 'allowoverride', 'allowswitch',
+ * 'allowview'
*/
public function force_preset($xml, array $options) {
if (!$info = core_role_preset::parse_preset($xml)) {
}
}
- foreach (array('assign', 'override', 'switch') as $type) {
+ foreach (array('assign', 'override', 'switch', 'view') as $type) {
if ($options['allow'.$type]) {
if (isset($info['allow'.$type])) {
$this->{'allow'.$type} = $info['allow'.$type];
$this->save_allow('assign');
$this->save_allow('override');
$this->save_allow('switch');
+ $this->save_allow('view');
// Permissions.
parent::save_changes();
$current = array_keys($this->get_allow_roles_list($type));
$wanted = $this->{'allow'.$type};
- $addfunction = 'allow_'.$type;
+ $addfunction = "core_role_set_{$type}_allowed";
$deltable = 'role_allow_'.$type;
$field = 'allow'.$type;
protected function get_allow_roles_list($type, $roleid = null) {
global $DB;
- if ($type !== 'assign' and $type !== 'switch' and $type !== 'override') {
+ if ($type !== 'assign' and $type !== 'switch' and $type !== 'override' and $type !== 'view') {
debugging('Invalid role allowed type specified', DEBUG_DEVELOPER);
return array();
}
/**
* Returns an array of roles with the allowed type.
*
- * @param string $type Must be one of: assign, switch, or override.
+ * @param string $type Must be one of: assign, switch, override or view.
* @return array Am array of role names with the allowed type
*/
protected function get_allow_role_control($type) {
- if ($type !== 'assign' and $type !== 'switch' and $type !== 'override') {
+ if ($type !== 'assign' and $type !== 'switch' and $type !== 'override' and $type !== 'view') {
debugging('Invalid role allowed type specified', DEBUG_DEVELOPER);
return '';
}
$this->print_field('menuallowassign', get_string('allowassign', 'core_role'), $this->get_allow_role_control('assign'));
$this->print_field('menuallowoverride', get_string('allowoverride', 'core_role'), $this->get_allow_role_control('override'));
$this->print_field('menuallowswitch', get_string('allowswitch', 'core_role'), $this->get_allow_role_control('switch'));
+ $this->print_field('menuallowview', get_string('allowview', 'core_role'), $this->get_allow_role_control('view'));
if ($risks = $this->get_role_risks_info()) {
$this->print_field('', get_string('rolerisks', 'core_role'), $risks);
}
$contextlevels->appendChild($dom->createElement('level', $name));
}
- foreach (array('assign', 'override', 'switch') as $type) {
+ foreach (array('assign', 'override', 'switch', 'view') as $type) {
$allows = $dom->createElement('allow'.$type);
$top->appendChild($allows);
$records = $DB->get_records('role_allow_'.$type, array('roleid'=>$roleid), "allow$type ASC");
}
}
- foreach (array('assign', 'override', 'switch') as $type) {
+ foreach (array('assign', 'override', 'switch', 'view') as $type) {
$values = self::get_node_children_values($dom, '/role/allow'.$type, 'shortname');
if (!isset($values)) {
$info['allow'.$type] = null;
$mform->addElement('advcheckbox', 'allowassign', get_string('allowassign', 'core_role'));
$mform->addElement('advcheckbox', 'allowoverride', get_string('allowoverride', 'core_role'));
$mform->addElement('advcheckbox', 'allowswitch', get_string('allowswitch', 'core_role'));
+ $mform->addElement('advcheckbox', 'allowview', get_string('allowview', 'core_role'));
$mform->addElement('advcheckbox', 'permissions', get_string('permissions', 'core_role'));
}
'contextlevels' => 1,
'allowassign' => 1,
'allowoverride' => 1,
- 'allowswitch' => 1);
+ 'allowswitch' => 1,
+ 'allowview' => 1);
if ($showadvanced) {
$definitiontable = new core_role_define_role_table_advanced($systemcontext, 0);
} else {
'contextlevels' => $data->contextlevels,
'allowassign' => $data->allowassign,
'allowoverride' => $data->allowoverride,
- 'allowswitch' => $data->allowswitch);
+ 'allowswitch' => $data->allowswitch,
+ 'allowview' => $data->allowview);
if ($showadvanced) {
$definitiontable = new core_role_define_role_table_advanced($systemcontext, $roleid);
} else {
$toprow[] = new tabobject('assign', new moodle_url('/admin/roles/allow.php', array('mode'=>'assign')), get_string('allowassign', 'core_role'));
$toprow[] = new tabobject('override', new moodle_url('/admin/roles/allow.php', array('mode'=>'override')), get_string('allowoverride', 'core_role'));
$toprow[] = new tabobject('switch', new moodle_url('/admin/roles/allow.php', array('mode'=>'switch')), get_string('allowswitch', 'core_role'));
+$toprow[] = new tabobject('view', new moodle_url('/admin/roles/allow.php', ['mode' => 'view']), get_string('allowview', 'core_role'));
echo $OUTPUT->tabtree($toprow, $currenttab);
<xs:element ref="allowassign" minOccurs="0"/>
<xs:element ref="allowoverride" minOccurs="0"/>
<xs:element ref="allowswitch" minOccurs="0"/>
+ <xs:element ref="allowview" minOccurs="0"/>
<xs:element ref="permissions" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:sequence>
</xs:complexType>
</xs:element>
+ <xs:element name="allowview">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element maxOccurs="unbounded" minOccurs="0" ref="shortname"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
<xs:element name="permissions">
<xs:complexType>
<xs:sequence>
$contextlevels = get_role_contextlevels($role->id);
$this->assertEquals(array_values($contextlevels), array_values($info['contextlevels']));
- foreach (array('assign', 'override', 'switch') as $type) {
+ foreach (array('assign', 'override', 'switch', 'view') as $type) {
$records = $DB->get_records('role_allow_'.$type, array('roleid'=>$role->id), "allow$type ASC");
$allows = array();
foreach ($records as $record) {
// now we'll deal with the case that the admin has submitted the form with changed settings
if ($data = data_submitted() and confirm_sesskey() and isset($data->action) and $data->action == 'save-settings') {
require_capability('moodle/site:config', $context);
- if (admin_write_settings($data)) {
- redirect($PAGE->url, get_string('changessaved'), null, \core\output\notification::NOTIFY_SUCCESS);
- }
-
+ $count = admin_write_settings($data);
if (!empty($adminroot->errors)) {
$errormsg = get_string('errorwithsettings', 'admin');
$firsterror = reset($adminroot->errors);
$focus = $firsterror->id;
} else {
+ // No errors. Did we change any setting? If so, then redirect with success.
+ if ($count) {
+ redirect($PAGE->url, get_string('changessaved'), null, \core\output\notification::NOTIFY_SUCCESS);
+ }
redirect($PAGE->url);
}
}
$errormsg = '';
if ($data = data_submitted() and confirm_sesskey()) {
- if (admin_write_settings($data)) {
- redirect($PAGE->url, get_string('changessaved'), null, \core\output\notification::NOTIFY_SUCCESS);
- }
+ $count = admin_write_settings($data);
+ // Regardless of whether any setting change was written (a positive count), check validation errors for those that didn't.
if (empty($adminroot->errors)) {
+ // No errors. Did we change any setting? If so, then redirect with success.
+ if ($count) {
+ redirect($PAGE->url, get_string('changessaved'), null, \core\output\notification::NOTIFY_SUCCESS);
+ }
+ // We didn't change a setting.
switch ($return) {
case 'site': redirect("$CFG->wwwroot/");
case 'admin': redirect("$CFG->wwwroot/$CFG->admin/");
// enable publishing in exports/imports
$temp->add(new admin_setting_configcheckbox('gradepublishing', new lang_string('gradepublishing', 'grades'), new lang_string('gradepublishing_help', 'grades'), 0));
+ $temp->add(new admin_setting_configcheckbox('grade_export_exportfeedback', new lang_string('exportfeedback', 'grades'),
+ new lang_string('exportfeedback_desc', 'grades'), 0));
+
$temp->add(new admin_setting_configselect('grade_export_displaytype', new lang_string('gradeexportdisplaytype', 'grades'),
new lang_string('gradeexportdisplaytype_desc', 'grades'), GRADE_DISPLAY_TYPE_REAL, $display_types));
--- /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/>.
+
+/**
+ * Helper class that contains helper functions for cli scripts.
+ *
+ * @package tool_analytics
+ * @copyright 2017 onwards Ankit Agarwal
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_analytics;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Helper class that contains helper functions for cli scripts.
+ *
+ * @package tool_analytics
+ * @copyright 2017 onwards Ankit Agarwal
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class clihelper {
+
+ /**
+ * List all models in the system. To be used from cli scripts.
+ *
+ * @return void
+ */
+ public static function list_models() {
+ cli_heading("List of models");
+ echo str_pad(get_string('modelid', 'tool_analytics'), 15, ' ') . ' ' . str_pad(get_string('name'), 50, ' ') .
+ ' ' . str_pad(get_string('status'), 15, ' ') . "\n";
+ $models = \core_analytics\manager::get_all_models();
+ foreach ($models as $model) {
+ $modelid = $model->get_id();
+ $isenabled = $model->is_enabled() ? get_string('enabled', 'tool_analytics') : get_string('disabled', 'tool_analytics');
+ $name = $model->get_target()->get_name();
+ echo str_pad($modelid, 15, ' ') . ' ' . str_pad($name, 50, ' ') . ' ' . str_pad($isenabled, 15, ' ') . "\n";
+ }
+ }
+}
\ No newline at end of file
debugging("The time splitting method '{$modeldata->timesplitting}' should include a '{$identifier}_help'
string to describe its purpose.", DEBUG_DEVELOPER);
}
+ } else {
+ $helpicon = new \help_icon('timesplittingnotdefined', 'tool_analytics');
+ $modeldata->timesplittinghelp = $helpicon->export_for_template($output);
}
// Has this model generated predictions?.
}
// Enable / disable.
- if ($model->is_enabled()) {
- $action = 'disable';
- $text = get_string('disable');
- $icontype = 't/block';
- } else {
- $action = 'enable';
- $text = get_string('enable');
- $icontype = 'i/checked';
+ if ($model->is_enabled() || !empty($modeldata->timesplitting)) {
+ // If there is no timesplitting method set, the model can not be enabled.
+ if ($model->is_enabled()) {
+ $action = 'disable';
+ $text = get_string('disable');
+ $icontype = 't/block';
+ } else {
+ $action = 'enable';
+ $text = get_string('enable');
+ $icontype = 'i/checked';
+ }
+ $urlparams['action'] = $action;
+ $url = new \moodle_url('model.php', $urlparams);
+ $icon = new \action_menu_link_secondary($url, new \pix_icon($icontype, $text), $text);
+ $actionsmenu->add($icon);
}
- $urlparams['action'] = $action;
- $url = new \moodle_url('model.php', $urlparams);
- $icon = new \action_menu_link_secondary($url, new \pix_icon($icontype, $text), $text);
- $actionsmenu->add($icon);
// Export training data.
if (!$model->is_static() && $model->is_trained()) {
Options:
--modelid Model id
+--list List models
--timesplitting Time splitting method full class name
-h, --help Print out this help
list($options, $unrecognized) = cli_get_params(
array(
'help' => false,
+ 'list' => false,
'modelid' => false,
'timesplitting' => false
),
exit(0);
}
-if ($options['modelid'] === false || $options['timesplitting'] === false) {
+if ($options['list'] || $options['modelid'] === false) {
+ \tool_analytics\clihelper::list_models();
+ exit(0);
+}
+
+if ($options['timesplitting'] === false) {
echo $help;
exit(0);
}
Options:
--modelid Model id
+--list List models
--non-interactive Not interactive questions
--timesplitting Restrict the evaluation to 1 single time splitting method (Optional)
--filter Analyser dependant. e.g. A courseid would evaluate the model using a single course (Optional)
array(
'help' => false,
'modelid' => false,
+ 'list' => false,
'timesplitting' => false,
'reuse-prev-analysed' => true,
'non-interactive' => false,
exit(0);
}
-if ($options['modelid'] === false) {
- echo $help;
+if ($options['list'] || $options['modelid'] === false) {
+ \tool_analytics\clihelper::list_models();
exit(0);
}
$string['clienablemodel'] = 'You can enable the model by selecting a time-splitting method by its ID. Note that you can also enable it later using the web interface (\'none\' to exit).';
$string['clievaluationandpredictions'] = 'A scheduled task iterates through enabled models and gets predictions. Models evaluation via the web interface is disabled. You can allow these processes to be executed manually via the web interface by disabling the <a href="{$a}">\'onlycli\'</a> analytics setting.';
$string['clievaluationandpredictionsnoadmin'] = 'A scheduled task iterates through enabled models and gets predictions. Models evaluation via the web interface is disabled. It may be enabled by a site administrator.';
+$string['disabled'] = 'Disabled';
$string['editmodel'] = 'Edit "{$a}" model';
$string['edittrainedwarning'] = 'This model has already been trained. Note that changing its indicators or its time-splitting method will delete its previous predictions and start generating new predictions.';
$string['enabled'] = 'Enabled';
$string['invalidprediction'] = 'Invalid to get predictions';
$string['invalidtraining'] = 'Invalid to train the model';
$string['loginfo'] = 'Log extra info';
+$string['modelid'] = 'Model id';
$string['modelinvalidanalysables'] = 'Invalid analysable elements for "{$a}" model';
$string['modelresults'] = '{$a} results';
$string['modeltimesplitting'] = 'Time splitting';
$string['samestartdate'] = 'Current start date is good';
$string['sameenddate'] = 'Current end date is good';
$string['target'] = 'Target';
+$string['timesplittingnotdefined'] = 'Time splitting is not defined.';
+$string['timesplittingnotdefined_help'] = 'You need to select a time-splitting method before enabling the model.';
$string['trainandpredictmodel'] = 'Training model and calculating predictions';
$string['trainingprocessfinished'] = 'Training process finished';
$string['trainingresults'] = 'Training results';
{{/timesplitting}}
{{^timesplitting}}
{{#str}}notdefined, tool_analytics{{/str}}
+ {{#timesplittinghelp}}
+ {{>core/help_icon}}
+ {{/timesplittinghelp}}
{{/timesplitting}}
</td>
<td>
// Show process name in first row.
foreach ($processes as $name => $process) {
// If we don't have enough space to show full run name then show runX.
- if ($lengthofprocessline < strlen($name + 2)) {
+ if ($lengthofprocessline < strlen($name) + 2) {
$name = substr($name, -5);
}
// One extra padding as we are adding | separator for rest of the data.
function xmldb_tool_customlang_upgrade($oldversion) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
"Test image from another site should be replaced" => [
"content" => '<img src="' . $this->getExternalTestFileUrl('/test.jpg', false) . '">',
"outputregex" => '/UPDATE/',
- "expectedcontent" => '<img src="' . $this->getExternalTestFileUrl('/test.jpg', true) . '">',
+ "expectedcontent" => '<img src="' . $this->get_converted_http_link('/test.jpg') . '">',
],
"Test object from another site should be replaced" => [
"content" => '<object data="' . $this->getExternalTestFileUrl('/test.swf', false) . '">',
"outputregex" => '/UPDATE/',
- "expectedcontent" => '<object data="' . $this->getExternalTestFileUrl('/test.swf', true) . '">',
+ "expectedcontent" => '<object data="' . $this->get_converted_http_link('/test.swf') . '">',
],
"Test image from a site with international name should be replaced" => [
"content" => '<img src="http://中国互联网络信息中心.中国/logosy/201706/W01.png">',
"Search for params should be case insensitive" => [
"content" => '<object DATA="' . $this->getExternalTestFileUrl('/test.swf', false) . '">',
"outputregex" => '/UPDATE/',
- "expectedcontent" => '<object DATA="' . $this->getExternalTestFileUrl('/test.swf', true) . '">',
+ "expectedcontent" => '<object DATA="' . $this->get_converted_http_link('/test.swf') . '">',
],
"URL should be case insensitive" => [
"content" => '<object data="HTTP://some.site/path?query">',
"content" => '<img alt="A picture" src="' . $this->getExternalTestFileUrl('/test.png', false) .
'" width="1”><p style="font-size: \'20px\'"></p>',
"outputregex" => '/UPDATE/',
- "expectedcontent" => '<img alt="A picture" src="' . $this->getExternalTestFileUrl('/test.png', true) .
+ "expectedcontent" => '<img alt="A picture" src="' . $this->get_converted_http_link('/test.png') .
'" width="1”><p style="font-size: \'20px\'"></p>',
],
"Broken URL should not be changed" => [
$this->getExternalTestFileUrl('/test.jpg', false) . '"></a>',
"outputregex" => '/UPDATE/',
"expectedcontent" => '<a href="' . $this->getExternalTestFileUrl('/test.png', false) . '"><img src="' .
- $this->getExternalTestFileUrl('/test.jpg', true) . '"></a>',
+ $this->get_converted_http_link('/test.jpg') . '"></a>',
],
];
}
+ /**
+ * Convert the HTTP external test file URL to use HTTPS.
+ *
+ * Note: We *must not* use getExternalTestFileUrl with the True option
+ * here, becase it is reasonable to have only one of these set due to
+ * issues with SSL certificates.
+ *
+ * @param string $path Path to be rewritten
+ * @return string
+ */
+ protected function get_converted_http_link($path) {
+ return preg_replace('/^http:/', 'https:', $this->getExternalTestFileUrl($path, false));
+ }
+
/**
* Test upgrade_http_links
* @param string $content Example content that we'll attempt to replace.
// Get the http url, since the default test wwwroot is https.
$wwwrootdomain = 'www.example.com';
$wwwroothttp = preg_replace('/^https:/', 'http:', $CFG->wwwroot);
- $testdomain = 'download.moodle.org';
+ $testdomain = $this->get_converted_http_link('');
return [
"Test image from an available site so shouldn't be reported" => [
"content" => '<img src="' . $this->getExternalTestFileUrl('/test.jpg', false) . '">',
function xmldb_tool_log_upgrade($oldversion) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_logstore_database_upgrade($oldversion) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_logstore_standard_upgrade($oldversion) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
<li>
<a href="#">{{#str}}edit{{/str}}</a><b class="caret"></b>
<ul class="dropdown dropdown-menu">
+ {{#canmanage}}
<li class="dropdown-item">
<a href="#" data-action="edit">
{{#pix}}t/edit{{/pix}} {{#str}}edit{{/str}}
{{#pix}}t/down{{/pix}} {{#str}}movedown{{/str}}
</a>
</li>
+ {{/canmanage}}
<li class="dropdown-item">
<a href="#" data-action="linkedcourses">
{{#pix}}t/viewdetails{{/pix}} {{#str}}linkedcourses, tool_lp{{/str}}
</a>
</li>
+ {{#canmanage}}
<li class="dropdown-item">
<a href="#" data-action="relatedcompetencies">
{{#pix}}t/add{{/pix}} {{#str}}addcrossreferencedcompetency, tool_lp{{/str}}
{{#pix}}t/edit{{/pix}} {{#str}}competencyrule, tool_lp{{/str}}
</a>
</li>
+ {{/canmanage}}
</ul>
</li>
</ul>
<p data-region="competencyinfo">
{{#str}}nocompetencyselected, tool_lp{{/str}}
</p>
- {{#canmanage}}
<div data-region="competencyactions">
+ {{#canmanage}}
<button class="btn btn-secondary" data-action="add">{{#pix}}t/add{{/pix}} <span data-region="term"></span></button>
+ {{/canmanage}}
</div>
- {{/canmanage}}
</div>
</div>
</div>
foreach ($records as $record) {
$instances[] = \core\message\inbound\manager::get_handler($record->classname);
}
+ $records->close();
echo $OUTPUT->header();
echo $renderer->messageinbound_handlers_table($instances);
$dbman = $DB->get_manager();
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
if ($oldversion < 2016052305) {
// Define field inactivedate to be added to tool_monitor_subscriptions.
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher@asd.com |
| student1 | Student | 1 | student@asd.com |
+ | student2 | Student | 2 | student2@asd.com |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ | student2 | C1 | student |
+ | teacher1 | C2 | editingteacher |
+ | student1 | C2 | student |
+ | student2 | C2 | student |
+ And the following "groups" exist:
+ | name | course | idnumber |
+ | Group A | C2 | G1 |
+ | Group B | C2 | G2 |
+ | Group C | C2 | G3 |
+ And the following "group members" exist:
+ | user | group |
+ | teacher1 | G1 |
+ | teacher1 | G2 |
+ | student1 | G1 |
+ | student2 | G1 |
+ | student2 | G2 |
And the following config values are set as admin:
| coursebinenable | 1 | tool_recyclebin |
| categorybinenable | 1 | tool_recyclebin |
And I wait to be redirected
And I go to the courses management page
And I should see "Course 2" in the "#course-listing" "css_element"
+ And I am on "Course 2" course homepage
+ And I navigate to "Groups" node in "Course administration > Users"
+ And I follow "Overview"
+ And "Student 1" "text" should exist in the "Group A" "table_row"
+ And "Student 2" "text" should exist in the "Group A" "table_row"
+ And "Student 2" "text" should exist in the "Group B" "table_row"
@javascript
Scenario: Deleting a single item from the recycle bin
$keywordlist = implode(', ', $keywords);
echo $OUTPUT->box(get_string('spamresult', 'tool_spamcleaner').s($keywordlist)).' ...';
- print_user_list(array($spamusers_desc,
- $spamusers_blog,
- $spamusers_blogsub,
- $spamusers_comment,
- $spamusers_message,
- $spamusers_forumpost,
- $spamusers_forumpostsub
- ),
- $keywords);
+ $recordsets = [
+ $spamusers_desc,
+ $spamusers_blog,
+ $spamusers_blogsub,
+ $spamusers_comment,
+ $spamusers_message,
+ $spamusers_forumpost,
+ $spamusers_forumpostsub
+ ];
+ print_user_list($recordsets, $keywords);
+ foreach ($recordsets as $rs) {
+ $rs->close();
+ }
}
// add default values for remaining fields
$formdefaults = array();
- if ($updatetype != UU_UPDATE_FILEOVERRIDE && $updatetype != UU_UPDATE_NOCHANGES) {
+ if (!$existinguser || ($updatetype != UU_UPDATE_FILEOVERRIDE && $updatetype != UU_UPDATE_NOCHANGES)) {
foreach ($STD_FIELDS as $field) {
if (isset($user->$field)) {
continue;
And I set the field "groups" to "Section 1 (1)"
And the "members" select box should contain "Tom Jones"
+ @javascript
+ Scenario: Upload users enrolling them on courses and groups applying defaults
+ Given the following "courses" exist:
+ | fullname | shortname | category |
+ | Maths | math102 | 0 |
+ And the following "groups" exist:
+ | name | course | idnumber |
+ | Section 1 | math102 | S1 |
+ | Section 3 | math102 | S3 |
+ And I log in as "admin"
+ And I navigate to "Upload users" node in "Site administration > Users > Accounts"
+ When I upload "lib/tests/fixtures/upload_users.csv" file to "File" filemanager
+ And I press "Upload users"
+ And I set the following fields to these values:
+ | City/town | Brighton |
+ | Department | Purchasing |
+ And I press "Upload users"
+ And I press "Continue"
+ And I navigate to "Users > Accounts > Browse list of users" in site administration
+ And I should see "Tom Jones"
+ And I follow "Tom Jones"
+ And I follow "Edit profile"
+ And the field "City/town" matches value "Brighton"
+ And the field "Department" matches value "Purchasing"
+
@javascript
Scenario: Upload users with custom profile fields
# Create user profile field.
decimalsField.value = '';
break;
case '2': // XMLDB_TYPE_NUMBER
- lengthTip.innerHTML = ' 1...20'; // Hardcoded xmldb_field::NUMBER_MAX_LENGTH, yes!
+ lengthTip.innerHTML = ' 1...38'; // Hardcoded xmldb_field::NUMBER_MAX_LENGTH, yes!
lengthField.disabled = false;
decimalsTip.innerHTML = ' 0...length or empty';
break;
'floatincorrectlength' => 'tool_xmldb',
'charincorrectlength' => 'tool_xmldb',
'numberincorrectdecimals' => 'tool_xmldb',
+ 'numberincorrectwholepart' => 'tool_xmldb',
'floatincorrectdecimals' => 'tool_xmldb',
'defaultincorrect' => 'tool_xmldb',
'back' => 'tool_xmldb',
$decimals < $length))) {
$errors[] = $this->str['numberincorrectdecimals'];
}
+ if (!empty($decimals) && ($length - $decimals > xmldb_field::INTEGER_MAX_LENGTH)) {
+ $errors[] = $this->str['numberincorrectwholepart'];
+ }
if (!(empty($default) || (is_numeric($default) &&
!empty($default)))) {
$errors[] = $this->str['defaultincorrect'];
$string['nowrongoraclesemanticsfound'] = 'No Oracle columns using BYTE semantics have been found, your DB doesn\'t need further actions.';
$string['numberincorrectdecimals'] = 'Incorrect number of decimals for number field';
$string['numberincorrectlength'] = 'Incorrect length for number field';
+$string['numberincorrectwholepart'] = 'Too big whole number part for number field';
$string['pendingchanges'] = 'Note: You have performed changes to this file. They can be saved at any moment.';
$string['pendingchangescannotbesaved'] = 'There are changes in this file but they cannot be saved! Please verify that both the directory and the "install.xml" within it have write permissions for the web server.';
$string['pendingchangescannotbesavedreload'] = 'There are changes in this file but they cannot be saved! Please verify that both the directory and the "install.xml" within it have write permissions for the web server. Then reload this page and you should be able to save those changes.';
$users[$key]->country = $countries[$user->country];
}
}
- if ($sort == "country") { // Need to resort by full country name, not code
+ if ($sort == "country") {
+ // Need to resort by full country name, not code.
foreach ($users as $user) {
$susers[$user->id] = $user->country;
}
- asort($susers);
+ // Sort by country name, according to $dir.
+ if ($dir === 'DESC') {
+ arsort($susers);
+ } else {
+ asort($susers);
+ }
foreach ($susers as $key => $value) {
$nusers[] = $users[$key];
}
}
$existingcalculations[$calculation->indicator][$calculation->sampleid] = $calculation->value;
}
+ $calculations->close();
return $existingcalculations;
}
if (!$this->is_static()) {
$this->model->trained = 0;
}
+ } else if (empty($this->model->timesplitting)) {
+ // A valid timesplitting method needs to be supplied before a model can be enabled.
+ throw new \moodle_exception('invalidtimesplitting', 'analytics', '', $this->model->id);
+
}
// Purge pages with insights as this may change things.
$this->model->mark_as_trained();
$this->assertEquals($originaluniqueid, $this->model->get_unique_id());
- $this->model->enable();
- $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
+ // Wait for the current timestamp to change.
+ $this->waitForSecond();
+ $this->model->enable('\core\analytics\time_splitting\deciles');
+ $this->assertNotEquals($originaluniqueid, $this->model->get_unique_id());
+ $uniqueid = $this->model->get_unique_id();
- // Wait 1 sec so the timestamp changes.
- sleep(1);
+ // Wait for the current timestamp to change.
+ $this->waitForSecond();
$this->model->enable('\core\analytics\time_splitting\quarters');
$this->assertNotEquals($originaluniqueid, $this->model->get_unique_id());
+ $this->assertNotEquals($uniqueid, $this->model->get_unique_id());
}
/**
function xmldb_auth_cas_upgrade($oldversion) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_auth_ldap_upgrade($oldversion) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
entry in its database. This module can read user attributes from LDAP and prefill
wanted fields in Moodle. For following logins only the username and
password are checked.';
-$string['auth_ldap_expiration_desc'] = 'Select No to disable expired password checking or LDAP to read passwordexpiration time directly from LDAP';
+$string['auth_ldap_expiration_desc'] = 'Select \'{$a->no}\' to disable expired password checking or \'{$a->ldapserver}\' to read the password expiration time directly from the LDAP server';
$string['auth_ldap_expiration_key'] = 'Expiration';
$string['auth_ldap_expiration_warning_desc'] = 'Number of days before password expiration warning is issued.';
$string['auth_ldap_expiration_warning_key'] = 'Expiration warning';
new lang_string('auth_ldap_passwdexpire_settings', 'auth_ldap'), ''));
// Password Expiration.
+
+ // Create the description lang_string object.
+ $strno = get_string('no');
+ $strldapserver = get_string('pluginname', 'auth_ldap');
+ $langobject = new stdClass();
+ $langobject->no = $strno;
+ $langobject->ldapserver = $strldapserver;
+ $description = new lang_string('auth_ldap_expiration_desc', 'auth_ldap', $langobject);
+
+ // Now create the options.
$expiration = array();
- $expiration['0'] = 'no';
- $expiration['1'] = 'LDAP';
+ $expiration['0'] = $strno;
+ $expiration['1'] = $strldapserver;
+
+ // Add the setting.
$settings->add(new admin_setting_configselect('auth_ldap/expiration',
new lang_string('auth_ldap_expiration_key', 'auth_ldap'),
- new lang_string('auth_ldap_expiration_desc', 'auth_ldap'), 0 , $expiration));
+ $description, 0 , $expiration));
// Password Expiration warning.
$settings->add(new admin_setting_configtext('auth_ldap/expiration_warning',
function xmldb_auth_manual_upgrade($oldversion) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
// with info so that the IDP can maintain mnetservice_enrol_enrolments
$mnetrequest->add_param($remoteuser->username);
$fields = 'id, category, sortorder, fullname, shortname, idnumber, summary, startdate, visible';
- $courses = enrol_get_users_courses($localuser->id, false, $fields, 'visible DESC,sortorder ASC');
+ $courses = enrol_get_users_courses($localuser->id, false, $fields);
if (is_array($courses) && !empty($courses)) {
// Second request to do the JOINs that we'd have done
// inside enrol_get_users_courses() if we had been allowed
function xmldb_auth_mnet_upgrade($oldversion) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2017020700) {
$link = 'http://invalidurldetected/';
}
} else {
- $link = $rawlink;
+ $link = htmlspecialchars(trim($rawlink), ENT_COMPAT, 'UTF-8', false);
}
}
}
$DB->set_field('course_' . $table . 's', 'availability', $newvalue,
array('id' => $thingid));
}
+ $rs->close();
}
- $rs->close();
}
}
assign_capability($cap, CAP_ALLOW, $roleidcat, $categorycontext);
}
- allow_assign($roleidcat, $studentrole->id);
+ core_role_set_assign_allowed($roleidcat, $studentrole->id);
role_assign($roleidcat, $user->id, $categorycontext);
accesslib_clear_all_caches_for_unit_testing();
if (!array_key_exists('setting_root_filename', $errors)) {
if (trim($data['setting_root_filename']) == '') {
$errors['setting_root_filename'] = get_string('errorfilenamerequired', 'backup');
+ } else if (strlen(trim($data['setting_root_filename'])) > 255) {
+ $errors['setting_root_filename'] = get_string('errorfilenametoolong', 'backup');
} else if (!preg_match('#\.mbz$#i', $data['setting_root_filename'])) {
$errors['setting_root_filename'] = get_string('errorfilenamemustbezip', 'backup');
}
if (!empty($badge->recipient->id)) {
if ($bake && ($badge->recipient->id == $USER->id)) {
$name = str_replace(' ', '_', $badge->badgeclass['name']) . '.png';
+ $name = clean_param($name, PARAM_FILE);
$filehash = badges_bake($id, $badge->badgeid, $USER->id, true);
$fs = get_file_storage();
$file = $fs->get_file_by_hash($filehash);
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'Badge id.', VALUE_OPTIONAL),
- 'name' => new external_value(PARAM_FILE, 'Badge name.'),
+ 'name' => new external_value(PARAM_TEXT, 'Badge name.'),
'description' => new external_value(PARAM_NOTAGS, 'Badge description.'),
'badgeurl' => new external_value(PARAM_URL, 'Badge URL.'),
'timecreated' => new external_value(PARAM_INT, 'Time created.', VALUE_OPTIONAL),
}
}
+ /**
+ * Triggered when 'badge_awarded' event happens.
+ *
+ * @param \core\event\badge_awarded $event event generated when a badge is awarded.
+ */
+ public static function badge_criteria_review(\core\event\badge_awarded $event) {
+ global $DB, $CFG;
+
+ if (!empty($CFG->enablebadges)) {
+ require_once($CFG->dirroot.'/lib/badgeslib.php');
+ $userid = $event->relateduserid;
+
+ if ($rs = $DB->get_records('badge_criteria', array('criteriatype' => BADGE_CRITERIA_TYPE_BADGE))) {
+ foreach ($rs as $r) {
+ $badge = new badge($r->badgeid);
+ if (!$badge->is_active() || $badge->is_issued($userid)) {
+ continue;
+ }
+
+ if ($badge->criteria[BADGE_CRITERIA_TYPE_BADGE]->review($userid)) {
+ $badge->criteria[BADGE_CRITERIA_TYPE_BADGE]->mark_complete($userid);
+
+ if ($badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->review($userid)) {
+ $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->mark_complete($userid);
+ $badge->issue($userid);
+ }
+ }
+ }
+ }
+ }
+ }
/**
* Triggered when 'user_updated' event happens.
*
*/
define('BADGE_CRITERIA_TYPE_PROFILE', 6);
+/*
+ * Badge completion criteria type
+ * Criteria type constant, primarily for storing criteria type in the database.
+ */
+define('BADGE_CRITERIA_TYPE_BADGE', 7);
+
/*
* Criteria type constant to class name mapping
*/
BADGE_CRITERIA_TYPE_SOCIAL => 'social',
BADGE_CRITERIA_TYPE_COURSE => 'course',
BADGE_CRITERIA_TYPE_COURSESET => 'courseset',
- BADGE_CRITERIA_TYPE_PROFILE => 'profile'
+ BADGE_CRITERIA_TYPE_PROFILE => 'profile',
+ BADGE_CRITERIA_TYPE_BADGE => 'badge',
);
/**
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the badge earned badge award criteria type class
+ *
+ * @package core
+ * @subpackage badges
+ * @copyright 2017 Stephen Bourget
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Badge award criteria -- award on badge completion
+ *
+ * @package core
+ * @subpackage badges
+ * @copyright 2017 Stephen Bourget
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class award_criteria_badge extends award_criteria {
+
+ /* @var int Criteria [BADGE_CRITERIA_TYPE_BADGE] */
+ public $criteriatype = BADGE_CRITERIA_TYPE_BADGE;
+
+ public $required_param = 'badge';
+ public $optional_params = array();
+
+ /**
+ * Get criteria details for displaying to users
+ * @param string $short Print short version of criteria
+ * @return string
+ */
+ public function get_details($short = '') {
+ global $DB, $OUTPUT;
+ $output = array();
+ foreach ($this->params as $p) {
+ $badgename = $DB->get_field('badge', 'name', array('id' => $p['badge']));
+ if (!$badgename) {
+ $str = $OUTPUT->error_text(get_string('error:nosuchbadge', 'badges'));
+ } else {
+ $str = html_writer::tag('b', '"' . $badgename . '"');
+ }
+ $output[] = $str;
+ }
+
+ if ($short) {
+ return implode(', ', $output);
+ } else {
+ return html_writer::alist($output, array(), 'ul');
+ }
+ }
+
+ /**
+ * Add appropriate new criteria options to the form
+ * @param object $mform moodle form
+ */
+ public function get_options(&$mform) {
+ global $DB;
+ $none = false;
+ $availablebadges = null;
+
+ $mform->addElement('header', 'first_header', $this->get_title());
+ $mform->addHelpButton('first_header', 'criteria_' . $this->criteriatype, 'badges');
+
+ // Determine if this badge is a course badge or a site badge.
+ $thisbadge = $DB->get_record('badge', array('id' => $this->badgeid));
+
+ if ($thisbadge->type == BADGE_TYPE_SITE) {
+ // Only list site badges that are enabled.
+ $select = " type = :site AND (status = :status1 OR status = :status2)";
+ $params = array('site' => BADGE_TYPE_SITE,
+ 'status1' => BADGE_STATUS_ACTIVE,
+ 'status2' => BADGE_STATUS_ACTIVE_LOCKED);
+ $availablebadges = $DB->get_records_select_menu('badge', $select, $params, 'name ASC', 'id, name');
+
+ } else if ($thisbadge->type == BADGE_TYPE_COURSE) {
+ // List both site badges and course badges belonging to this course.
+ $select = " (type = :site OR (type = :course AND courseid = :courseid)) AND (status = :status1 OR status = :status2)";
+ $params = array('site' => BADGE_TYPE_SITE,
+ 'course' => BADGE_TYPE_COURSE,
+ 'courseid' => $thisbadge->courseid,
+ 'status1' => BADGE_STATUS_ACTIVE,
+ 'status2' => BADGE_STATUS_ACTIVE_LOCKED);
+ $availablebadges = $DB->get_records_select_menu('badge', $select, $params, 'name ASC', 'id, name');
+ }
+ if (!empty($availablebadges)) {
+ $select = array();
+ $selected = array();
+ foreach ($availablebadges as $bid => $badgename) {
+ if ($bid != $this->badgeid) {
+ // Do not let it use itself as criteria.
+ $select[$bid] = format_string($badgename, true);
+ }
+ }
+
+ if ($this->id !== 0) {
+ $selected = array_keys($this->params);
+ }
+ $settings = array('multiple' => 'multiple', 'size' => 20, 'class' => 'selectbadge');
+ $mform->addElement('select', 'badge_badges', get_string('addbadge', 'badges'), $select, $settings);
+ $mform->addRule('badge_badges', get_string('requiredbadge', 'badges'), 'required');
+ $mform->addHelpButton('badge_badges', 'addbadge', 'badges');
+
+ if ($this->id !== 0) {
+ $mform->setDefault('badge_badges', $selected);
+ }
+ } else {
+ $mform->addElement('static', 'nobadges', '', get_string('error:nobadges', 'badges'));
+ $none = true;
+ }
+
+ // Add aggregation.
+ if (!$none) {
+ $mform->addElement('header', 'aggregation', get_string('method', 'badges'));
+ $agg = array();
+ $agg[] =& $mform->createElement('radio', 'agg', '', get_string('allmethodbadges', 'badges'), 1);
+ $agg[] =& $mform->createElement('radio', 'agg', '', get_string('anymethodbadges', 'badges'), 2);
+ $mform->addGroup($agg, 'methodgr', '', array('<br/>'), false);
+ if ($this->id !== 0) {
+ $mform->setDefault('agg', $this->method);
+ } else {
+ $mform->setDefault('agg', BADGE_CRITERIA_AGGREGATION_ANY);
+ }
+ }
+
+ return array($none, get_string('noparamstoadd', 'badges'));
+ }
+
+ /**
+ * Save criteria records
+ *
+ * @param array $params Values from the form or any other array.
+ */
+ public function save($params = array()) {
+ $badges = $params['badge_badges'];
+ unset($params['badge_badges']);
+ foreach ($badges as $badgeid) {
+ $params["badge_{$badgeid}"] = $badgeid;
+ }
+
+ parent::save($params);
+ }
+
+ /**
+ * Review this criteria and decide if it has been completed
+ *
+ * @param int $userid User whose criteria completion needs to be reviewed.
+ * @param bool $filtered An additional parameter indicating that user list
+ * has been reduced and some expensive checks can be skipped.
+ *
+ * @return bool Whether criteria is complete.
+ */
+ public function review($userid, $filtered = false) {
+
+ global $DB;
+ $overall = false;
+
+ foreach ($this->params as $param) {
+ $badge = $DB->get_record('badge', array('id' => $param['badge']));
+ // See if the user has earned this badge.
+ $awarded = $DB->get_record('badge_issued', array('badgeid' => $param['badge'], 'userid' => $userid));
+
+ // Extra check in case a badge was deleted while this badge is still active.
+ if (!$badge) {
+ if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) {
+ return false;
+ } else {
+ continue;
+ }
+ }
+
+ if ($this->method == BADGE_CRITERIA_AGGREGATION_ALL) {
+
+ if ($awarded) {
+ $overall = true;
+ continue;
+ } else {
+ return false;
+ }
+ } else if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+ if ($awarded) {
+ return true;
+ } else {
+ $overall = false;
+ continue;
+ }
+ }
+ }
+
+ return $overall;
+ }
+
+ /**
+ * Checks criteria for any major problems.
+ *
+ * @return array A list containing status and an error message (if any).
+ */
+ public function validate() {
+ global $DB;
+ $params = array_keys($this->params);
+ $method = ($this->method == BADGE_CRITERIA_AGGREGATION_ALL);
+ $singleparam = (count($params) == 1);
+
+ foreach ($params as $param) {
+ // Perform check if there only one parameter with any type of aggregation,
+ // Or there are more than one parameter with aggregation ALL.
+
+ if (($singleparam || $method) && !$DB->record_exists('badge', array('id' => $param))) {
+ return array(false, get_string('error:invalidparambadge', 'badges'));
+ }
+ }
+
+ return array(true, '');
+ }
+
+ /**
+ * Returns array with sql code and parameters returning all ids
+ * of users who meet this particular criterion.
+ *
+ * @return array list($join, $where, $params)
+ */
+ public function get_completed_criteria_sql() {
+ $join = '';
+ $where = '';
+ $params = array();
+
+ if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
+ // User has received ANY of the required badges.
+ $join = " LEFT JOIN {badge_issued} bi2 ON bi2.userid = u.id";
+ $where = "AND (";
+ $i = 0;
+ foreach ($this->params as $param) {
+ if ($i == 0) {
+ $where .= ' bi2.badgeid = :badgeid'.$i;
+ } else {
+ $where .= ' OR bi2.badgeid = :badgeid'.$i;
+ }
+ $params['badgeid'.$i] = $param['badge'];
+ $i++;
+ }
+ $where .= ") ";
+ return array($join, $where, $params);
+ } else {
+ // User has received ALL of the required badges.
+ $join = " LEFT JOIN {badge_issued} bi2 ON bi2.userid = u.id";
+ $i = 0;
+ foreach ($this->params as $param) {
+ $i++;
+ $where = ' AND bi2.badgeid = :badgeid'.$i;
+ $params['badgeid'.$i] = $param['badge'];
+ }
+ return array($join, $where, $params);
+ }
+ }
+}
$none = true;
$roles = get_roles_with_capability('moodle/badges:awardbadge', CAP_ALLOW, $PAGE->context);
+ $visibleroles = get_viewable_roles($PAGE->context);
$roleids = array_map(function($o) {
return $o->id;
}, $roles);
$mform->addElement('header', 'first_header', $this->get_title());
$mform->addHelpButton('first_header', 'criteria_' . $this->criteriatype, 'badges');
foreach ($roleids as $rid) {
+ if (!key_exists($rid, $visibleroles)) {
+ continue;
+ }
$checked = false;
if (in_array($rid, $existing)) {
$checked = true;
$mform->addElement('header', 'badgedetails', get_string('badgedetails', 'badges'));
$mform->addElement('text', 'name', get_string('name'), array('size' => '70'));
- // Using PARAM_FILE to avoid problems later when downloading badge files.
- $mform->setType('name', PARAM_FILE);
+ // When downloading badge, it will be necessary to clean the name as PARAM_FILE.
+ $mform->setType('name', PARAM_TEXT);
$mform->addRule('name', null, 'required');
$mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
require_sesskey();
$badge = new badge($download);
$name = str_replace(' ', '_', $badge->name) . '.png';
+ $name = clean_param($name, PARAM_FILE);
$filehash = badges_bake($hash, $download, $USER->id, true);
$fs = get_file_storage();
$file = $fs->get_file_by_hash($filehash);
$fordb = new stdClass();
$fordb->id = null;
- $fordb->name = "Test badge";
+ $fordb->name = "Test badge with 'apostrophe' and other friends (<>&@#)";
$fordb->description = "Testing badges";
$fordb->timecreated = time();
$fordb->timemodified = time();
Scenario: Add a badge
Given I navigate to "Add a new badge" node in "Site administration > Badges"
And I set the following fields to these values:
- | Name | Test Badge |
+ | Name | Test badge with 'apostrophe' and other friends (<>&@#) |
| Description | Test badge description |
| issuername | Test Badge Site |
| issuercontact | testuser@example.com |
And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
When I press "Create badge"
Then I should see "Edit details"
- And I should see "Test Badge"
+ And I should see "Test badge with 'apostrophe' and other friends (&@#)"
And I should not see "Create badge"
And I follow "Manage badges"
And I should see "Number of badges available: 1"
As an admin
I need to add criteria to badges in the system
+ @javascript
+ Scenario: Award badge on other badges as criteria
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ | student1 | Student | 1 | student1@example.com |
+ And the following "courses" exist:
+ | fullname | shortname | category | groupmode |
+ | Course 1 | C1 | 0 | 1 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ And I log in as "teacher1"
+ And I am on "Course 1" course homepage
+ # Create course badge 1.
+ And I navigate to "Add a new badge" node in "Course administration > Badges"
+ And I follow "Add a new badge"
+ And I set the following fields to these values:
+ | Name | Course Badge 1 |
+ | Description | Course badge 1 description |
+ | issuername | Tester of course badge |
+ And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
+ And I press "Create badge"
+ And I set the field "type" to "Manual issue by role"
+ And I expand all fieldsets
+ # Set to ANY of the roles awards badge.
+ And I set the field "Teacher" to "1"
+ And I set the field "Any of the selected roles awards the badge" to "1"
+ And I press "Save"
+ And I press "Enable access"
+ And I press "Continue"
+ # Badge #2
+ And I navigate to "Add a new badge" node in "Course administration > Badges"
+ And I follow "Add a new badge"
+ And I set the following fields to these values:
+ | Name | Course Badge 2 |
+ | Description | Course badge 2 description |
+ | issuername | Tester of course badge |
+ And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
+ And I press "Create badge"
+ # Set "course badge 1" as criteria
+ And I set the field "type" to "Awarded badges"
+ And I set the field "id_badge_badges" to "Course Badge 1"
+ And I press "Save"
+ And I press "Enable access"
+ And I press "Continue"
+ And I follow "Manage badges"
+ And I follow "Course Badge 1"
+ And I follow "Recipients (0)"
+ And I press "Award badge"
+ # Award course badge 1 to student 1.
+ And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
+ When I press "Award badge"
+ And I follow "Course Badge 1"
+ And I follow "Recipients (1)"
+ Then I should see "Recipients (1)"
+ And I log out
+ # Student 1 should have both badges.
+ And I log in as "student1"
+ And I follow "Profile" in the user menu
+ When I click on "Course 1" "link" in the "region-main" "region"
+ Then I should see "Course Badge 1"
+ And I should see "Course Badge 2"
+
@javascript
Scenario: Award profile badge
Given I log in as "admin"
--- /dev/null
+@core @core_badges
+Feature: Test role visibility for the badge administration page
+ In order to control access
+ As an admin
+ I need to control which roles can see each other
+
+ Background: Add a bunch of users
+ Given the following "courses" exist:
+ | fullname | shortname |
+ | Course 1 | C1 |
+ And the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ | manager1 | Manager | 1 | manager1@example.com |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | manager1 | C1 | manager |
+
+ @javascript @_file_upload
+ Scenario: Check the default roles are visible
+ Given I log in as "manager1"
+ And I am on "Course 1" course homepage
+ And I navigate to "Add a new badge" node in "Course administration > Badges"
+ And I follow "Add a new badge"
+ And I set the following fields to these values:
+ | Name | Course Badge |
+ | Description | Course badge description |
+ | issuername | Tester of course badge |
+ And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
+ And I press "Create badge"
+ And I set the field "type" to "Manual issue by role"
+ Then I should see "Teacher"
+ And I should see "Manager"
+
+ @javascript @_file_upload
+ Scenario: Check hidden roles are not visible
+ Given I log in as "teacher1"
+ And I am on "Course 1" course homepage
+ And I navigate to "Add a new badge" node in "Course administration > Badges"
+ And I follow "Add a new badge"
+ And I set the following fields to these values:
+ | Name | Course Badge |
+ | Description | Course badge description |
+ | issuername | Tester of course badge |
+ And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
+ And I press "Create badge"
+ And I set the following fields to these values:
+ | Add badge criteria | Manual issue by role |
+ Then I should see "Teacher"
+ And I should not see "Manager"
function xmldb_block_badges_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_block_calendar_month_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_block_calendar_upcoming_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_block_community_upgrade($oldversion) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_block_completionstatus_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
if (empty($CFG->disablemycourses) and isloggedin() and !isguestuser() and
!(has_capability('moodle/course:update', context_system::instance()) and $adminseesall)) { // Just print My Courses
- // As this is producing navigation sort order should default to $CFG->navsortmycoursessort instead
- // of using the default.
- if (!empty($CFG->navsortmycoursessort)) {
- $sortorder = 'visible DESC, ' . $CFG->navsortmycoursessort . ' ASC';
- } else {
- $sortorder = 'visible DESC, sortorder ASC';
- }
- if ($courses = enrol_get_my_courses(NULL, $sortorder)) {
+ if ($courses = enrol_get_my_courses()) {
foreach ($courses as $course) {
$coursecontext = context_course::instance($course->id);
$linkcss = $course->visible ? "" : " class=\"dimmed\" ";
function xmldb_block_course_summary_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
'type' => 'text', 'size' => '15');
$this->content->text .= html_writer::empty_tag('input', $inputoptions);
+ // Context id.
+ if ($this->page->context && $this->page->context->contextlevel !== CONTEXT_SYSTEM) {
+ $this->content->text .= html_writer::empty_tag('input', ['type' => 'hidden',
+ 'name' => 'context', 'value' => $this->page->context->id]);
+ }
+
// Search button.
$this->content->text .= html_writer::tag('button', get_string('search', 'search'),
array('id' => 'searchform_button', 'type' => 'submit', 'title' => 'globalsearch', 'class' => 'btn btn-secondary'));
function xmldb_block_html_upgrade($oldversion) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
use renderable;
use renderer_base;
use templatable;
+use required_capability_exception;
/**
* Summary renderable class.
$this->user = $user;
// Get the plans.
- $this->plans = api::list_user_plans($this->user->id);
+ try {
+ $this->plans = api::list_user_plans($this->user->id);
+ } catch (required_capability_exception $e) {
+ $this->plans = [];
+ }
// Get the competencies to review.
$this->compstoreview = api::list_user_competencies_to_review(0, 3);
public function export_for_template(renderer_base $output) {
global $USER;
- $courses = enrol_get_my_courses('*', 'fullname ASC');
+ $courses = enrol_get_my_courses('*');
$coursesprogress = [];
foreach ($courses as $course) {
function xmldb_block_navigation_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
//Calculate minutes
$minutes = floor($timetoshowusers/60);
+ $periodminutes = get_string('periodnminutes', 'block_online_users', $minutes);
+
+ // Count users.
+ $usercount = $onlineusers->count_users();
+ if ($usercount === 0) {
+ $usercount = get_string('nouser', 'block_online_users');
+ } else if ($usercount === 1) {
+ $usercount = get_string('numuser', 'block_online_users', $usercount);
+ } else {
+ $usercount = get_string('numusers', 'block_online_users', $usercount);
+ }
+
+ $this->content->text = '<div class="info">'.$usercount.' ('.$periodminutes.')</div>';
// Verify if we can see the list of users, if not just print number of users
if (!has_capability('block/online_users:viewlist', $this->page->context)) {
- if (!$usercount = $onlineusers->count_users()) {
- $usercount = get_string("none");
- }
- $this->content->text = "<div class=\"info\">".get_string("periodnminutes","block_online_users",$minutes).": $usercount</div>";
return $this->content;
}
+
$userlimit = 50; // We'll just take the most recent 50 maximum.
if ($users = $onlineusers->get_users($userlimit)) {
foreach ($users as $user) {
$users = array();
}
- $usercount = $onlineusers->count_users();
- $usercount = ": $usercount";
-
- $this->content->text = "<div class=\"info\">(".get_string("periodnminutes","block_online_users",$minutes)."$usercount)</div>";
-
//Now, we have in users, the list of users to show
//Because they are online
if (!empty($users)) {
$this->content->text .= "</li>\n";
}
$this->content->text .= '</ul><div class="clearer"><!-- --></div>';
- } else {
- $this->content->text .= "<div class=\"info\">".get_string("none")."</div>";
}
return $this->content;
*/
$string['configtimetosee'] = 'Number of minutes determining the period of inactivity after which a user is no longer considered to be online.';
+$string['nouser'] = 'No online users';
+$string['numuser'] = '{$a} online user';
+$string['numusers'] = '{$a} online users';
$string['online_users:addinstance'] = 'Add a new online users block';
$string['online_users:myaddinstance'] = 'Add a new online users block to Dashboard';
$string['online_users:viewlist'] = 'View list of online users';
And I am on "Course 1" course homepage with editing mode on
When I add the "Online users" block
Then I should see "Teacher 1" in the "Online users" "block"
+ And I should see "1 online user" in the "Online users" "block"
Scenario: Add the online users on course page and see other logged in users
Given I log in as "teacher1"
Then I should see "Teacher 1" in the "Online users" "block"
And I should see "Student 1" in the "Online users" "block"
And I should not see "Student 2" in the "Online users" "block"
+ And I should see "2 online users" in the "Online users" "block"
Scenario: View the online users block on the dashboard and see myself
Given I log in as "teacher1"
Then I should see "Teacher 1" in the "Online users" "block"
+ And I should see "1 online user" in the "Online users" "block"
Scenario: View the online users block on the dashboard and see other logged in users
Given I log in as "student2"
Then I should see "Teacher 1" in the "Online users" "block"
And I should see "Student 1" in the "Online users" "block"
And I should see "Student 2" in the "Online users" "block"
+ And I should see "3 online users" in the "Online users" "block"
And I navigate to "Turn editing on" node in "Front page settings"
When I add the "Online users" block
Then I should see "Admin User" in the "Online users" "block"
+ And I should see "1 online user" in the "Online users" "block"
Scenario: View the online users block on the front page as a logged in user
Given I log in as "admin"
Then I should see "Admin User" in the "Online users" "block"
And I should see "Student 1" in the "Online users" "block"
And I should see "Student 2" in the "Online users" "block"
+ And I should see "3 online users" in the "Online users" "block"
Scenario: View the online users block on the front page as a guest
Given I log in as "admin"
Then I should see "Admin User" in the "Online users" "block"
And I should see "Student 1" in the "Online users" "block"
And I should see "Student 2" in the "Online users" "block"
+ And I should see "3 online users" in the "Online users" "block"
function xmldb_block_quiz_results_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_block_recent_activity_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_block_rss_client_upgrade($oldversion) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
"datepublished": "12 January 2016, 9:12 pm"
}
}}
-<li>
+<li class="p-y-1">
{{$title}}
<div class="link">
<a href="{{{link}}}" onclick='this.target="_blank"'>{{title}}</a>
{{$content}}
{{#description}}
+ <div class="date text-muted muted m-b-1">
+ <small>{{{datepublished}}}</small>
+ </div>
<div class="description">
{{{description}}}
</div>
function xmldb_block_section_links_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_block_selfcompletion_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
function xmldb_block_settings_upgrade($oldversion, $block) {
global $CFG;
- // Moodle v3.1.0 release upgrade line.
- // Put any upgrade step following this.
-
// Automatically generated Moodle v3.2.0 release upgrade line.
// Put any upgrade step following this.
if (empty($this->config->title)) {
$this->title = get_string('pluginname', 'block_tags');
} else {
- $this->title = $this->config->title;
+ $this->title = format_string($this->config->title, true, ['context' => $this->context]);
}
}
.fail(Notification.exception);
});
- var eventFormPromise = CalendarCrud.registerEventFormModal(root);
+ var eventFormPromise = CalendarCrud.registerEventFormModal(root),
+ contextId = $(SELECTORS.CALENDAR_MONTH_WRAPPER).data('context-id');
registerCalendarEventListeners(root, eventFormPromise);
- // Bind click events to calendar days.
- root.on('click', SELECTORS.DAY, function(e) {
-
- var target = $(e.target);
-
- if (!target.is(SELECTORS.VIEW_DAY_LINK)) {
- var startTime = $(this).attr('data-new-event-timestamp');
- eventFormPromise.then(function(modal) {
- var wrapper = target.closest(CalendarSelectors.wrapper);
- modal.setCourseId(wrapper.data('courseid'));
-
- var categoryId = wrapper.data('categoryid');
- if (typeof categoryId !== 'undefined') {
- modal.setCategoryId(categoryId);
- }
-
- modal.setContextId(wrapper.data('contextId'));
- modal.setStartTime(startTime);
- modal.show();
- return;
- })
- .fail(Notification.exception);
-
- e.preventDefault();
- }
- });
+ if (contextId) {
+ // Bind click events to calendar days.
+ root.on('click', SELECTORS.DAY, function (e) {
+
+ var target = $(e.target);
+
+ if (!target.is(SELECTORS.VIEW_DAY_LINK)) {
+ var startTime = $(this).attr('data-new-event-timestamp');
+ eventFormPromise.then(function (modal) {
+ var wrapper = target.closest(CalendarSelectors.wrapper);
+ modal.setCourseId(wrapper.data('courseid'));
+
+ var categoryId = wrapper.data('categoryid');
+ if (typeof categoryId !== 'undefined') {
+ modal.setCategoryId(categoryId);
+ }
+
+ modal.setContextId(wrapper.data('contextId'));
+ modal.setStartTime(startTime);
+ modal.show();
+ return;
+ })
+ .fail(Notification.exception);
+
+ e.preventDefault();
+ }
+ });
+ }
};
return {
$params['aftereventid'] = null;
}
- $courses = enrol_get_my_courses('*', 'visible DESC,sortorder ASC', 0, [$courseid]);
+ $courses = enrol_get_my_courses('*', null, 0, [$courseid]);
$courses = array_values($courses);
if (empty($courses)) {
}
$renderer = $PAGE->get_renderer('core_calendar');
- $courses = enrol_get_my_courses('*', 'visible DESC,sortorder ASC', 0, $params['courseids']);
+ $courses = enrol_get_my_courses('*', null, 0, $params['courseids']);
$courses = array_values($courses);
if (empty($courses)) {
}
$this->properties = $data;
-
- if (empty($data->context)) {
- $this->properties->context = $this->calculate_context();
- }
}
/**
return $context;
}
+ /**
+ * Returns the context for this event. The context is calculated
+ * the first time is is requested and then stored in a member
+ * variable to be returned each subsequent time.
+ *
+ * This is a magical getter function that will be called when
+ * ever the context property is accessed, e.g. $event->context.
+ *
+ * @return context
+ */
+ protected function get_context() {
+ if (!isset($this->properties->context)) {
+ $this->properties->context = $this->calculate_context();
+ }
+
+ return $this->properties->context;
+ }
+
/**
* Returns an array of editoroptions for this event.
*
// Check if we have already resolved the context for this event.
if ($this->editorcontext === null) {
// Switch on the event type to decide upon the appropriate context to use for this event.
- $this->editorcontext = $this->properties->context;
+ $this->editorcontext = $this->get_context();
if (!calendar_is_valid_eventtype($this->properties->eventtype)) {
return clean_text($this->properties->description, $this->properties->format);
}
// Prepare event data.
$eventargs = array(
- 'context' => $this->properties->context,
+ 'context' => $this->get_context(),
'objectid' => $this->properties->id,
'other' => array(
'repeatid' => empty($this->properties->repeatid) ? 0 : $this->properties->repeatid,
// were set when calculate_context() was called from the constructor.
if ($usingeditor) {
$this->properties->context = $this->calculate_context();
- $this->editorcontext = $this->properties->context;
+ $this->editorcontext = $this->get_context();
}
$editor = $this->properties->description;
// Log the event entry.
$eventargs['objectid'] = $this->properties->id;
- $eventargs['context'] = $this->properties->context;
+ $eventargs['context'] = $this->get_context();
$event = \core\event\calendar_event_created::create($eventargs);
$event->trigger();
// Trigger an event for the delete action.
$eventargs = array(
- 'context' => $this->properties->context,
+ 'context' => $this->get_context(),
'objectid' => $this->properties->id,
'other' => array(
'repeatid' => empty($this->properties->repeatid) ? 0 : $this->properties->repeatid,
// If the editor context hasn't already been set then set it now.
if ($this->editorcontext === null) {
- $this->editorcontext = $this->properties->context;
+ $this->editorcontext = $this->get_context();
}
// If the context has been set delete all associated files.
if ($properties->eventtype === 'site') {
// Site context.
- $this->editorcontext = $this->properties->context;
+ $this->editorcontext = $this->get_context();
} else if ($properties->eventtype === 'user') {
// User context.
- $this->editorcontext = $this->properties->context;
+ $this->editorcontext = $this->get_context();
} else if ($properties->eventtype === 'group' || $properties->eventtype === 'course') {
// First check the course is valid.
$course = $DB->get_record('course', array('id' => $properties->courseid));
print_error('invalidcourse');
}
// Course context.
- $this->editorcontext = $this->properties->context;
+ $this->editorcontext = $this->get_context();
// We have a course and are within the course context so we had
// better use the courses max bytes value.
$this->editoroptions['maxbytes'] = $course->maxbytes;
// First check the course is valid.
\coursecat::get($properties->categoryid, MUST_EXIST, true);
// Course context.
- $this->editorcontext = $this->properties->context;
+ $this->editorcontext = $this->get_context();
// We have a course and are within the course context so we had
// better use the courses max bytes value.
$this->editoroptions['maxbytes'] = $course->maxbytes;
// Prepare event data.
$eventargs = array(
- 'context' => $this->properties->context,
+ 'context' => $this->get_context(),
'objectid' => $this->properties->id,
'other' => array(
'repeatid' => empty($this->properties->repeatid) ? 0 : $this->properties->repeatid,
if ($this->editorcontext === null) {
// Switch on the event type to decide upon the appropriate context to use for this event.
- $this->editorcontext = $this->properties->context;
+ $this->editorcontext = $this->get_context();
if (!calendar_is_valid_eventtype($this->properties->eventtype)) {
// We don't have a context here, do a normal format_text.
<td class="dayblank"> </td>
{{/prepadding}}
{{#days}}
- <td class="clickable day text-sm-center text-md-left{{!
+ <td class="day text-sm-center text-md-left{{!
}}{{#istoday}} today{{/istoday}}{{!
}}{{#isweekend}} weekend{{/isweekend}}{{!
}}{{#durationevents.0}} duration{{/durationevents.0}}{{!
}}{{#durationevents}} duration_{{.}}{{/durationevents}}{{!
+ }}{{#defaulteventcontext}} clickable{{/defaulteventcontext}}{{!
}}"
data-day-timestamp="{{timestamp}}"
data-drop-zone="month-view-day"
$counter++;
if ($element == 'dateselector') {
- $el = $this->mform->addElement('date_selector', 'dateselector' . $counter, null, array('timezone' => 0.0, 'step' => 1));
+ $el = $this->mform->addElement('date_selector',
+ 'dateselector' . $counter, null, array('timezone' => 0.0));
} else {
- $el = $this->mform->addElement('date_time_selector', 'dateselector' . $counter, null, array('timezone' => 0.0, 'step' => 1, 'optional' => false));
+ $el = $this->mform->addElement('date_time_selector',
+ 'dateselector' . $counter, null, array('timezone' => 0.0, 'optional' => false));
}
$submitvalues = array('dateselector' . $counter => $date);
echo html_writer::start_tag('div', array('class'=>'heightcontainer'));
echo $OUTPUT->heading(get_string('calendar', 'calendar'));
-if ($view == 'day' || $view == 'upcoming') {
- switch($view) {
- case 'day':
- list($data, $template) = calendar_get_view($calendar, $view);
- echo $renderer->render_from_template($template, $data);
- break;
- case 'upcoming':
- list($data, $template) = calendar_get_view($calendar, $view);
- echo $renderer->render_from_template($template, $data);
- break;
- }
-} else if ($view == 'month') {
- list($data, $template) = calendar_get_view($calendar, $view);
- echo $renderer->render_from_template($template, $data);
-}
+
+list($data, $template) = calendar_get_view($calendar, $view);
+echo $renderer->render_from_template($template, $data);
+
echo html_writer::end_tag('div');
list($data, $template) = calendar_get_footer_options($calendar);
'default' => '',
'null' => NULL_ALLOWED
),
+ 'description' => array(
+ 'type' => PARAM_TEXT,
+ 'default' => '',
+ 'null' => NULL_ALLOWED
+ ),
+ 'descriptionformat' => array(
+ 'type' => PARAM_INT,
+ 'default' => FORMAT_HTML,
+ 'null' => NULL_ALLOWED
+ ),
'visible' => array(
'type' => PARAM_BOOL,
)
$excludedcontexts[] = $ctx->id;
}
}
+ $records->close();
return $excludedcontexts;
}
require_capability('moodle/competency:competencymanage', $competency->get_context());
// Reset the sortorder, use reorder instead.
- $competency->set('sortorder', null);
+ $competency->set('sortorder', 0);
$competency->create();
\core\event\competency_created::create_from_competency($competency)->trigger();
'default' => FORMAT_HTML
),
'sortorder' => array(
- 'default' => null,
+ 'default' => 0,
'type' => PARAM_INT
),
'parentid' => array(
$validcolumns = array('id', 'shortname', 'description', 'sortorder', 'idnumber',
'parentid', 'competencyframeworkid');
foreach ($params['filters'] as $filter) {
- if (!in_array($filter->column, $validcolumns)) {
+ if (!in_array($filter['column'], $validcolumns)) {
throw new invalid_parameter_exception('Filter column was invalid');
}
- $safefilters[$filter->column] = $filter->value;
+ $safefilters[$filter['column']] = $filter['value'];
}
$context = null;
),
'sortorder' => array(
'type' => PARAM_INT,
- 'default' => null,
+ 'default' => 0,
),
);
}
ORDER BY p.timesproficient ASC, c.id DESC';
$results = $DB->get_records_sql($sql, $params, $skip, $limit);
- $a = $DB->get_records_sql('SELECT * from {' . self::TABLE . '}');
$comps = array();
foreach ($results as $r) {
),
'sortorder' => array(
'type' => PARAM_INT,
- 'default' => null,
+ 'default' => 0,
),
);
}
'idnumber' => 'idnumber' . $number,
'description' => 'description' . $number,
'descriptionformat' => FORMAT_HTML,
- 'competencyframeworkid' => $frameworkid,
- 'sortorder' => 0
+ 'competencyframeworkid' => $frameworkid
);
$result = external::create_competency($competency);
return (object) external_api::clean_returnvalue(external::create_competency_returns(), $result);
'shortname' => 'shortname' . $number,
'idnumber' => 'idnumber' . $number,
'description' => 'description' . $number,
- 'descriptionformat' => FORMAT_HTML,
- 'sortorder' => 0
+ 'descriptionformat' => FORMAT_HTML
);
$result = external::update_competency($competency);
return external_api::clean_returnvalue(external::update_competency_returns(), $result);
$result = external::update_course_competency_settings($course->id, array('pushratingstouserplans' => true));
}
+ /**
+ * Test that we can list competencies with a filter.
+ *
+ * @return void
+ */
+ public function test_list_competencies_with_filter() {
+ $this->resetAfterTest(true);
+ $this->setAdminUser();
+ $dg = $this->getDataGenerator();
+ $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency');
+
+ $framework = $lpg->create_framework();
+ $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id')));
+ $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id')));
+ $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id')));
+ $c4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id')));
+ $c5 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id')));
+
+ // Test if removing competency from plan don't create sortorder holes.
+ $filters = [];
+ $sort = 'id';
+ $order = 'ASC';
+ $skip = 0;
+ $limit = 0;
+ $result = external::list_competencies($filters, $sort, $order, $skip, $limit);
+ $this->assertCount(5, $result);
+
+ $result = external::list_competencies($filters, $sort, $order, 2, $limit);
+ $this->assertCount(3, $result);
+ $result = external::list_competencies($filters, $sort, $order, 2, 2);
+ $this->assertCount(2, $result);
+
+ $filter = $result[0]->shortname;
+ $filters[0] = ['column' => 'shortname', 'value' => $filter];
+ $result = external::list_competencies($filters, $sort, $order, $skip, $limit);
+ $this->assertCount(1, $result);
+ $this->assertEquals($filter, $result[0]->shortname);
+ }
+
}
$completion = new completion_info($course);
$activities = $completion->get_activities();
- $progresses = $completion->get_progress_all();
+ $progresses = $completion->get_progress_all('u.id = :uid', ['uid' => $params['userid']]);
$userprogress = $progresses[$user->id];
$results = array();
$details['requirement'][] = get_string('markingyourselfcomplete', 'completion');
} elseif ($cm->completion == COMPLETION_TRACKING_AUTOMATIC) {
if ($cm->completionview) {
- $details['requirement'][] = get_string('viewingactivity', 'completion', $this->module);
+ $modulename = core_text::strtolower(get_string('modulename', $this->module));
+ $details['requirement'][] = get_string('viewingactivity', 'completion', $modulename);
}
if (!is_null($cm->completiongradeitemnumber)) {
"require-dev": {
"phpunit/phpunit": "6.4.*",
"phpunit/dbUnit": "3.0.*",
- "moodlehq/behat-extension": "3.34.1",
+ "moodlehq/behat-extension": "3.35.0",
"mikey179/vfsStream": "^1.6"
}
}
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "content-hash": "b36746ace2486c033136c855a63f3793",
+ "content-hash": "7cd70172c941fb07f0a2d4173baef5f1",
"packages": [],
"packages-dev": [
{
},
{
"name": "behat/mink-extension",
- "version": "v2.2",
+ "version": "2.3.0",
"source": {
"type": "git",
"url": "https://github.com/Behat/MinkExtension.git",
- "reference": "5b4bda64ff456104564317e212c823e45cad9d59"
+ "reference": "badc565b7a1d05c4a4bf49c789045bcf7af6c6de"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/5b4bda64ff456104564317e212c823e45cad9d59",
- "reference": "5b4bda64ff456104564317e212c823e45cad9d59",
+ "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/badc565b7a1d05c4a4bf49c789045bcf7af6c6de",
+ "reference": "badc565b7a1d05c4a4bf49c789045bcf7af6c6de",
"shasum": ""
},
"require": {
- "behat/behat": "~3.0,>=3.0.5",
- "behat/mink": "~1.5",
+ "behat/behat": "^3.0.5",
+ "behat/mink": "^1.5",
"php": ">=5.3.2",
- "symfony/config": "~2.2|~3.0"
+ "symfony/config": "^2.7|^3.0|^4.0"
},
"require-dev": {
- "behat/mink-goutte-driver": "~1.1",
- "phpspec/phpspec": "~2.0"
+ "behat/mink-goutte-driver": "^1.1",
+ "phpspec/phpspec": "^2.0"
},
"type": "behat-extension",
"extra": {
"test",
"web"
],
- "time": "2016-02-15T07:55:18+00:00"
+ "time": "2017-11-24T19:30:49+00:00"
},
{
"name": "behat/mink-goutte-driver",
},
{
"name": "fabpot/goutte",
- "version": "v3.2.1",
+ "version": "v3.2.2",
"source": {
"type": "git",
"url": "https://github.com/FriendsOfPHP/Goutte.git",
- "reference": "db5c28f4a010b4161d507d5304e28a7ebf211638"
+ "reference": "395f61d7c2e15a813839769553a4de16fa3b3c96"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/db5c28f4a010b4161d507d5304e28a7ebf211638",
- "reference": "db5c28f4a010b4161d507d5304e28a7ebf211638",
+ "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/395f61d7c2e15a813839769553a4de16fa3b3c96",
+ "reference": "395f61d7c2e15a813839769553a4de16fa3b3c96",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6.0",
"php": ">=5.5.0",
- "symfony/browser-kit": "~2.1|~3.0",
- "symfony/css-selector": "~2.1|~3.0",
- "symfony/dom-crawler": "~2.1|~3.0"
+ "symfony/browser-kit": "~2.1|~3.0|~4.0",
+ "symfony/css-selector": "~2.1|~3.0|~4.0",
+ "symfony/dom-crawler": "~2.1|~3.0|~4.0"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "^3.3 || ^4"
},
"type": "application",
"extra": {
"autoload": {
"psr-4": {
"Goutte\\": "Goutte"
- }
+ },
+ "exclude-from-classmap": [
+ "Goutte/Tests"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"keywords": [
"scraper"
],
- "time": "2017-01-03T13:21:43+00:00"
+ "time": "2017-11-19T08:45:40+00:00"
},
{
"name": "guzzlehttp/guzzle",
},
{
"name": "moodlehq/behat-extension",
- "version": "v3.34.1",
+ "version": "v3.35.0",
"source": {
"type": "git",
"url": "https://github.com/moodlehq/moodle-behat-extension.git",
},
{
"name": "phpspec/prophecy",
- "version": "v1.7.2",
+ "version": "1.7.3",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
- "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6"
+ "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6",
- "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf",
+ "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf",
"shasum": ""
},
"require": {
},
"require-dev": {
"phpspec/phpspec": "^2.5|^3.2",
- "phpunit/phpunit": "^4.8 || ^5.6.5"
+ "phpunit/phpunit": "^4.8.35 || ^5.7"
},
"type": "library",
"extra": {
"spy",
"stub"
],
- "time": "2017-09-04T11:05:03+00:00"
+ "time": "2017-11-24T13:59:53+00:00"
},
{
"name": "phpunit/dbunit",
- "version": "3.0.1",
+ "version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/dbunit.git",
- "reference": "6b9cec80dca8694243aade33bceb425ccafbbd0d"
+ "reference": "403350339b6aca748ee0067d027d85621992e21f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/6b9cec80dca8694243aade33bceb425ccafbbd0d",
- "reference": "6b9cec80dca8694243aade33bceb425ccafbbd0d",
+ "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/403350339b6aca748ee0067d027d85621992e21f",
+ "reference": "403350339b6aca748ee0067d027d85621992e21f",
"shasum": ""
},
"require": {
"ext-simplexml": "*",
"php": "^7.0",
"phpunit/phpunit": "^6.0",
- "symfony/yaml": "^3.0"
+ "symfony/yaml": "^3.0 || ^4.0"
},
"type": "library",
"extra": {
"testing",
"xunit"
],
- "time": "2017-10-19T13:21:48+00:00"
+ "time": "2017-11-18T17:40:34+00:00"
},
{
"name": "phpunit/php-code-coverage",
},
{
"name": "phpunit/php-file-iterator",
- "version": "1.4.2",
+ "version": "1.4.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
- "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5"
+ "reference": "8ebba84e5bd74fc5fdeb916b38749016c7232f93"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
- "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/8ebba84e5bd74fc5fdeb916b38749016c7232f93",
+ "reference": "8ebba84e5bd74fc5fdeb916b38749016c7232f93",
"shasum": ""
},
"require": {
"filesystem",
"iterator"
],
- "time": "2016-10-03T07:40:28+00:00"
+ "time": "2017-11-24T15:00:59+00:00"
},
{
"name": "phpunit/php-text-template",
},
{
"name": "symfony/browser-kit",
- "version": "v3.3.10",
+ "version": "v3.3.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
- "reference": "317d5bdf0127f06db7ea294186132b4f5b036839"
+ "reference": "03f957cd24bf939524f07b8b910c89cfcad722a8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/browser-kit/zipball/317d5bdf0127f06db7ea294186132b4f5b036839",
- "reference": "317d5bdf0127f06db7ea294186132b4f5b036839",
+ "url": "https://api.github.com/repos/symfony/browser-kit/zipball/03f957cd24bf939524f07b8b910c89cfcad722a8",
+ "reference": "03f957cd24bf939524f07b8b910c89cfcad722a8",
"shasum": ""
},
"require": {
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
- "time": "2017-10-02T06:42:24+00:00"
+ "time": "2017-11-07T14:12:55+00:00"
},
{
"name": "symfony/class-loader",
- "version": "v3.3.10",
+ "version": "v3.3.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/class-loader.git",
- "reference": "7572c904b209fa9907c69a6a9a68243c265a4d01"
+ "reference": "df173ac2af96ce202bf8bb5a3fc0bec8a4fdd4d1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/class-loader/zipball/7572c904b209fa9907c69a6a9a68243c265a4d01",
- "reference": "7572c904b209fa9907c69a6a9a68243c265a4d01",
+ "url": "https://api.github.com/repos/symfony/class-loader/zipball/df173ac2af96ce202bf8bb5a3fc0bec8a4fdd4d1",
+ "reference": "df173ac2af96ce202bf8bb5a3fc0bec8a4fdd4d1",
"shasum": ""
},
"require": {
],
"description": "Symfony ClassLoader Component",
"homepage": "https://symfony.com",
- "time": "2017-10-02T06:42:24+00:00"
+ "time": "2017-11-05T15:47:03+00:00"
},
{
"name": "symfony/config",
- "version": "v3.3.10",
+ "version": "v3.3.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "4ab62407bff9cd97c410a7feaef04c375aaa5cfd"
+ "reference": "8d2649077dc54dfbaf521d31f217383d82303c5f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/4ab62407bff9cd97c410a7feaef04c375aaa5cfd",
- "reference": "4ab62407bff9cd97c410a7feaef04c375aaa5cfd",
+ "url": "https://api.github.com/repos/symfony/config/zipball/8d2649077dc54dfbaf521d31f217383d82303c5f",
+ "reference": "8d2649077dc54dfbaf521d31f217383d82303c5f",
"shasum": ""
},
"require": {
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
- "time": "2017-10-04T18:56:58+00:00"
+ "time": "2017-11-07T14:16:22+00:00"
},
{
"name": "symfony/console",
- "version": "v3.3.10",
+ "version": "v3.3.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "116bc56e45a8e5572e51eb43ab58c769a352366c"
+ "reference": "63cd7960a0a522c3537f6326706d7f3b8de65805"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/116bc56e45a8e5572e51eb43ab58c769a352366c",
- "reference": "116bc56e45a8e5572e51eb43ab58c769a352366c",
+ "url": "https://api.github.com/repos/symfony/console/zipball/63cd7960a0a522c3537f6326706d7f3b8de65805",
+ "reference": "63cd7960a0a522c3537f6326706d7f3b8de65805",
"shasum": ""
},
"require": {
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2017-10-02T06:42:24+00:00"
+ "time": "2017-11-16T15:24:32+00:00"
},
{
"name": "symfony/css-selector",
- "version": "v3.3.10",
+ "version": "v3.3.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "07447650225ca9223bd5c97180fe7c8267f7d332"
+ "reference": "66e6e046032ebdf1f562c26928549f613d428bd1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/07447650225ca9223bd5c97180fe7c8267f7d332",
- "reference": "07447650225ca9223bd5c97180fe7c8267f7d332",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/66e6e046032ebdf1f562c26928549f613d428bd1",
+ "reference": "66e6e046032ebdf1f562c26928549f613d428bd1",
"shasum": ""
},
"require": {
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
- "time": "2017-10-02T06:42:24+00:00"
+ "time": "2017-11-05T15:47:03+00:00"
},
{
"name": "symfony/debug",
- "version": "v3.3.10",
+ "version": "v3.3.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
- "reference": "eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd"
+ "reference": "74557880e2846b5c84029faa96b834da37e29810"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/debug/zipball/eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd",
- "reference": "eb95d9ce8f18dcc1b3dfff00cb624c402be78ffd",
+ "url": "https://api.github.com/repos/symfony/debug/zipball/74557880e2846b5c84029faa96b834da37e29810",
+ "reference": "74557880e2846b5c84029faa96b834da37e29810",
"shasum": ""
},
"require": {
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
- "time": "2017-10-02T06:42:24+00:00"
+ "time": "2017-11-10T16:38:39+00:00"
},
{
"name": "symfony/dependency-injection",
- "version": "v3.3.10",
+ "version": "v3.3.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "8ebad929aee3ca185b05f55d9cc5521670821ad1"
+ "reference": "4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8ebad929aee3ca185b05f55d9cc5521670821ad1",
- "reference": "8ebad929aee3ca185b05f55d9cc5521670821ad1",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8",
+ "reference": "4e84f5af2c2d51ee3dee72df40b7fc08f49b4ab8",
"shasum": ""
},
"require": {
],
"description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com",
- "time": "2017-10-04T17:15:30+00:00"
+ "time": "2017-11-13T18:10:32+00:00"
},
{
"name": "symfony/dom-crawler",
- "version": "v3.3.10",
+ "version": "v3.3.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
- "reference": "40dafd42d5dad7fe5ad4e958413d92a207522ac1"
+ "reference": "cebe3c068867956e012d9135282ba6a05d8a259e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/40dafd42d5dad7fe5ad4e958413d92a207522ac1",
- "reference": "40dafd42d5dad7fe5ad4e958413d92a207522ac1",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/cebe3c068867956e012d9135282ba6a05d8a259e",
+ "reference": "cebe3c068867956e012d9135282ba6a05d8a259e",
"shasum": ""
},
"require": {