diff --git a/admin/cli/mysql_collation.php b/admin/cli/mysql_collation.php index adaadbba6c7..ae3c709816b 100644 --- a/admin/cli/mysql_collation.php +++ b/admin/cli/mysql_collation.php @@ -53,7 +53,7 @@ Options: -h, --help Print out this help Example: -\$ sudo -u www-data /usr/bin/php admin/cli/mysql_collation.php --collation=utf8_general_ci +\$ sudo -u www-data /usr/bin/php admin/cli/mysql_collation.php --collation=utf8mb4_unicode_ci "; if (!empty($options['collation'])) { @@ -145,9 +145,22 @@ if (!empty($options['collation'])) { $skipped++; } else { - $DB->change_database_structure("ALTER TABLE $table->name DEFAULT CHARACTER SET $charset DEFAULT COLLATE = $collation"); - echo "CONVERTED\n"; - $converted++; + try { + $DB->change_database_structure("ALTER TABLE $table->name CONVERT TO CHARACTER SET $charset COLLATE $collation"); + echo "CONVERTED\n"; + $converted++; + } catch (ddl_exception $e) { + $result = mysql_set_row_format($table->name, $charset, $collation, $engine); + if ($result) { + echo "CONVERTED\n"; + $converted++; + } else { + // We don't know what the problem is. Stop the conversion. + cli_error("Error: Tried to convert $table->name, but there was a problem. Please check the details of this + table and try again."); + die(); + } + } } $sql = "SHOW FULL COLUMNS FROM $table->name WHERE collation IS NOT NULL"; @@ -290,3 +303,26 @@ function mysql_get_column_collations($tablename) { $rs->close(); return $collations; } + +function mysql_set_row_format($tablename, $charset, $collation, $engine) { + global $DB; + + $sql = "SELECT row_format + FROM INFORMATION_SCHEMA.TABLES + WHERE table_schema = DATABASE() AND table_name = ?"; + $rs = $DB->get_record_sql($sql, array($tablename)); + if ($rs) { + if ($rs->row_format == 'Compact' || $rs->row_format == 'Redundant') { + $rowformat = $DB->get_row_format_sql($engine, $collation); + // Try to convert to compressed format and then try updating the collation again. + $DB->change_database_structure("ALTER TABLE $tablename $rowformat"); + $DB->change_database_structure("ALTER TABLE $tablename CONVERT TO CHARACTER SET $charset COLLATE $collation"); + } else { + // Row format may not be the problem. Can not diagnose problem. Send fail reply. + return false; + } + } else { + return false; + } + return true; +} diff --git a/admin/registration/forms.php b/admin/registration/forms.php index 92b15e8cb44..6e0b8a99bd1 100644 --- a/admin/registration/forms.php +++ b/admin/registration/forms.php @@ -232,7 +232,7 @@ class site_registration_form extends moodleform { } $language = get_config('hub', 'site_language_' . $cleanhuburl); if ($language === false) { - $language = current_language(); + $language = explode('_', current_language())[0]; } $geolocation = get_config('hub', 'site_geolocation_' . $cleanhuburl); $contactable = get_config('hub', 'site_contactable_' . $cleanhuburl); diff --git a/admin/roles/usersroles.php b/admin/roles/usersroles.php index 5e871e6198b..c455902c03e 100644 --- a/admin/roles/usersroles.php +++ b/admin/roles/usersroles.php @@ -193,8 +193,8 @@ function print_report_tree($contextid, $contexts, $systemcontext, $fullname, $al $strgoto = get_string('gotoassignroles', 'core_role', $a); $strcheck = get_string('checkuserspermissionshere', 'core_role', $a); } - echo ' ' . $OUTPUT->pix_icon('t/edit', 'core', $stredit) . ' '; - echo ' ' . $OUTPUT->pix_icon('t/preview', 'core', $strcheckpermissions) . ' '; + echo ' ' . $OUTPUT->pix_icon('t/edit', $stredit) . ' '; + echo ' ' . $OUTPUT->pix_icon('t/preview', $strcheckpermissions) . ' '; echo "

\n"; } } diff --git a/admin/tool/langimport/index.php b/admin/tool/langimport/index.php index 0e98a5d2de4..92a09dad241 100644 --- a/admin/tool/langimport/index.php +++ b/admin/tool/langimport/index.php @@ -40,7 +40,7 @@ if (empty($CFG->langotherroot)) { $mode = optional_param('mode', 0, PARAM_INT); // action $pack = optional_param_array('pack', array(), PARAM_SAFEDIR); // pack to install $uninstalllang = optional_param_array('uninstalllang', array(), PARAM_LANG);// installed pack to uninstall -$confirmtounistall = optional_param('confirmtouninstall', '', PARAM_ALPHAEXT); // uninstallation confirmation +$confirmtounistall = optional_param('confirmtouninstall', '', PARAM_SAFEPATH); // uninstallation confirmation $purgecaches = optional_param('purgecaches', false, PARAM_BOOL); // explicit caches reset if ($purgecaches) { @@ -74,7 +74,7 @@ if ($mode == DELETION_OF_SELECTED_LANG and (!empty($uninstalllang) or !empty($co // Actually deleting languages, languages to delete are passed as GET parameter as string // ...need to populate them to array. if (empty($uninstalllang)) { - $uninstalllang = explode('-', $confirmtounistall); + $uninstalllang = explode('/', $confirmtounistall); } if (in_array('en', $uninstalllang)) { @@ -84,8 +84,10 @@ if ($mode == DELETION_OF_SELECTED_LANG and (!empty($uninstalllang) or !empty($co } else if (empty($confirmtounistall) and confirm_sesskey()) { // User chose langs to be deleted, show confirmation. echo $OUTPUT->header(); echo $OUTPUT->confirm(get_string('uninstallconfirm', 'tool_langimport', implode(', ', $uninstalllang)), - 'index.php?mode='.DELETION_OF_SELECTED_LANG.'&confirmtouninstall='.implode('-', $uninstalllang), - 'index.php'); + new moodle_url($PAGE->url, array( + 'mode' => DELETION_OF_SELECTED_LANG, + 'confirmtouninstall' => implode('/', $uninstalllang), + )), $PAGE->url); echo $OUTPUT->footer(); die; diff --git a/admin/tool/log/backup/moodle2/restore_tool_log_logstore_subplugin.class.php b/admin/tool/log/backup/moodle2/restore_tool_log_logstore_subplugin.class.php index 0c00f00880a..c48c2c86c24 100644 --- a/admin/tool/log/backup/moodle2/restore_tool_log_logstore_subplugin.class.php +++ b/admin/tool/log/backup/moodle2/restore_tool_log_logstore_subplugin.class.php @@ -84,8 +84,7 @@ abstract class restore_tool_log_logstore_subplugin extends restore_subplugin { } } - // Roll dates. - $data->timecreated = $this->apply_date_offset($data->timecreated); + // There is no need to roll dates. Logs are supposed to be immutable. See MDL-44961. // Revert other to its original php way. $data->other = unserialize(base64_decode($data->other)); diff --git a/admin/tool/lp/templates/competency_rule_config.mustache b/admin/tool/lp/templates/competency_rule_config.mustache index b0e9db8440b..167a4237e8e 100644 --- a/admin/tool/lp/templates/competency_rule_config.mustache +++ b/admin/tool/lp/templates/competency_rule_config.mustache @@ -81,10 +81,11 @@ {{/config}} -
+
{{#config}} {{/config}}
+
diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index c788ac11c82..f88649614f9 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -3089,7 +3089,8 @@ class restore_course_logs_structure_step extends restore_structure_step { $data = (object)($data); - $data->time = $this->apply_date_offset($data->time); + // There is no need to roll dates. Logs are supposed to be immutable. See MDL-44961. + $data->userid = $this->get_mappingid('user', $data->userid); $data->course = $this->get_courseid(); $data->cmid = 0; @@ -3136,7 +3137,8 @@ class restore_activity_logs_structure_step extends restore_course_logs_structure $data = (object)($data); - $data->time = $this->apply_date_offset($data->time); + // There is no need to roll dates. Logs are supposed to be immutable. See MDL-44961. + $data->userid = $this->get_mappingid('user', $data->userid); $data->course = $this->get_courseid(); $data->cmid = $this->task->get_moduleid(); diff --git a/blocks/myoverview/amd/build/tab_preferences.min.js b/blocks/myoverview/amd/build/tab_preferences.min.js index 2bfb8fe06b0..da5bd970f13 100644 --- a/blocks/myoverview/amd/build/tab_preferences.min.js +++ b/blocks/myoverview/amd/build/tab_preferences.min.js @@ -1 +1 @@ -define(["jquery","core/ajax","core/custom_interaction_events"],function(a,b,c){var d=function(d){c.define(d,[c.events.activate]),d.on(c.events.activate,"[data-toggle='tab']",function(c){var d=a(c.currentTarget).data("tabname");"function"==typeof window.history.pushState&&window.history.pushState(null,null,"?myoverviewtab="+d);var e={methodname:"core_user_update_user_preferences",args:{preferences:[{type:"block_myoverview_last_tab",value:d}]}};b.call([e])[0].fail(Notification.exception)})};return{registerEventListeners:d}}); \ No newline at end of file +define(["jquery","core/ajax","core/custom_interaction_events","core/notification"],function(a,b,c,d){var e=function(e){c.define(e,[c.events.activate]),e.on(c.events.activate,"[data-toggle='tab']",function(c){var e=a(c.currentTarget).data("tabname");"function"==typeof window.history.pushState&&window.history.pushState(null,null,"?myoverviewtab="+e);var f={methodname:"core_user_update_user_preferences",args:{preferences:[{type:"block_myoverview_last_tab",value:e}]}};b.call([f])[0].fail(d.exception)})};return{registerEventListeners:e}}); \ No newline at end of file diff --git a/blocks/myoverview/amd/src/tab_preferences.js b/blocks/myoverview/amd/src/tab_preferences.js index f6afc1b6193..25ac2eefa3d 100644 --- a/blocks/myoverview/amd/src/tab_preferences.js +++ b/blocks/myoverview/amd/src/tab_preferences.js @@ -21,7 +21,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery', 'core/ajax', 'core/custom_interaction_events'], function($, Ajax, CustomEvents) { +define(['jquery', 'core/ajax', 'core/custom_interaction_events', + 'core/notification'], function($, Ajax, CustomEvents, Notification) { /** * Registers an event that saves the user's tab preference when switching between them. diff --git a/blocks/navigation/amd/build/ajax_response_renderer.min.js b/blocks/navigation/amd/build/ajax_response_renderer.min.js index 4c6949207c8..02578ec2d3c 100644 --- a/blocks/navigation/amd/build/ajax_response_renderer.min.js +++ b/blocks/navigation/amd/build/ajax_response_renderer.min.js @@ -1 +1 @@ -define(["jquery"],function(a){function b(d,e){var f=a("");f.attr("role","group"),f.attr("aria-hidden",!0),a.each(e,function(d,e){if("object"==typeof e){var g=a("
  • "),h=a("

    "),i=e.id||e.key+"_tree_item",j=null,k=!(!e.expandable&&!e.haschildren);if(h.addClass("tree_item"),h.attr("id",i),h.attr("role","treeitem"),h.attr("tabindex","-1"),e.requiresajaxloading&&(h.attr("data-requires-ajax",!0),h.attr("data-node-id",e.id),h.attr("data-node-key",e.key),h.attr("data-node-type",e.type)),k&&(g.addClass("collapsed contains_branch"),h.attr("aria-expanded",!1),h.addClass("branch")),!e.icon||k&&e.type!==c.ACTIVITY&&e.type!==c.RESOURCE||(g.addClass("item_with_icon"),h.addClass("hasicon"),j=a(""),j.attr("alt",e.icon.alt),j.attr("title",e.icon.title),j.attr("src",M.util.image_url(e.icon.pix,e.icon.component)),a.each(e.icon.classes,function(a,b){j.addClass(b)})),e.link){var l=a('');j?(l.append(j),l.append(''+e.name+"")):l.append(e.name),e.hidden&&l.addClass("dimmed"),h.append(l)}else{var m=a("");j?(m.append(j),m.append(''+e.name+"")):m.append(e.name),e.hidden&&m.addClass("dimmed"),h.append(m)}g.append(h),f.append(g),e.children&&e.children.length?b(h,e.children):k&&!e.requiresajaxloading&&(g.removeClass("contains_branch"),h.addClass("emptybranch"))}}),d.parent().append(f);var g=d.attr("id")+"_group";f.attr("id",g),d.attr("aria-owns",g),d.attr("role","treeitem")}var c={ACTIVITY:40,RESOURCE:50};return{render:function(a,c){if(c.children&&c.children.length){b(a,c.children);var d=a.children("[role='treeitem']").first(),e=a.find("#"+d.attr("aria-owns"));d.attr("aria-expanded",!0),e.attr("aria-hidden",!1)}else a.parent().hasClass("contains_branch")&&(a.parent().removeClass("contains_branch"),a.addClass("emptybranch"))}}}); \ No newline at end of file +define(["jquery","core/templates","core/notification","core/url"],function(a,b,c,d){function e(g,h){var i=a("");i.attr("role","group"),i.attr("aria-hidden",!0),a.each(h,function(g,h){if("object"==typeof h){var j=a("
  • "),k=a("

    "),l=h.id||h.key+"_tree_item",m=null,n=!(!h.expandable&&!h.haschildren);k.addClass("tree_item"),k.attr("id",l),k.attr("role","treeitem"),k.attr("tabindex","-1"),h.requiresajaxloading&&(k.attr("data-requires-ajax",!0),k.attr("data-node-id",h.id),k.attr("data-node-key",h.key),k.attr("data-node-type",h.type)),n&&(j.addClass("collapsed contains_branch"),k.attr("aria-expanded",!1),k.addClass("branch"));var o=null;if(h.link){var p=a('');o=p,p.append(''+h.name+""),h.hidden&&p.addClass("dimmed"),k.append(p)}else{var q=a("");o=q,q.append(''+h.name+""),h.hidden&&q.addClass("dimmed"),k.append(q)}!h.icon||n&&h.type!==f.ACTIVITY&&h.type!==f.RESOURCE||(j.addClass("item_with_icon"),k.addClass("hasicon"),h.type===f.ACTIVITY||h.type===f.RESOURCE?(m=a(""),m.attr("alt",h.icon.alt),m.attr("title",h.icon.title),m.attr("src",d.imageUrl(h.icon.pix,h.icon.component)),a.each(h.icon.classes,function(a,b){m.addClass(b)}),o.prepend(m)):("moodle"==h.icon.component&&(h.icon.component="core"),b.renderPix(h.icon.pix,h.icon.component,h.icon.title).then(function(a){o.prepend(a)})["catch"](c.exception))),j.append(k),i.append(j),h.children&&h.children.length?e(k,h.children):n&&!h.requiresajaxloading&&(j.removeClass("contains_branch"),k.addClass("emptybranch"))}}),g.parent().append(i);var j=g.attr("id")+"_group";i.attr("id",j),g.attr("aria-owns",j),g.attr("role","treeitem")}var f={ACTIVITY:40,RESOURCE:50};return{render:function(a,b){if(b.children&&b.children.length){e(a,b.children);var c=a.children("[role='treeitem']").first(),d=a.find("#"+c.attr("aria-owns"));c.attr("aria-expanded",!0),d.attr("aria-hidden",!1)}else a.parent().hasClass("contains_branch")&&(a.parent().removeClass("contains_branch"),a.addClass("emptybranch"))}}}); \ No newline at end of file diff --git a/blocks/navigation/amd/src/ajax_response_renderer.js b/blocks/navigation/amd/src/ajax_response_renderer.js index d9357424d1f..99f2e28f1fe 100644 --- a/blocks/navigation/amd/src/ajax_response_renderer.js +++ b/blocks/navigation/amd/src/ajax_response_renderer.js @@ -22,7 +22,7 @@ * @copyright 2015 John Okely * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery'], function($) { +define(['jquery', 'core/templates', 'core/notification', 'core/url'], function($, Templates, Notification, Url) { // Mappings for the different types of nodes coming from the navigation. // Copied from lib/navigationlib.php navigation_node constants. @@ -75,28 +75,12 @@ define(['jquery'], function($) { p.addClass('branch'); } - if (node.icon && (!isBranch || node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE)) { - li.addClass('item_with_icon'); - p.addClass('hasicon'); - - icon = $(''); - icon.attr('alt', node.icon.alt); - icon.attr('title', node.icon.title); - icon.attr('src', M.util.image_url(node.icon.pix, node.icon.component)); - $.each(node.icon.classes, function(index, className) { - icon.addClass(className); - }); - } - + var eleToAddIcon = null; if (node.link) { var link = $(''); - if (icon) { - link.append(icon); - link.append('' + node.name + ''); - } else { - link.append(node.name); - } + eleToAddIcon = link; + link.append('' + node.name + ''); if (node.hidden) { link.addClass('dimmed'); @@ -106,12 +90,8 @@ define(['jquery'], function($) { } else { var span = $(''); - if (icon) { - span.append(icon); - span.append('' + node.name + ''); - } else { - span.append(node.name); - } + eleToAddIcon = span; + span.append('' + node.name + ''); if (node.hidden) { span.addClass('dimmed'); @@ -120,6 +100,31 @@ define(['jquery'], function($) { p.append(span); } + if (node.icon && (!isBranch || node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE)) { + li.addClass('item_with_icon'); + p.addClass('hasicon'); + + if (node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE) { + icon = $(''); + icon.attr('alt', node.icon.alt); + icon.attr('title', node.icon.title); + icon.attr('src', Url.imageUrl(node.icon.pix, node.icon.component)); + $.each(node.icon.classes, function(index, className) { + icon.addClass(className); + }); + eleToAddIcon.prepend(icon); + } else { + if (node.icon.component == 'moodle') { + node.icon.component = 'core'; + } + Templates.renderPix(node.icon.pix, node.icon.component, node.icon.title).then(function(html) { + // Prepend. + eleToAddIcon.prepend(html); + return; + }).catch(Notification.exception); + } + } + li.append(p); ul.append(li); diff --git a/blocks/navigation/styles.css b/blocks/navigation/styles.css index bb541f55926..2369c1af0d6 100644 --- a/blocks/navigation/styles.css +++ b/blocks/navigation/styles.css @@ -71,6 +71,6 @@ display: block; } -.block_navigation .block_tree [aria-hidden="true"] { +.block_navigation .block_tree [aria-hidden="true"]:not(.icon) { display: none; } diff --git a/blocks/settings/styles.css b/blocks/settings/styles.css index e0c94b2bad6..3d7e1d1a3e2 100644 --- a/blocks/settings/styles.css +++ b/blocks/settings/styles.css @@ -58,7 +58,7 @@ display: block; } -.block_settings .block_tree [aria-hidden="true"] { +.block_settings .block_tree [aria-hidden="true"]:not(.icon) { display: none; } diff --git a/calendar/classes/local/event/container.php b/calendar/classes/local/event/container.php index 20b39ec5728..9ac8cbf3432 100644 --- a/calendar/classes/local/event/container.php +++ b/calendar/classes/local/event/container.php @@ -71,11 +71,6 @@ class container { */ protected static $eventretrievalstrategy; - /** - * @var array A list of callbacks to use. - */ - protected static $callbacks = array(); - /** * @var \stdClass[] An array of cached courses to use with the event factory. */ @@ -91,16 +86,6 @@ class container { */ private static function init() { if (empty(self::$eventfactory)) { - // When testing the container's components, we need to make sure - // the callback implementations in modules are not executed, since - // we cannot control their output from PHPUnit. To do this we have - // a set of 'testing' callbacks that the factory can use. This way - // we know exactly how the factory behaves when being tested. - $getcallback = function($which) { - return self::$callbacks[PHPUNIT_TEST ? 'testing' : 'production'][$which]; - }; - - self::initcallbacks(); self::$actionfactory = new action_factory(); self::$eventmapper = new event_mapper( // The event mapper we return from here needs to know how to @@ -129,8 +114,8 @@ class container { ); self::$eventfactory = new event_factory( - $getcallback('action'), - $getcallback('visibility'), + [self::class, 'apply_component_provide_event_action'], + [self::class, 'apply_component_is_event_visible'], function ($dbrow) { // At present we only have a bail-out check for events in course modules. if (empty($dbrow->modulename)) { @@ -183,6 +168,19 @@ class container { } } + /** + * Reset all static caches, called between tests. + */ + public static function reset_caches() { + self::$eventfactory = null; + self::$eventmapper = null; + self::$eventvault = null; + self::$actionfactory = null; + self::$eventretrievalstrategy = null; + self::$coursecache = []; + self::$modulecache = []; + } + /** * Gets the event factory. * @@ -214,88 +212,74 @@ class container { } /** - * Initialises the callbacks. + * Calls callback 'core_calendar_provide_event_action' from the component responsible for the event * - * There are two sets here, one is used during PHPUnit runs. - * See the comment at the start of the init method for more - * detail. + * If no callback is present or callback returns null, there is no action on the event + * and it will not be displayed on the dashboard. + * + * @param event_interface $event + * @return action_event|event_interface */ - private static function initcallbacks() { - self::$callbacks = array( - 'testing' => array( - // Always return an action event. - 'action' => function (event_interface $event) { - return new action_event( - $event, - new \core_calendar\local\event\value_objects\action( - 'test', - new \moodle_url('http://example.com'), - 420, - true - )); - }, - // Always be visible. - 'visibility' => function (event_interface $event) { - return true; - } - ), - 'production' => array( - // This function has type event_interface -> event_interface. - // This is enforced by the event_factory. - 'action' => function (event_interface $event) { - // Callbacks will get supplied a "legacy" version - // of the event class. - $mapper = self::$eventmapper; - $action = null; - if ($event->get_course_module()) { - // TODO MDL-58866 Only activity modules currently support this callback. - // Any other event will not be displayed on the dashboard. - $action = component_callback( - 'mod_' . $event->get_course_module()->get('modname'), - 'core_calendar_provide_event_action', - [ - $mapper->from_event_to_legacy_event($event), - self::$actionfactory - ] - ); - } + public static function apply_component_provide_event_action(event_interface $event) { + // Callbacks will get supplied a "legacy" version + // of the event class. + $mapper = self::$eventmapper; + $action = null; + if ($event->get_course_module()) { + // TODO MDL-58866 Only activity modules currently support this callback. + // Any other event will not be displayed on the dashboard. + $action = component_callback( + 'mod_' . $event->get_course_module()->get('modname'), + 'core_calendar_provide_event_action', + [ + $mapper->from_event_to_legacy_event($event), + self::$actionfactory + ] + ); + } - // If we get an action back, return an action event, otherwise - // continue piping through the original event. - // - // If a module does not implement the callback, component_callback - // returns null. - return $action ? new action_event($event, $action) : $event; - }, - // This function has type event_interface -> bool. - // This is enforced by the event_factory. - 'visibility' => function (event_interface $event) { - $mapper = self::$eventmapper; - $eventvisible = null; - if ($event->get_course_module()) { - // TODO MDL-58866 Only activity modules currently support this callback. - $eventvisible = component_callback( - 'mod_' . $event->get_course_module()->get('modname'), - 'core_calendar_is_event_visible', - [ - $mapper->from_event_to_legacy_event($event) - ] - ); - } + // If we get an action back, return an action event, otherwise + // continue piping through the original event. + // + // If a module does not implement the callback, component_callback + // returns null. + return $action ? new action_event($event, $action) : $event; + } - // Do not display the event if there is nothing to action. - if ($event instanceof action_event_interface && $event->get_action()->get_item_count() === 0) { - return false; - } + /** + * Calls callback 'core_calendar_is_event_visible' from the component responsible for the event + * + * The visibility callback is optional, if not present it is assumed as visible. + * If it is an actionable event but the get_item_count() returns 0 the visibility + * is set to false. + * + * @param event_interface $event + * @return bool + */ + public static function apply_component_is_event_visible(event_interface $event) { + $mapper = self::$eventmapper; + $eventvisible = null; + if ($event->get_course_module()) { + // TODO MDL-58866 Only activity modules currently support this callback. + $eventvisible = component_callback( + 'mod_' . $event->get_course_module()->get('modname'), + 'core_calendar_is_event_visible', + [ + $mapper->from_event_to_legacy_event($event) + ] + ); + } - // Module does not implement the callback, event should be visible. - if (is_null($eventvisible)) { - return true; - } + // Do not display the event if there is nothing to action. + if ($event instanceof action_event_interface && $event->get_action()->get_item_count() === 0) { + return false; + } - return $eventvisible ? true : false; - } - ), - ); + // Module does not implement the callback, event should be visible. + if (is_null($eventvisible)) { + return true; + } + + return $eventvisible ? true : false; } } diff --git a/calendar/tests/externallib_test.php b/calendar/tests/externallib_test.php index 9912bcc1381..fc0ecc2effc 100644 --- a/calendar/tests/externallib_test.php +++ b/calendar/tests/externallib_test.php @@ -674,13 +674,15 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase { */ public function test_get_calendar_events_override() { $user = $this->getDataGenerator()->create_user(); + $user2 = $this->getDataGenerator()->create_user(); $teacher = $this->getDataGenerator()->create_user(); $anotheruser = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign'); $moduleinstance = $generator->create_instance(['course' => $course->id]); - $this->getDataGenerator()->enrol_user($user->id, $course->id); + $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); + $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student'); $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher'); $this->resetAfterTest(true); $this->setAdminUser(); @@ -692,11 +694,12 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase { ]; $now = time(); + // Create two events - one for everybody in the course and one only for the first student. $event1 = $this->create_calendar_event('Base event', 0, 'due', 0, $now + DAYSECS, $params + ['courseid' => $course->id]); $event2 = $this->create_calendar_event('User event', $user->id, 'due', 0, $now + 2*DAYSECS, $params + ['courseid' => 0]); - // Retrieve course events for teacher - only one "Base event" is returned. - $this->setUser($teacher); + // Retrieve course events for the second student - only one "Base event" is returned. + $this->setUser($user2); $paramevents = array('courseids' => array($course->id)); $options = array ('siteevents' => true, 'userevents' => true); $events = core_calendar_external::get_calendar_events($paramevents, $options); @@ -705,7 +708,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase { $this->assertEquals(0, count($events['warnings'])); $this->assertEquals('Base event', $events['events'][0]['name']); - // Retrieve events for user - both events are returned. + // Retrieve events for the first student - both events are returned. $this->setUser($user); $events = core_calendar_external::get_calendar_events($paramevents, $options); $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events); diff --git a/course/format/renderer.php b/course/format/renderer.php index f16bdd61ca3..b630ad8bef4 100644 --- a/course/format/renderer.php +++ b/course/format/renderer.php @@ -559,7 +559,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base { // so there is definitely something to print. $formattedinfo = \core_availability\info::format_info( $section->availableinfo, $section->course); - $o .= $this->courserenderer->availability_info($formattedinfo); + $o .= $this->courserenderer->availability_info($formattedinfo, 'isrestricted'); } } else if ($canviewhidden && !empty($CFG->enableavailability)) { // Check if there is an availability restriction. @@ -568,7 +568,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base { if ($fullinfo) { $formattedinfo = \core_availability\info::format_info( $fullinfo, $section->course); - $o .= $this->courserenderer->availability_info($formattedinfo); + $o .= $this->courserenderer->availability_info($formattedinfo, 'isrestricted isfullinfo'); } } return $o; diff --git a/course/renderer.php b/course/renderer.php index bee3fe78f70..565f90c3f80 100644 --- a/course/renderer.php +++ b/course/renderer.php @@ -728,7 +728,24 @@ class core_course_renderer extends plugin_renderer_base { * @return string */ public function availability_info($text, $additionalclasses = '') { + $data = ['text' => $text, 'classes' => $additionalclasses]; + $additionalclasses = array_filter(explode(' ', $additionalclasses)); + + if (in_array('ishidden', $additionalclasses)) { + $data['ishidden'] = 1; + + } else if (in_array('isstealth', $additionalclasses)) { + $data['isstealth'] = 1; + + } else if (in_array('isrestricted', $additionalclasses)) { + $data['isrestricted'] = 1; + + if (in_array('isfullinfo', $additionalclasses)) { + $data['isfullinfo'] = 1; + } + } + return $this->render_from_template('core/availability_info', $data); } @@ -752,7 +769,7 @@ class core_course_renderer extends plugin_renderer_base { if (!empty($mod->availableinfo)) { $formattedinfo = \core_availability\info::format_info( $mod->availableinfo, $mod->get_course()); - $output = $this->availability_info($formattedinfo); + $output = $this->availability_info($formattedinfo, 'isrestricted'); } return $output; } @@ -775,9 +792,9 @@ class core_course_renderer extends plugin_renderer_base { // Display information about conditional availability. // Don't add availability information if user is not editing and activity is hidden. if ($mod->visible || $this->page->user_is_editing()) { - $hidinfoclass = ''; + $hidinfoclass = 'isrestricted isfullinfo'; if (!$mod->visible) { - $hidinfoclass = 'hide'; + $hidinfoclass .= ' hide'; } $ci = new \core_availability\info_module($mod); $fullinfo = $ci->get_full_information(); diff --git a/filter/urltolink/filter.php b/filter/urltolink/filter.php index b232edae8b8..211e149e837 100644 --- a/filter/urltolink/filter.php +++ b/filter/urltolink/filter.php @@ -73,8 +73,8 @@ class filter_urltolink extends moodle_text_filter { // //<a href="blah"> //<a href="blah"> - $filterignoretagsopen = array(']+?>'); - $filterignoretagsclose = array(''); + $filterignoretagsopen = array(']+?>', ']+?class="nolink"[^>]*?>'); + $filterignoretagsclose = array('', ''); filter_save_ignore_tags($text,$filterignoretagsopen,$filterignoretagsclose,$ignoretags); // Check if we support unicode modifiers in regular expressions. Cache it. @@ -174,4 +174,3 @@ function filter_urltolink_img_callback($link) { } return ''; } - diff --git a/filter/urltolink/tests/filter_test.php b/filter/urltolink/tests/filter_test.php index 0ae7f891cff..1dca5b7f74a 100644 --- a/filter/urltolink/tests/filter_test.php +++ b/filter/urltolink/tests/filter_test.php @@ -174,6 +174,9 @@ class filter_urltolink_filter_testcase extends basic_testcase { '' => '', 'www.google.com' => 'www.google.com', 'http://nolandforzombies.com Zombies FTW http://aliens.org' => 'http://nolandforzombies.com Zombies FTW http://aliens.org', + // Test 'nolink' class. + 'URL: http://moodle.org' => 'URL: http://moodle.org', + 'URL: http://moodle.org' => 'URL: http://moodle.org', //URLs in Javascript. Commented out as part of MDL-21183 //'var url="http://moodle.org";'=>'var url="http://moodle.org";', //'var url = "http://moodle.org";'=>'var url = "http://moodle.org";', diff --git a/group/classes/output/index_page.php b/group/classes/output/index_page.php new file mode 100644 index 00000000000..8430aae74b9 --- /dev/null +++ b/group/classes/output/index_page.php @@ -0,0 +1,111 @@ +. + +/** + * Group index page. + * + * @package core_group + * @copyright 2017 Jun Pataleta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_group\output; +defined('MOODLE_INTERNAL') || die(); + +use renderable; +use renderer_base; +use stdClass; +use templatable; + +/** + * Group index page class. + * + * @package core_group + * @copyright 2017 Jun Pataleta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class index_page implements renderable, templatable { + + /** @var int $courseid The course ID. */ + public $courseid; + + /** @var array The array of groups to be rendered. */ + public $groups; + + /** @var string The name of the currently selected group. */ + public $selectedgroupname; + + /** @var array The array of group members to be rendered, if a group is selected. */ + public $selectedgroupmembers; + + /** @var bool Whether to disable the add members/edit group buttons. */ + public $disableaddedit; + + /** @var bool Whether to disable the delete group button. */ + public $disabledelete; + + /** @var array Groups that can't be deleted by the user. */ + public $undeletablegroups; + + /** + * index_page constructor. + * + * @param int $courseid The course ID. + * @param array $groups The array of groups to be rendered. + * @param string $selectedgroupname The name of the currently selected group. + * @param array $selectedgroupmembers The array of group members to be rendered, if a group is selected. + * @param bool $disableaddedit Whether to disable the add members/edit group buttons. + * @param bool $disabledelete Whether to disable the delete group button. + * @param array $undeletablegroups Groups that can't be deleted by the user. + */ + public function __construct($courseid, $groups, $selectedgroupname, $selectedgroupmembers, $disableaddedit, $disabledelete, + $undeletablegroups) { + $this->courseid = $courseid; + $this->groups = $groups; + $this->selectedgroupname = $selectedgroupname; + $this->selectedgroupmembers = $selectedgroupmembers; + $this->disableaddedit = $disableaddedit; + $this->disabledelete = $disabledelete; + $this->undeletablegroups = $undeletablegroups; + } + + /** + * Export the data. + * + * @param renderer_base $output + * @return stdClass + */ + public function export_for_template(renderer_base $output) { + global $CFG; + + $data = new stdClass(); + + // Variables that will be passed to the JS helper. + $data->courseid = $this->courseid; + $data->wwwroot = $CFG->wwwroot; + // To be passed to the JS init script in the template. Encode as a JSON string. + $data->undeletablegroups = json_encode($this->undeletablegroups); + + // Some buttons are enabled if single group selected. + $data->addmembersdisabled = $this->disableaddedit; + $data->editgroupsettingsdisabled = $this->disableaddedit; + $data->deletegroupdisabled = $this->disabledelete; + $data->groups = $this->groups; + $data->members = $this->selectedgroupmembers; + $data->selectedgroup = $this->selectedgroupname; + + return $data; + } +} diff --git a/group/classes/output/renderer.php b/group/classes/output/renderer.php new file mode 100644 index 00000000000..14443cf7891 --- /dev/null +++ b/group/classes/output/renderer.php @@ -0,0 +1,50 @@ +. + +/** + * Renderers. + * + * @package core_group + * @copyright 2017 Jun Pataleta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_group\output; + +defined('MOODLE_INTERNAL') || die(); + +use plugin_renderer_base; + +/** + * Renderer class. + * + * @package core_group + * @copyright 2017 Jun Pataleta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class renderer extends plugin_renderer_base { + + /** + * Defer to template. + * + * @param index_page $page + * @return string + */ + public function render_index_page(index_page $page) { + $data = $page->export_for_template($this); + return parent::render_from_template('core_group/index', $data); + } +} diff --git a/group/externallib.php b/group/externallib.php index 5359a373f17..c3d25304e1e 100644 --- a/group/externallib.php +++ b/group/externallib.php @@ -1386,6 +1386,7 @@ class core_group_external extends external_api { $results = array( 'groups' => $usergroups, + 'canaccessallgroups' => has_capability('moodle/site:accessallgroups', $context, $user), 'warnings' => $warnings ); return $results; @@ -1401,6 +1402,8 @@ class core_group_external extends external_api { return new external_single_structure( array( 'groups' => new external_multiple_structure(self::group_description()), + 'canaccessallgroups' => new external_value(PARAM_BOOL, + 'Whether the user will be able to access all the activity groups.', VALUE_OPTIONAL), 'warnings' => new external_warnings(), ) ); diff --git a/group/index.php b/group/index.php index 6571f545d91..8150e196f00 100644 --- a/group/index.php +++ b/group/index.php @@ -61,6 +61,7 @@ $context = context_course::instance($course->id); require_capability('moodle/course:managegroups', $context); $PAGE->requires->js('/group/clientlib.js'); +$PAGE->requires->js('/group/module.js'); // Check for multiple/no group errors if (!$singlegroup) { @@ -152,41 +153,23 @@ echo $OUTPUT->header(); $currenttab = 'groups'; require('tabs.php'); -$disabled = 'disabled="disabled"'; - -// Some buttons are enabled if single group selected. -$showaddmembersform_disabled = $singlegroup ? '' : $disabled; -$showeditgroupsettingsform_disabled = $singlegroup ? '' : $disabled; -$deletegroup_disabled = count($groupids) > 0 ? '' : $disabled; - echo $OUTPUT->heading(format_string($course->shortname, true, array('context' => $context)) .' '.$strgroups, 3); -echo '
    '."\n"; -echo '
    '."\n"; -echo ''."\n"; - -echo html_writer::start_tag('div', array('class' => 'groupmanagementtable boxaligncenter')); -echo html_writer::start_tag('div', array('class' => 'groups')); - -echo '

    '."\n"; - -$onchange = 'M.core_group.membersCombo.refreshMembers();'; - -echo ''."\n"; -echo '

    '."\n"; -echo '

    '."\n"; -echo '

    '."\n"; - -echo '

    '."\n"; - -echo '

    '."\n"; - -echo '

    '."\n"; - -echo html_writer::end_tag('div'); -echo html_writer::start_tag('div', array('class' => 'members')); - -echo '

    '."\n"; -//NOTE: the SELECT was, multiple="multiple" name="user[]" - not used and breaks onclick. -echo ''."\n"; - -echo '

    '."\n"; -echo html_writer::end_tag('div'); -echo html_writer::end_tag('div'); - -// -echo '
    '."\n"; -echo '
    '."\n"; - -$PAGE->requires->js_init_call('M.core_group.init_index', array($CFG->wwwroot, $courseid)); -$PAGE->requires->js_init_call('M.core_group.groupslist', array($preventgroupremoval)); +$disableaddedit = !$singlegroup; +$disabledelete = !empty($groupids); +$renderable = new \core_group\output\index_page($courseid, $groupoptions, $selectedname, $members, $disableaddedit, $disabledelete, + $preventgroupremoval); +$output = $PAGE->get_renderer('core_group'); +echo $output->render($renderable); echo $OUTPUT->footer(); diff --git a/group/templates/index.mustache b/group/templates/index.mustache new file mode 100644 index 00000000000..e68bdaf2da4 --- /dev/null +++ b/group/templates/index.mustache @@ -0,0 +1,145 @@ +{{! + 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 . +}} +{{! + @template core_group/index + + Template for the Groups page. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * courseid int The course ID. + * selectedgroup string The initially selected group. + * editgroupsettingsdisabled bool Whether to disable the "Edit group settings" button on load. + * deletegroupdisabled bool Whether to disable the "Delete selected group" button on load. + * addmembersdisabled bool Whether to disable the "Add/remove users" button on load. + * groups array The list of groups. + * members array The list of members, grouped based on roles. + * undeletablegroups string A JSON string containing an array of group IDs that a user cannot delete. + + Example context (json): + { + "courseid": "1", + "selectedgroup": "Group 1 (3)", + "editgroupsettingsdisabled": false, + "deletegroupdisabled": false, + "addmembersdisabled": false, + "groups": [ + { + "value": "1", + "text": "Group 1 (3)", + "selected": true + }, + { + "value": "2", + "text": "Group 2 (2)" + } + ], + "members": [ + { + "role": "Student", + "rolemembers": [ + { + "value": "1", + "text": "John Doe" + }, + { + "value": "2", + "text": "Jane Doe" + }, + { + "value": "3", + "text": "John Smith" + } + ] + } + ], + "undeletablegroups": "[1: true, 3: true]" + } +}} +
    +
    +
    +
    + +
    + + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    +{{#js}} + require(['jquery', 'core/yui'], function($) { + $("#groups").change(function() { + M.core_group.membersCombo.refreshMembers(); + }); + M.core_group.init_index(Y, "{{wwwroot}}", {{courseid}}); + var undeletableGroups = JSON.parse('{{{undeletablegroups}}}'); + M.core_group.groupslist(Y, undeletableGroups); + }); +{{/js}} diff --git a/group/tests/externallib_test.php b/group/tests/externallib_test.php index 97a6cf93184..dcbb0ef2742 100644 --- a/group/tests/externallib_test.php +++ b/group/tests/externallib_test.php @@ -523,6 +523,7 @@ class core_group_externallib_testcase extends externallib_advanced_testcase { $groups = core_group_external::get_activity_allowed_groups($cm1->id); $groups = external_api::clean_returnvalue(core_group_external::get_activity_allowed_groups_returns(), $groups); $this->assertCount(2, $groups['groups']); + $this->assertFalse($groups['canaccessallgroups']); foreach ($groups['groups'] as $group) { if ($group['name'] == $group1data['name']) { @@ -539,12 +540,21 @@ class core_group_externallib_testcase extends externallib_advanced_testcase { $groups = core_group_external::get_activity_allowed_groups($cm1->id, $student->id); $groups = external_api::clean_returnvalue(core_group_external::get_activity_allowed_groups_returns(), $groups); $this->assertCount(2, $groups['groups']); + // We are checking the $student passed as parameter so this will return false. + $this->assertFalse($groups['canaccessallgroups']); // Check warnings. Trying to get groups for a user not enrolled in course. $groups = core_group_external::get_activity_allowed_groups($cm1->id, $otherstudent->id); $groups = external_api::clean_returnvalue(core_group_external::get_activity_allowed_groups_returns(), $groups); $this->assertCount(1, $groups['warnings']); + $this->assertFalse($groups['canaccessallgroups']); + // Checking teacher groups. + $groups = core_group_external::get_activity_allowed_groups($cm1->id); + $groups = external_api::clean_returnvalue(core_group_external::get_activity_allowed_groups_returns(), $groups); + $this->assertCount(2, $groups['groups']); + // Teachers by default can access all groups. + $this->assertTrue($groups['canaccessallgroups']); } /** diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 3387da57456..845118ebcb2 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -1505,6 +1505,7 @@ $string['publicsitefileswarning3'] = 'Note: Files placed here can be accessed by $string['publish'] = 'Publish'; $string['question'] = 'Question'; $string['questionsinthequestionbank'] = 'Questions in the question bank'; +$string['quotausage'] = 'You have currently used {$a->used} of your {$a->total} limit.'; $string['readinginfofrombackup'] = 'Reading info from backup'; $string['readme'] = 'README'; $string['recentactivity'] = 'Recent activity'; diff --git a/lib/amd/build/ajax.min.js b/lib/amd/build/ajax.min.js index 936d338c377..96fcee8fbe6 100644 --- a/lib/amd/build/ajax.min.js +++ b/lib/amd/build/ajax.min.js @@ -1 +1 @@ -define(["jquery","core/config"],function(a,b){var c=function(a){var b,c,d=this,e=null,f=0;for(f=0;f 'fa-external-link', 'core:i/moodle_host' => 'fa-graduation-cap', 'core:i/move_2d' => 'fa-arrows', - 'core:i/navigationitem' => 'fa-angle-right', + 'core:i/navigationitem' => 'fa-fw', 'core:i/ne_red_mark' => 'fa-remove', 'core:i/new' => 'fa-plus', 'core:i/news' => 'fa-newspaper-o', @@ -280,7 +280,7 @@ class icon_system_fontawesome extends icon_system_font { 'core:i/scales' => 'fa-balance-scale', 'core:i/scheduled' => 'fa-calendar-check-o', 'core:i/search' => 'fa-search', - 'core:i/settings' => 'fa-cogs', + 'core:i/settings' => 'fa-cog', 'core:i/show' => 'fa-eye-slash', 'core:i/siteevent' => 'fa-share-alt', 'core:i/star-rating' => 'fa-star', @@ -309,7 +309,7 @@ class icon_system_fontawesome extends icon_system_font { 'core:t/block' => 'fa-ban', 'core:t/block_to_dock_rtl' => 'fa-chevron-right', 'core:t/block_to_dock' => 'fa-chevron-left', - 'core:t/calc_off' => 'fa-times fa-cross', + 'core:t/calc_off' => 'fa-calculator', // TODO: Change to better icon once we have stacked icon support or more icons. 'core:t/calc' => 'fa-calculator', 'core:t/check' => 'fa-check', 'core:t/cohort' => 'fa-users', diff --git a/lib/classes/plugininfo/repository.php b/lib/classes/plugininfo/repository.php index 861b24d2547..689edde3c91 100644 --- a/lib/classes/plugininfo/repository.php +++ b/lib/classes/plugininfo/repository.php @@ -37,7 +37,7 @@ class repository extends base { */ public static function get_enabled_plugins() { global $DB; - return $DB->get_records_menu('repository', array('visible'=>1), 'type ASC', 'type, type AS val'); + return $DB->get_records_menu('repository', null, 'type ASC', 'type, type AS val'); } public function get_settings_section_name() { diff --git a/lib/classes/user.php b/lib/classes/user.php index 49672295cdc..6925e0224f6 100644 --- a/lib/classes/user.php +++ b/lib/classes/user.php @@ -461,7 +461,7 @@ class core_user { 'choices' => array_merge(array('' => ''), get_string_manager()->get_list_of_countries(true, true))); $fields['lang'] = array('type' => PARAM_LANG, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->lang, 'choices' => array_merge(array('' => ''), get_string_manager()->get_list_of_translations(false))); - $fields['calendartype'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->calendartype, + $fields['calendartype'] = array('type' => PARAM_PLUGIN, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->calendartype, 'choices' => array_merge(array('' => ''), \core_calendar\type_factory::get_list_of_calendar_types())); $fields['theme'] = array('type' => PARAM_THEME, 'null' => NULL_NOT_ALLOWED, 'default' => theme_config::DEFAULT_THEME, 'choices' => array_merge(array('' => ''), get_list_of_themes())); diff --git a/lib/completionlib.php b/lib/completionlib.php index c32ad4c4bfe..c8bf8177e86 100644 --- a/lib/completionlib.php +++ b/lib/completionlib.php @@ -423,6 +423,21 @@ class completion_info { // Load criteria from database $records = (array)$DB->get_records('course_completion_criteria', $params); + // Order records so activities are in the same order as they appear on the course view page. + if ($records) { + $activitiesorder = array_keys(get_fast_modinfo($this->course)->get_cms()); + usort($records, function ($a, $b) use ($activitiesorder) { + $aidx = ($a->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) ? + array_search($a->moduleinstance, $activitiesorder) : false; + $bidx = ($b->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) ? + array_search($b->moduleinstance, $activitiesorder) : false; + if ($aidx === false || $bidx === false || $aidx == $bidx) { + return 0; + } + return ($aidx < $bidx) ? -1 : 1; + }); + } + // Build array of criteria objects $this->criteria = array(); foreach ($records as $record) { diff --git a/lib/db/services.php b/lib/db/services.php index bdc4019709c..d608f24b599 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -1206,6 +1206,15 @@ $functions = array( 'type' => 'write', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), + 'core_user_get_private_files_info' => array( + 'classname' => 'core_user_external', + 'methodname' => 'get_private_files_info', + 'classpath' => 'user/externallib.php', + 'description' => 'Returns general information about files in the user private files area.', + 'type' => 'read', + 'capabilities' => 'moodle/user:manageownfiles', + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), + ), // Competencies functions. 'core_competency_create_competency_framework' => array( diff --git a/lib/filelib.php b/lib/filelib.php index b1dafb9e3a2..28faa7eaae8 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -503,9 +503,29 @@ function file_rewrite_pluginfile_urls($text, $file, $contextid, $component, $fil * (more information will be added as needed). */ function file_get_draft_area_info($draftitemid, $filepath = '/') { - global $CFG, $USER; + global $USER; $usercontext = context_user::instance($USER->id); + return file_get_file_area_info($usercontext->id, 'user', 'draft', $draftitemid, $filepath); +} + +/** + * Returns information about files in an area. + * + * @param int $contextid context id + * @param string $component component + * @param string $filearea file area name + * @param int $itemid item id or all files if not specified + * @param string $filepath path to the directory from which the information have to be retrieved. + * @return array with the following entries: + * 'filecount' => number of files in the area. + * 'filesize' => total size of the files in the area. + * 'foldercount' => number of folders in the area. + * 'filesize_without_references' => total size of the area excluding file references. + * @since Moodle 3.4 + */ +function file_get_file_area_info($contextid, $component, $filearea, $itemid = 0, $filepath = '/') { + $fs = get_file_storage(); $results = array( @@ -515,11 +535,8 @@ function file_get_draft_area_info($draftitemid, $filepath = '/') { 'filesize_without_references' => 0 ); - if ($filepath != '/') { - $draftfiles = $fs->get_directory_files($usercontext->id, 'user', 'draft', $draftitemid, $filepath, true, true); - } else { - $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id', true); - } + $draftfiles = $fs->get_directory_files($contextid, $component, $filearea, $itemid, $filepath, true, true); + foreach ($draftfiles as $file) { if ($file->is_directory()) { $results['foldercount'] += 1; diff --git a/lib/filestorage/zip_archive.php b/lib/filestorage/zip_archive.php index b7d0fd68dde..21313143642 100644 --- a/lib/filestorage/zip_archive.php +++ b/lib/filestorage/zip_archive.php @@ -662,6 +662,7 @@ class zip_archive extends file_archive { case 'ISO-8859-6': $encoding = 'CP720'; break; case 'ISO-8859-7': $encoding = 'CP737'; break; case 'ISO-8859-8': $encoding = 'CP862'; break; + case 'WINDOWS-1251': $encoding = 'CP866'; break; case 'EUC-JP': case 'UTF-8': if ($winchar = get_string('localewincharset', 'langconfig')) { diff --git a/lib/phpunit/classes/util.php b/lib/phpunit/classes/util.php index a3997c400af..57c67c06d03 100644 --- a/lib/phpunit/classes/util.php +++ b/lib/phpunit/classes/util.php @@ -229,6 +229,11 @@ class phpunit_util extends testing_util { // Reset internal users. core_user::reset_internal_users(); + // Clear static caches in calendar container. + if (class_exists('\core_calendar\local\event\container', false)) { + core_calendar\local\event\container::reset_caches(); + } + //TODO MDL-25290: add more resets here and probably refactor them to new core function // Reset course and module caches. diff --git a/lib/tablelib.php b/lib/tablelib.php index 84d077b5011..4dd84f8de56 100644 --- a/lib/tablelib.php +++ b/lib/tablelib.php @@ -1545,7 +1545,7 @@ class table_sql extends flexible_table { * Of course you can use sub-queries, JOINS etc. by putting them in the * appropriate clause of the query. */ - function set_sql($fields, $from, $where, array $params = NULL) { + function set_sql($fields, $from, $where, array $params = array()) { $this->sql = new stdClass(); $this->sql->fields = $fields; $this->sql->from = $from; diff --git a/lib/tests/filelib_test.php b/lib/tests/filelib_test.php index ea18b514a2f..87a37bd0fe2 100644 --- a/lib/tests/filelib_test.php +++ b/lib/tests/filelib_test.php @@ -1229,6 +1229,117 @@ EOF; $file = array_shift($files); $this->assertTrue($file->is_directory()); } + + /** + * Test file_get_draft_area_info. + */ + public function test_file_get_draft_area_info() { + global $USER; + + $this->resetAfterTest(true); + $this->setAdminUser(); + $fs = get_file_storage(); + + $filerecord = array( + 'filename' => 'one.txt', + ); + $file = self::create_draft_file($filerecord); + $size = $file->get_filesize(); + $draftitemid = $file->get_itemid(); + // Add another file. + $filerecord = array( + 'itemid' => $draftitemid, + 'filename' => 'second.txt', + ); + $file = self::create_draft_file($filerecord); + $size += $file->get_filesize(); + + // Create directory. + $usercontext = context_user::instance($USER->id); + $dir = $fs->create_directory($usercontext->id, 'user', 'draft', $draftitemid, '/testsubdir/'); + // Add file to directory. + $filerecord = array( + 'itemid' => $draftitemid, + 'filename' => 'third.txt', + 'filepath' => '/testsubdir/', + ); + $file = self::create_draft_file($filerecord); + $size += $file->get_filesize(); + + $fileinfo = file_get_draft_area_info($draftitemid); + $this->assertEquals(3, $fileinfo['filecount']); + $this->assertEquals($size, $fileinfo['filesize']); + $this->assertEquals(1, $fileinfo['foldercount']); // Directory created. + $this->assertEquals($size, $fileinfo['filesize_without_references']); + + // Now get files from just one folder. + $fileinfo = file_get_draft_area_info($draftitemid, '/testsubdir/'); + $this->assertEquals(1, $fileinfo['filecount']); + $this->assertEquals($file->get_filesize(), $fileinfo['filesize']); + $this->assertEquals(0, $fileinfo['foldercount']); // No subdirectories inside the directory. + $this->assertEquals($file->get_filesize(), $fileinfo['filesize_without_references']); + + // Check we get the same results if we call file_get_file_area_info. + $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'draft', $draftitemid); + $this->assertEquals(3, $fileinfo['filecount']); + $this->assertEquals($size, $fileinfo['filesize']); + $this->assertEquals(1, $fileinfo['foldercount']); // Directory created. + $this->assertEquals($size, $fileinfo['filesize_without_references']); + } + + /** + * Test file_get_file_area_info. + */ + public function test_file_get_file_area_info() { + global $USER; + + $this->resetAfterTest(true); + $this->setAdminUser(); + $fs = get_file_storage(); + + $filerecord = array( + 'filename' => 'one.txt', + ); + $file = self::create_draft_file($filerecord); + $size = $file->get_filesize(); + $draftitemid = $file->get_itemid(); + // Add another file. + $filerecord = array( + 'itemid' => $draftitemid, + 'filename' => 'second.txt', + ); + $file = self::create_draft_file($filerecord); + $size += $file->get_filesize(); + + // Create directory. + $usercontext = context_user::instance($USER->id); + $dir = $fs->create_directory($usercontext->id, 'user', 'draft', $draftitemid, '/testsubdir/'); + // Add file to directory. + $filerecord = array( + 'itemid' => $draftitemid, + 'filename' => 'third.txt', + 'filepath' => '/testsubdir/', + ); + $file = self::create_draft_file($filerecord); + $size += $file->get_filesize(); + + // Add files to user private file area. + $options = array('subdirs' => 1, 'maxfiles' => 3); + file_merge_files_from_draft_area_into_filearea($draftitemid, $file->get_contextid(), 'user', 'private', 0, $options); + + $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'private'); + $this->assertEquals(3, $fileinfo['filecount']); + $this->assertEquals($size, $fileinfo['filesize']); + $this->assertEquals(1, $fileinfo['foldercount']); // Directory created. + $this->assertEquals($size, $fileinfo['filesize_without_references']); + + // Now get files from just one folder. + $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'private', 0, '/testsubdir/'); + $this->assertEquals(1, $fileinfo['filecount']); + $this->assertEquals($file->get_filesize(), $fileinfo['filesize']); + $this->assertEquals(0, $fileinfo['foldercount']); // No subdirectories inside the directory. + $this->assertEquals($file->get_filesize(), $fileinfo['filesize_without_references']); + } } /** diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 09c2839f65e..fce1d3ae0b7 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -10,6 +10,9 @@ information provided here is intended especially for developers. * Removed accesslib private functions: load_course_context(), load_role_access_by_context(), dedupe_user_access() (MDL-49398). * Internal "accessdata" structure format has changed to improve ability to perform role definition caching (MDL-49398). * Role definitions are no longer cached in user session (MDL-49398). +* External function core_group_external::get_activity_allowed_groups now returns an additional field: canaccessallgroups. + It indicates whether the user will be able to access all the activity groups. +* file_get_draft_area_info does not sum the root folder anymore when calculating the foldercount. === 3.3.1 === diff --git a/lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm-debug.js b/lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm-debug.js index d12bfd2320e..6f50f4c2c34 100644 --- a/lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm-debug.js +++ b/lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm-debug.js @@ -98,7 +98,7 @@ Y.extend(Confirmation, Y.Base, { _uninstall: function(e, langCodes) { Y.config.win.location.href = this.get('uninstallUrl') + '?mode=4' + '&sesskey=' + M.cfg.sesskey + - '&confirmtouninstall=' + langCodes.join('-'); + '&confirmtouninstall=' + langCodes.join('/'); } }); diff --git a/lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm-min.js b/lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm-min.js index fd52350bcd1..94a7844dd2a 100644 --- a/lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm-min.js +++ b/lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm-min.js @@ -1 +1 @@ -YUI.add("moodle-core-languninstallconfirm",function(e,t){function n(){n.superclass.constructor.apply(this,arguments)}var r={UNINSTALLBUTTON:"#languninstallbutton",UNINSTALLSELECT:"#menuuninstalllang option",ENGLISHOPTION:"#menuuninstalllang option[value='en']"};n.NAME=t,n.ATTRS={uninstallUrl:{validator:e.Lang.isString}},e.extend(n,e.Base,{initializer:function(){e.one(r.UNINSTALLBUTTON).on("click",this._confirm,this)},_confirm:function(t){t.preventDefault();var n=[],i=[];e.all(r.UNINSTALLSELECT).each(function(e){e.get("selected")&&(n.push(e.getAttribute("value")),i.push(e.get("text")))});if(n.length===0){(new M.core.alert({message:M.util.get_string("selectlangs","tool_langimport")})).show();return}if(n.indexOf("en")>-1){e.one(r.ENGLISHOPTION).set("selected",!1),(new M.core.alert({message:M.util.get_string("noenglishuninstall","tool_langimport")})).show();return}var s={modal:!0,visible:!1,centered:!0,title:M.util.get_string("uninstall","tool_langimport"),question:M.util.get_string("uninstallconfirm","tool_langimport",i.join(", "))};(new M.core.confirm(s)).show().on("complete-yes",this._uninstall,this,n)},_uninstall:function(t,n){e.config.win.location.href=this.get("uninstallUrl")+"?mode=4"+"&sesskey="+M.cfg.sesskey+"&confirmtouninstall="+n.join("-")}}),e.namespace("M.core.languninstallconfirm").Confirmation=n,e.namespace("M.core.languninstallconfirm").init=function(e){return new n(e)}},"@VERSION@",{requires:["base","node","moodle-core-notification-confirm","moodle-core-notification-alert"]}); +YUI.add("moodle-core-languninstallconfirm",function(e,t){function n(){n.superclass.constructor.apply(this,arguments)}var r={UNINSTALLBUTTON:"#languninstallbutton",UNINSTALLSELECT:"#menuuninstalllang option",ENGLISHOPTION:"#menuuninstalllang option[value='en']"};n.NAME=t,n.ATTRS={uninstallUrl:{validator:e.Lang.isString}},e.extend(n,e.Base,{initializer:function(){e.one(r.UNINSTALLBUTTON).on("click",this._confirm,this)},_confirm:function(t){t.preventDefault();var n=[],i=[];e.all(r.UNINSTALLSELECT).each(function(e){e.get("selected")&&(n.push(e.getAttribute("value")),i.push(e.get("text")))});if(n.length===0){(new M.core.alert({message:M.util.get_string("selectlangs","tool_langimport")})).show();return}if(n.indexOf("en")>-1){e.one(r.ENGLISHOPTION).set("selected",!1),(new M.core.alert({message:M.util.get_string("noenglishuninstall","tool_langimport")})).show();return}var s={modal:!0,visible:!1,centered:!0,title:M.util.get_string("uninstall","tool_langimport"),question:M.util.get_string("uninstallconfirm","tool_langimport",i.join(", "))};(new M.core.confirm(s)).show().on("complete-yes",this._uninstall,this,n)},_uninstall:function(t,n){e.config.win.location.href=this.get("uninstallUrl")+"?mode=4"+"&sesskey="+M.cfg.sesskey+"&confirmtouninstall="+n.join("/")}}),e.namespace("M.core.languninstallconfirm").Confirmation=n,e.namespace("M.core.languninstallconfirm").init=function(e){return new n(e)}},"@VERSION@",{requires:["base","node","moodle-core-notification-confirm","moodle-core-notification-alert"]}); diff --git a/lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm.js b/lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm.js index d12bfd2320e..6f50f4c2c34 100644 --- a/lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm.js +++ b/lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm.js @@ -98,7 +98,7 @@ Y.extend(Confirmation, Y.Base, { _uninstall: function(e, langCodes) { Y.config.win.location.href = this.get('uninstallUrl') + '?mode=4' + '&sesskey=' + M.cfg.sesskey + - '&confirmtouninstall=' + langCodes.join('-'); + '&confirmtouninstall=' + langCodes.join('/'); } }); diff --git a/lib/yui/src/languninstallconfirm/js/languninstallconfirm.js b/lib/yui/src/languninstallconfirm/js/languninstallconfirm.js index 88eead86e08..98d87ea2019 100644 --- a/lib/yui/src/languninstallconfirm/js/languninstallconfirm.js +++ b/lib/yui/src/languninstallconfirm/js/languninstallconfirm.js @@ -96,7 +96,7 @@ Y.extend(Confirmation, Y.Base, { _uninstall: function(e, langCodes) { Y.config.win.location.href = this.get('uninstallUrl') + '?mode=4' + '&sesskey=' + M.cfg.sesskey + - '&confirmtouninstall=' + langCodes.join('-'); + '&confirmtouninstall=' + langCodes.join('/'); } }); diff --git a/media/player/videojs/amd/build/loader.min.js b/media/player/videojs/amd/build/loader.min.js index 6c77c7f187d..d57bd6098af 100644 --- a/media/player/videojs/amd/build/loader.min.js +++ b/media/player/videojs/amd/build/loader.min.js @@ -1 +1 @@ -define(["jquery","core/event"],function(a,b){var c,d=function(d){c=d,e(null,a("body")),b.getLegacyEvents().done(function(b){a(document).on(b.FILTER_CONTENT_UPDATED,e)})},e=function(b,d){var e=".mediaplugin_videojs";d.find(e).addBack(e).find("audio, video").each(function(){var b=a(this).attr("id"),d=a(this).data("setup"),e=["media_videojs/video-lazy"];d.techOrder&&d.techOrder.indexOf("youtube")!==-1&&e.push("media_videojs/Youtube-lazy"),require(e,function(a){c&&(c(a),c=null),a(b,d)})})};return{setUp:d}}); \ No newline at end of file +define(["jquery","core/event"],function(a,b){var c,d=function(d){c=d,e(null,a("body")),b.getLegacyEvents().done(function(b){a(document).on(b.FILTER_CONTENT_UPDATED,e)})},e=function(b,d){var e=".mediaplugin_videojs";d.find(e).addBack(e).find("audio, video").each(function(){var b=a(this).attr("id"),d=a(this).data("setup-lazy"),e=["media_videojs/video-lazy"];d.techOrder&&d.techOrder.indexOf("youtube")!==-1&&e.push("media_videojs/Youtube-lazy"),require(e,function(a){c&&(c(a),c=null),a(b,d)})})};return{setUp:d}}); \ No newline at end of file diff --git a/media/player/videojs/amd/src/loader.js b/media/player/videojs/amd/src/loader.js index 2601cf888fa..427b609eea4 100644 --- a/media/player/videojs/amd/src/loader.js +++ b/media/player/videojs/amd/src/loader.js @@ -62,7 +62,7 @@ define(['jquery', 'core/event'], function($, Event) { .addBack(selector) .find('audio, video').each(function() { var id = $(this).attr('id'), - config = $(this).data('setup'), + config = $(this).data('setup-lazy'), modules = ['media_videojs/video-lazy']; if (config.techOrder && config.techOrder.indexOf('youtube') !== -1) { diff --git a/media/player/videojs/classes/plugin.php b/media/player/videojs/classes/plugin.php index f13352fe577..3d076b046f1 100644 --- a/media/player/videojs/classes/plugin.php +++ b/media/player/videojs/classes/plugin.php @@ -129,8 +129,13 @@ class media_videojs_plugin extends core_media_player_native { } // Attributes for the video/audio tag. + // We use data-setup-lazy as the attribute name for the config instead of + // data-setup because data-setup will cause video.js to load the player as soon as the library is loaded, + // which is BEFORE we have a chance to load any additional libraries (youtube). + // The data-setup-lazy is just a tag name that video.js does not recognise so we can manually initialise + // it when we are sure the dependencies are loaded. $attributes = [ - 'data-setup' => '{' . join(', ', $datasetup) . '}', + 'data-setup-lazy' => '{' . join(', ', $datasetup) . '}', 'id' => 'id_videojs_' . uniqid(), 'class' => get_config('media_videojs', $isaudio ? 'audiocssclass' : 'videocssclass') ]; diff --git a/media/player/videojs/tests/player_test.php b/media/player/videojs/tests/player_test.php index 43d698c2745..7bc50ead4b5 100644 --- a/media/player/videojs/tests/player_test.php +++ b/media/player/videojs/tests/player_test.php @@ -230,7 +230,7 @@ class media_videojs_testcase extends advanced_testcase { protected function youtube_plugin_engaged($t) { $this->assertContains('mediaplugin_videojs', $t); - $this->assertContains('data-setup="{"techOrder": ["youtube"]', $t); + $this->assertContains('data-setup-lazy="{"techOrder": ["youtube"]', $t); } /** diff --git a/message/amd/build/message_repository.min.js b/message/amd/build/message_repository.min.js index bded03758d5..5f20161d194 100644 --- a/message/amd/build/message_repository.min.js +++ b/message/amd/build/message_repository.min.js @@ -1 +1 @@ -define(["jquery","core/ajax","core/notification","core/log"],function(a,b,c,d){var e=function(a){"undefined"==typeof a.limit&&(a.limit=0),"undefined"==typeof a.offset&&(a.offset=0),a.limitfrom=a.offset,a.limitnum=a.limit,delete a.limit,delete a.offset;var d={methodname:"core_message_data_for_messagearea_conversations",args:a},e=b.call([d])[0];return e.fail(c.exception),e},f=function(a){var c={methodname:"core_message_get_unread_conversations_count",args:a},e=b.call([c])[0];return e.fail(function(a){d.error("Could not retrieve unread message count: "+a.message)}),e},g=function(a){var d={methodname:"core_message_mark_all_messages_as_read",args:a},e=b.call([d])[0];return e.fail(c.exception),e};return{query:e,countUnreadConversations:f,markAllAsRead:g}}); \ No newline at end of file +define(["jquery","core/ajax","core/notification"],function(a,b,c){var d=function(a){"undefined"==typeof a.limit&&(a.limit=0),"undefined"==typeof a.offset&&(a.offset=0),a.limitfrom=a.offset,a.limitnum=a.limit,delete a.limit,delete a.offset;var d={methodname:"core_message_data_for_messagearea_conversations",args:a},e=b.call([d])[0];return e.fail(c.exception),e},e=function(a){var d={methodname:"core_message_get_unread_conversations_count",args:a},e=b.call([d])[0];return e.fail(c.exception),e},f=function(a){var d={methodname:"core_message_mark_all_messages_as_read",args:a},e=b.call([d])[0];return e.fail(c.exception),e};return{query:d,countUnreadConversations:e,markAllAsRead:f}}); \ No newline at end of file diff --git a/message/amd/src/message_repository.js b/message/amd/src/message_repository.js index b81eae6043e..6c0ee94f039 100644 --- a/message/amd/src/message_repository.js +++ b/message/amd/src/message_repository.js @@ -22,7 +22,7 @@ * @copyright 2016 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery', 'core/ajax', 'core/notification', 'core/log'], function($, Ajax, Notification, Log) { +define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notification) { /** * Retrieve a list of messages from the server. * @@ -72,9 +72,7 @@ define(['jquery', 'core/ajax', 'core/notification', 'core/log'], function($, Aja var promise = Ajax.call([request])[0]; - promise.fail(function(e) { - Log.error('Could not retrieve unread message count: ' + e.message); - }); + promise.fail(Notification.exception); return promise; }; diff --git a/message/output/popup/amd/build/notification_repository.min.js b/message/output/popup/amd/build/notification_repository.min.js index beb592ec1cc..6d076df7bf4 100644 --- a/message/output/popup/amd/build/notification_repository.min.js +++ b/message/output/popup/amd/build/notification_repository.min.js @@ -1 +1 @@ -define(["core/ajax","core/notification","core/log"],function(a,b,c){var d=function(c){"undefined"==typeof c.limit&&(c.limit=20),"undefined"==typeof c.offset&&(c.offset=0);var d={methodname:"message_popup_get_popup_notifications",args:c},e=a.call([d])[0];return e.fail(b.exception),e},e=function(b){var d={methodname:"message_popup_get_unread_popup_notification_count",args:b},e=a.call([d])[0];return e.fail(function(a){c.error("Could not retrieve notifications count: "+a.message)}),e},f=function(c){var d={methodname:"core_message_mark_all_notifications_as_read",args:c},e=a.call([d])[0];return e.fail(b.exception),e},g=function(c,d){var e={messageid:c};d&&(e.timeread=d);var f={methodname:"core_message_mark_message_read",args:e},g=a.call([f])[0];return g.fail(b.exception),g};return{query:d,countUnread:e,markAllAsRead:f,markAsRead:g}}); \ No newline at end of file +define(["core/ajax","core/notification"],function(a,b){var c=function(c){"undefined"==typeof c.limit&&(c.limit=20),"undefined"==typeof c.offset&&(c.offset=0);var d={methodname:"message_popup_get_popup_notifications",args:c},e=a.call([d])[0];return e.fail(b.exception),e},d=function(c){var d={methodname:"message_popup_get_unread_popup_notification_count",args:c},e=a.call([d])[0];return e.fail(b.exception),e},e=function(c){var d={methodname:"core_message_mark_all_notifications_as_read",args:c},e=a.call([d])[0];return e.fail(b.exception),e},f=function(c,d){var e={messageid:c};d&&(e.timeread=d);var f={methodname:"core_message_mark_message_read",args:e},g=a.call([f])[0];return g.fail(b.exception),g};return{query:c,countUnread:d,markAllAsRead:e,markAsRead:f}}); \ No newline at end of file diff --git a/message/output/popup/amd/src/notification_repository.js b/message/output/popup/amd/src/notification_repository.js index 093238a9249..2fdb83ea6d0 100644 --- a/message/output/popup/amd/src/notification_repository.js +++ b/message/output/popup/amd/src/notification_repository.js @@ -22,7 +22,7 @@ * @copyright 2016 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['core/ajax', 'core/notification', 'core/log'], function(Ajax, Notification, Log) { +define(['core/ajax', 'core/notification'], function(Ajax, Notification) { /** * Retrieve a list of notifications from the server. * @@ -64,9 +64,7 @@ define(['core/ajax', 'core/notification', 'core/log'], function(Ajax, Notificati var promise = Ajax.call([request])[0]; - promise.fail(function(e) { - Log.error('Could not retrieve notifications count: ' + e.message); - }); + promise.fail(Notification.exception); return promise; }; diff --git a/mod/assign/submission/file/lang/en/assignsubmission_file.php b/mod/assign/submission/file/lang/en/assignsubmission_file.php index 381adb28b0f..309cd8a5ac1 100644 --- a/mod/assign/submission/file/lang/en/assignsubmission_file.php +++ b/mod/assign/submission/file/lang/en/assignsubmission_file.php @@ -29,6 +29,7 @@ $string['configmaxbytes'] = 'Maximum file size'; $string['countfiles'] = '{$a} files'; $string['default'] = 'Enabled by default'; $string['default_help'] = 'If set, this submission method will be enabled by default for all new assignments.'; +$string['defaultacceptedfiletypes'] = 'Default accepted file types'; $string['enabled'] = 'File submissions'; $string['enabled_help'] = 'If enabled, students are able to upload one or more files as their submission.'; $string['eventassessableuploaded'] = 'A file has been uploaded.'; diff --git a/mod/assign/submission/file/locallib.php b/mod/assign/submission/file/locallib.php index e2fe32f698b..d96b293009a 100644 --- a/mod/assign/submission/file/locallib.php +++ b/mod/assign/submission/file/locallib.php @@ -71,7 +71,12 @@ class assign_submission_file extends assign_submission_plugin { $defaultmaxfilesubmissions = $this->get_config('maxfilesubmissions'); $defaultmaxsubmissionsizebytes = $this->get_config('maxsubmissionsizebytes'); - $defaultfiletypes = (string)$this->get_config('filetypeslist'); + if ($this->assignment->has_instance()) { + $defaultfiletypes = $this->get_config('filetypeslist'); + } else { + $defaultfiletypes = get_config('assignsubmission_file', 'filetypes'); + } + $defaultfiletypes = (string)$defaultfiletypes; $settings = array(); $options = array(); @@ -108,7 +113,7 @@ class assign_submission_file extends assign_submission_plugin { 'notchecked'); $name = get_string('acceptedfiletypes', 'assignsubmission_file'); - $mform->addElement('text', 'assignsubmission_file_filetypes', $name); + $mform->addElement('text', 'assignsubmission_file_filetypes', $name, array('size' => '60')); $mform->addHelpButton('assignsubmission_file_filetypes', 'acceptedfiletypes', 'assignsubmission_file'); $mform->setType('assignsubmission_file_filetypes', PARAM_RAW); $mform->setDefault('assignsubmission_file_filetypes', $defaultfiletypes); diff --git a/mod/assign/submission/file/settings.php b/mod/assign/submission/file/settings.php index 4d7eb220e1f..a895e6240be 100644 --- a/mod/assign/submission/file/settings.php +++ b/mod/assign/submission/file/settings.php @@ -32,6 +32,10 @@ $settings->add(new admin_setting_configtext('assignsubmission_file/maxfiles', new lang_string('maxfiles', 'assignsubmission_file'), new lang_string('maxfiles_help', 'assignsubmission_file'), 20, PARAM_INT)); +$settings->add(new admin_setting_configtext('assignsubmission_file/filetypes', + new lang_string('defaultacceptedfiletypes', 'assignsubmission_file'), + new lang_string('acceptedfiletypes_help', 'assignsubmission_file'), '', PARAM_RAW, 60)); + if (isset($CFG->maxbytes)) { $name = new lang_string('maximumsubmissionsize', 'assignsubmission_file'); diff --git a/pix/i/mahara_host.gif b/pix/i/mahara_host.gif deleted file mode 100644 index 6b4225351ec..00000000000 Binary files a/pix/i/mahara_host.gif and /dev/null differ diff --git a/pix/i/mahara_host.png b/pix/i/mahara_host.png new file mode 100644 index 00000000000..c2e655d54c8 Binary files /dev/null and b/pix/i/mahara_host.png differ diff --git a/pix/i/mahara_host.svg b/pix/i/mahara_host.svg new file mode 100644 index 00000000000..c5cc65e8179 --- /dev/null +++ b/pix/i/mahara_host.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/theme/boost/scss/moodle/blocks.scss b/theme/boost/scss/moodle/blocks.scss index bfa78154978..c5df38a326e 100644 --- a/theme/boost/scss/moodle/blocks.scss +++ b/theme/boost/scss/moodle/blocks.scss @@ -206,3 +206,45 @@ $doughnut-fill-colour: $brand-warning; } } } + +// Show expand collapse with font-awesome. +.block_settings .block_tree [aria-expanded="true"], +.block_settings .block_tree [aria-expanded="true"].emptybranch, +.block_settings .block_tree [aria-expanded="false"], +.block_navigation .block_tree [aria-expanded="true"], +.block_navigation .block_tree [aria-expanded="true"].emptybranch, +.block_navigation .block_tree [aria-expanded="false"] { + background-image: none; +} +.block_settings .block_tree [aria-expanded="true"]:before, +.block_navigation .block_tree [aria-expanded="true"]:before { + content: $fa-var-angle-down; + margin-right: 0; + font-size: 16px; + @extend .fa; + width: 16px; +} + +.block_settings .block_tree [aria-expanded="false"]:before, +.block_navigation .block_tree [aria-expanded="false"]:before { + content: $fa-var-angle-right; + font-size: 16px; + margin-right: 0; + @extend .fa; + width: 16px; +} +.dir-rtl { + .block_settings .block_tree [aria-expanded="false"]:before, + .block_navigation .block_tree [aria-expanded="false"]:before { + content: $fa-var-angle-left; + } +} + +.block_navigation .block_tree p.hasicon, +.block_settings .block_tree p.hasicon { + text-indent: -3px; + + .icon { + margin-right: 2px; + } +} diff --git a/theme/boost/scss/moodle/core.scss b/theme/boost/scss/moodle/core.scss index 7f757a19c8b..cb149d12d14 100644 --- a/theme/boost/scss/moodle/core.scss +++ b/theme/boost/scss/moodle/core.scss @@ -346,9 +346,6 @@ a.skip:active { margin-left: 43px; } // Group -#page-group-index #groupeditform { - text-align: center; -} #doc-contents h1 { margin: 1em 0 0 0; diff --git a/theme/boost/scss/moodle/user.scss b/theme/boost/scss/moodle/user.scss index 701d8341cef..af83503ca71 100644 --- a/theme/boost/scss/moodle/user.scss +++ b/theme/boost/scss/moodle/user.scss @@ -277,21 +277,6 @@ } } -#groupeditform { - .groups, - .members { - min-width: 175px; - width: 49%; - float: left; - text-align: left; - - select { - min-width: 175px; - max-width: 90%; - } - } -} - // Remove the little cog from participants page because we are putting a cog menu there. .userlist h3 .action-icon { display: none; diff --git a/theme/boost/templates/core/availability_info.mustache b/theme/boost/templates/core/availability_info.mustache index 3cc64343b67..56cf9c37528 100644 --- a/theme/boost/templates/core/availability_info.mustache +++ b/theme/boost/templates/core/availability_info.mustache @@ -15,13 +15,47 @@ along with Moodle. If not, see . }} {{! - Availability info. + @template core/availability_info + + Renders the availability info on the course outline page. + + Availability info can be displayed for activity modules or whole course + sections. Activity modules can be either hidden from students, or available + but not shown on course page (stealth), or the access can be restricted by + configured conditions. Sections can be hidden. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * classes String list of CSS classes for the wrapping element + * text HTML formatted text with the actual availability information + * ishidden Boolean flag indiciating that the item is hidden from students + * isstealth Boolean flag indicating that the item is in stealth mode + * isrestricted Boolean flag indicating that restricted access conditions apply + * isfullinfo Boolean flag indicating that the full list of restricted + access conditions is displayed (aka teacher's view). Example context (json): - { "classes": "", "text": "This activity is not available" } + { + "classes": "", + "text": "Not available unless:
    • It is on or after 8 June 2012
    ", + "ishidden": 0, + "isstealth": 0, + "isrestricted": 1, + "isfullinfo": 1 + } }} {{#text}}
    + {{^isrestricted}} {{{text}}} + {{/isrestricted}} + {{#isrestricted}} + {{#str}}restricted, core{{/str}} {{{text}}} + {{/isrestricted}}
    {{/text}} diff --git a/theme/bootstrapbase/less/moodle/blocks.less b/theme/bootstrapbase/less/moodle/blocks.less index c42131dba54..68c70daa83b 100644 --- a/theme/bootstrapbase/less/moodle/blocks.less +++ b/theme/bootstrapbase/less/moodle/blocks.less @@ -53,6 +53,15 @@ display: inline; } } + p { + &.hasicon { + img { + &.icon { + padding-right: 0; + } + } + } + } } .footer { margin-bottom: 4px; diff --git a/theme/bootstrapbase/less/moodle/core.less b/theme/bootstrapbase/less/moodle/core.less index 03e0b8b0500..6a60df6bdd0 100644 --- a/theme/bootstrapbase/less/moodle/core.less +++ b/theme/bootstrapbase/less/moodle/core.less @@ -429,9 +429,6 @@ a.skip:active { margin-left: 43px; } // Group -#page-group-index #groupeditform { - text-align: center; -} #doc-contents h1 { margin: 1em 0 0 0; } diff --git a/theme/bootstrapbase/less/moodle/tool_usertours.less b/theme/bootstrapbase/less/moodle/tool_usertours.less index 3ea1e6f4f95..88fbccaa915 100644 --- a/theme/bootstrapbase/less/moodle/tool_usertours.less +++ b/theme/bootstrapbase/less/moodle/tool_usertours.less @@ -37,6 +37,7 @@ div[data-flexitour="step-background-fader"], span[data-flexitour="container"] { div[data-role="flexitour-step"] { background-color: #fff; + color: #333; border-radius: 6px; border: 1px solid rgba(0, 0, 0, .2); box-shadow: 0 5px 10px rgba(0, 0, 0, .2); diff --git a/theme/bootstrapbase/less/moodle/user.less b/theme/bootstrapbase/less/moodle/user.less index c4df6c0d397..24a48027d9e 100644 --- a/theme/bootstrapbase/less/moodle/user.less +++ b/theme/bootstrapbase/less/moodle/user.less @@ -264,21 +264,6 @@ } } -#groupeditform { - .groups, - .members { - min-width: 175px; - width: 49%; - float: left; - text-align: left; - - select { - min-width: 175px; - max-width: 90%; - } - } -} - /** Preferences page */ .preferences-group { ul { diff --git a/theme/bootstrapbase/style/moodle.css b/theme/bootstrapbase/style/moodle.css index fa74dbd2c8a..147abba0c0c 100644 --- a/theme/bootstrapbase/style/moodle.css +++ b/theme/bootstrapbase/style/moodle.css @@ -2723,9 +2723,6 @@ a.skip:active { .blog_entry .content { margin-left: 43px; } -#page-group-index #groupeditform { - text-align: center; -} #doc-contents h1 { margin: 1em 0 0 0; } @@ -9891,18 +9888,6 @@ body.path-question-type .mform fieldset.hidden { .profileeditor > .singlebutton input { margin: 0; } -#groupeditform .groups, -#groupeditform .members { - min-width: 175px; - width: 49%; - float: left; - text-align: left; -} -#groupeditform .groups select, -#groupeditform .members select { - min-width: 175px; - max-width: 90%; -} /** Preferences page */ .preferences-group ul { list-style: none; @@ -15873,6 +15858,9 @@ body { margin-left: 5px; display: inline; } +.block .content p.hasicon img.icon { + padding-right: 0; +} .block .footer { margin-bottom: 4px; display: block; @@ -19339,6 +19327,7 @@ div[data-flexitour="step-background-fader"], } span[data-flexitour="container"] div[data-role="flexitour-step"] { background-color: #fff; + color: #333; border-radius: 6px; border: 1px solid rgba(0, 0, 0, 0.2); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); diff --git a/theme/bootstrapbase/templates/block_myoverview/main.mustache b/theme/bootstrapbase/templates/block_myoverview/main.mustache index 650eb9bc060..42577fed2eb 100644 --- a/theme/bootstrapbase/templates/block_myoverview/main.mustache +++ b/theme/bootstrapbase/templates/block_myoverview/main.mustache @@ -24,27 +24,33 @@ }}
    -