Merge branch 'MDL-58777-master' of git://github.com/cameron1729/moodle
authorJake Dallimore <jake@moodle.com>
Thu, 11 May 2017 03:41:48 +0000 (11:41 +0800)
committerJake Dallimore <jake@moodle.com>
Thu, 11 May 2017 03:41:48 +0000 (11:41 +0800)
28 files changed:
admin/index.php
admin/tool/templatelibrary/classes/api.php
auth/classes/output/login.php
auth/oauth2/classes/api.php
auth/oauth2/db/events.php [new file with mode: 0644]
auth/oauth2/lang/en/auth_oauth2.php
auth/oauth2/linkedlogins.php
auth/oauth2/version.php
calendar/classes/external/event_action_exporter.php
calendar/classes/external/event_exporter.php
calendar/classes/external/event_icon_exporter.php
calendar/classes/local/event/container.php
lang/en/backup.php
lang/en/completion.php
lib/classes/output/icon_system_fontawesome.php
lib/setuplib.php
lib/templates/columns-1to1to1.mustache
lib/templates/columns-1to2.mustache
lib/templates/columns-2to1.mustache
login/signup.php
mod/assign/gradingtable.php
mod/lti/classes/service_exception_handler.php
mod/scorm/lang/en/scorm.php
repository/onedrive/lang/en/repository_onedrive.php
theme/boost/templates/core/columns-1to1to1.mustache [new file with mode: 0644]
theme/boost/templates/core/columns-1to2.mustache [new file with mode: 0644]
theme/boost/templates/core/columns-2to1.mustache [new file with mode: 0644]
webservice/xmlrpc/locallib.php

index 30ba5cb..6cf7f45 100644 (file)
@@ -101,6 +101,12 @@ if (function_exists('opcache_invalidate')) {
 // indirectly calls the protected init() method is good here.
 core_component::get_core_subsystems();
 
+if (is_major_upgrade_required() && isloggedin()) {
+    // A major upgrade is required.
+    // Terminate the session and redirect back here before anything DB-related happens.
+    redirect_if_major_upgrade_required();
+}
+
 require_once($CFG->libdir.'/adminlib.php');    // various admin-only functions
 require_once($CFG->libdir.'/upgradelib.php');  // general upgrade/install related functions
 
index b760007..7ae2b0e 100644 (file)
@@ -70,6 +70,9 @@ class api {
             // Look at all the templates dirs for subsystems.
             $subsystems = core_component::get_core_subsystems();
             foreach ($subsystems as $subsystem => $dir) {
+                if (empty($dir)) {
+                    continue;
+                }
                 $dir .= '/templates';
                 if (is_dir($dir)) {
                     $dirs = mustache_template_finder::get_template_directories_for_component('core_' . $subsystem, $themename);
index fcb42b7..8bdf91d 100644 (file)
@@ -128,7 +128,7 @@ class login implements renderable, templatable {
         $data->error = $this->error;
         $data->forgotpasswordurl = $this->forgotpasswordurl->out(false);
         $data->hasidentityproviders = !empty($this->identityproviders);
-        $data->hasinstructions = !empty($this->instructions);
+        $data->hasinstructions = !empty($this->instructions) || $this->cansignup;
         $data->identityproviders = $identityproviders;
         list($data->instructions, $data->instructionsformat) = external_format_text($this->instructions, FORMAT_MOODLE,
             context_system::instance()->id);
index 430abee..ffcb79c 100644 (file)
@@ -105,6 +105,10 @@ class api {
             $userid = $USER->id;
         }
 
+        if (linked_login::count_records(['username' => $userinfo['username']]) > 0) {
+            throw new moodle_exception('alreadylinked', 'auth_oauth2');
+        }
+
         if (\core\session\manager::is_loggedinas()) {
             throw new moodle_exception('notwhileloggedinas', 'auth_oauth2');
         }
@@ -144,9 +148,8 @@ class api {
         $record->issuerid = $issuer->get('id');
         $record->username = $userinfo['username'];
         $record->userid = $userid;
-        $existing = linked_login::get_record((array)$record);
-        if ($existing) {
-            return false;
+        if (linked_login::count_records(['username' => $userinfo['username']]) > 0) {
+            throw new moodle_exception('alreadylinked', 'auth_oauth2');
         }
         $record->email = $userinfo['email'];
         $record->confirmtoken = random_string(32);
@@ -239,6 +242,10 @@ class api {
         require_once($CFG->dirroot.'/user/profile/lib.php');
         require_once($CFG->dirroot.'/user/lib.php');
 
+        if (linked_login::count_records(['username' => $userinfo['username']]) > 0) {
+            throw new moodle_exception('alreadylinked', 'auth_oauth2');
+        }
+
         $user = new stdClass();
         $user->username = $userinfo['username'];
         $user->email = $userinfo['email'];
@@ -319,4 +326,18 @@ class api {
 
         $login->delete();
     }
+
+    /**
+     * Delete linked logins for a user.
+     *
+     * @param \core\event\user_deleted $event
+     * @return boolean
+     */
+    public static function user_deleted(\core\event\user_deleted $event) {
+        global $DB;
+
+        $userid = $event->objectid;
+
+        return $DB->delete_records(linked_login::TABLE, ['userid' => $userid]);
+    }
 }
diff --git a/auth/oauth2/db/events.php b/auth/oauth2/db/events.php
new file mode 100644 (file)
index 0000000..b6f793c
--- /dev/null
@@ -0,0 +1,31 @@
+<?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 definies observers needed by the plugin.
+ *
+ * @package    auth_oauth2
+ * @copyright  2017 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// List of observers.
+$observers = [
+    [
+        'eventname'   => '\core\event\user_deleted',
+        'callback'    => '\auth_oauth2\api::user_deleted',
+    ],
+];
index f0715e0..4dd4e0b 100644 (file)
@@ -77,9 +77,10 @@ $string['loginerror_userincomplete'] = 'The user information returned did not co
 $string['loginerror_nouserinfo'] = 'No user information was returned. The OAuth 2 service may be configured incorrectly.';
 $string['loginerror_invaliddomain'] = 'The email address is not allowed at this site.';
 $string['loginerror_authenticationfailed'] = 'The authentication process failed.';
-$string['loginerror_cannotcreateaccounts'] = 'The account does not exist and this site does not allow self-registration.';
+$string['loginerror_cannotcreateaccounts'] = 'An account with your email address could not be found.';
 $string['notloggedindebug'] = 'The login attempt failed. Reason: {$a}';
 $string['notwhileloggedinas'] = 'Linked logins cannot be managed while logged in as another user.';
 $string['oauth2:managelinkedlogins'] = 'Manage own linked login accounts';
 $string['plugindescription'] = 'This authentication plugin displays a list of the configured identity providers on the login page. Selecting an identity provider allows users to login with their credentials from an OAuth 2 provider.';
 $string['pluginname'] = 'OAuth 2';
+$string['alreadylinked'] = 'This external account is already linked to an account on this site';
index 1228530..fe18f8f 100644 (file)
@@ -58,8 +58,12 @@ if ($action == 'new') {
     $userinfo = $client->get_userinfo();
 
     if (!empty($userinfo)) {
-        \auth_oauth2\api::link_login($userinfo, $issuer);
-        redirect($PAGE->url, get_string('changessaved'), null, \core\output\notification::NOTIFY_SUCCESS);
+        try {
+            \auth_oauth2\api::link_login($userinfo, $issuer);
+            redirect($PAGE->url, get_string('changessaved'), null, \core\output\notification::NOTIFY_SUCCESS);
+        } catch (Exception $e) {
+            redirect($PAGE->url, $e->getMessage(), null, \core\output\notification::NOTIFY_ERROR);
+        }
     } else {
         redirect($PAGE->url, get_string('notloggedin', 'auth_oauth2'), null, \core\output\notification::NOTIFY_ERROR);
     }
index 8fc34eb..2758934 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017051500;        // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2017051501;        // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2017050500;        // Requires this Moodle version.
 $plugin->component = 'auth_oauth2';       // Full name of the plugin (used for diagnostics).
index 2002cc3..097adbc 100644 (file)
@@ -90,6 +90,10 @@ class event_action_exporter extends exporter {
     protected function get_other_values(renderer_base $output) {
         $event = $this->related['event'];
 
+        if (!$event->get_course_module()) {
+            // TODO MDL-58866 Only activity modules currently support this callback.
+            return ['showitemcount' => false];
+        }
         $modulename = $event->get_course_module()->get('modname');
         $component = 'mod_' . $modulename;
         $showitemcountcallback = 'core_calendar_event_action_shows_item_count';
index ade78fa..baa73f6 100644 (file)
@@ -178,10 +178,17 @@ class event_exporter extends exporter {
         $values = [];
         $event = $this->event;
         $context = $this->related['context'];
-        $modulename = $event->get_course_module()->get('modname');
-        $moduleid = $event->get_course_module()->get('id');
+        if ($moduleproxy = $event->get_course_module()) {
+            $modulename = $moduleproxy->get('modname');
+            $moduleid = $moduleproxy->get('id');
+            $url = new \moodle_url(sprintf('/mod/%s/view.php', $modulename), ['id' => $moduleid]);
+        } else {
+            // TODO MDL-58866 We do not have any way to find urls for events outside of course modules.
+            global $CFG;
+            require_once($CFG->dirroot.'/course/lib.php');
+            $url = \course_get_url($this->related['course'] ?: SITEID);
+        }
         $timesort = $event->get_times()->get_sort_time()->getTimestamp();
-        $url = new \moodle_url(sprintf('/mod/%s/view.php', $modulename), ['id' => $moduleid]);
         $iconexporter = new event_icon_exporter($event, ['context' => $context]);
 
         $values['url'] = $url->out(false);
index 2895a40..5f4bd4d 100644 (file)
@@ -54,7 +54,7 @@ class event_icon_exporter extends exporter {
         $userid = $user ? $user->get('id') : null;
         $isactivityevent = !empty($coursemodule);
         $isglobalevent = ($course && $courseid == SITEID);
-        $iscourseevent = ($course && !empty($courseid) && $courseid != SITEID && $group && empty($groupid));
+        $iscourseevent = ($course && !empty($courseid) && $courseid != SITEID && empty($groupid));
         $isgroupevent = ($group && !empty($groupid));
         $isuserevent = ($user && !empty($userid));
 
index 083b19c..20b39ec 100644 (file)
@@ -132,7 +132,7 @@ class container {
                 $getcallback('action'),
                 $getcallback('visibility'),
                 function ($dbrow) {
-                    // At present we only handle callbacks in course modules.
+                    // At present we only have a bail-out check for events in course modules.
                     if (empty($dbrow->modulename)) {
                         return false;
                     }
@@ -246,14 +246,19 @@ class container {
                     // Callbacks will get supplied a "legacy" version
                     // of the event class.
                     $mapper = self::$eventmapper;
-                    $action = component_callback(
-                        'mod_' . $event->get_course_module()->get('modname'),
-                        'core_calendar_provide_event_action',
-                        [
-                            $mapper->from_event_to_legacy_event($event),
-                            self::$actionfactory
-                        ]
-                    );
+                    $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.
@@ -266,13 +271,17 @@ class container {
                 // This is enforced by the event_factory.
                 'visibility' => function (event_interface $event) {
                     $mapper = self::$eventmapper;
-                    $eventvisible = component_callback(
-                        'mod_' . $event->get_course_module()->get('modname'),
-                        'core_calendar_is_event_visible',
-                        [
-                            $mapper->from_event_to_legacy_event($event)
-                        ]
-                    );
+                    $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)
+                            ]
+                        );
+                    }
 
                     // Do not display the event if there is nothing to action.
                     if ($event instanceof action_event_interface && $event->get_action()->get_item_count() === 0) {
index 0b1e736..5e2e3d4 100644 (file)
@@ -83,8 +83,8 @@ $string['choosefilefromactivitybackup'] = 'Activity backup area';
 $string['choosefilefromactivitybackup_help'] = 'Activity backups made using default settings are stored here.';
 $string['choosefilefromautomatedbackup'] = 'Automated backups';
 $string['choosefilefromautomatedbackup_help'] = 'Contains automatically generated backups.';
-$string['config_keep_groups_and_groupings'] = 'By default keep current roles and enrolments';
-$string['config_keep_roles_and_enrolments'] = 'By default keep current groups and groupings';
+$string['config_keep_groups_and_groupings'] = 'By default keep current groups and groupings.';
+$string['config_keep_roles_and_enrolments'] = 'By default keep current roles and enrolments.';
 $string['config_overwrite_conf'] = 'Allows user to overwrite the current course configuration';
 $string['config_overwrite_course_fullname'] = 'By default overwrite course full name with the one from the backup file. This requires "Overwrite course configuration" to be checked and current user to have the capability to change course full name (moodle/course:changefullname)';
 $string['config_overwrite_course_shortname'] = 'By default overwrite course short name with the one from the backup file. This requires "Overwrite course configuration" to be checked and current user to have the capability to change course short name (moodle/course:changeshortname)';
index dd16d5f..bb4f0d7 100644 (file)
@@ -134,7 +134,7 @@ $string['dependenciescompleted'] = 'Completion of other courses';
 $string['hiddenrules'] = 'Some settings specific to <b>{$a}</b> have been hidden. To view unselect other activities';
 $string['editcoursecompletionsettings'] = 'Edit course completion settings';
 $string['enablecompletion'] = 'Enable completion tracking';
-$string['enablecompletion_help'] = 'If enabled, activity completion conditions may be set in the activity settings and/or course completion conditions may be set. It is recommended to have this enabled in order for the course progress dashboard to display meaningful data.';
+$string['enablecompletion_help'] = 'If enabled, activity completion conditions may be set in the activity settings and/or course completion conditions may be set. It is recommended to have this enabled so that meaningful data is displayed in the course overview on the Dashboard.';
 $string['enrolmentduration'] = 'Enrolment duration';
 $string['enrolmentdurationlength'] = 'User must remain enrolled for';
 $string['err_noactivities'] = 'Completion information is not enabled for any activity, so none can be displayed. You can enable completion information by editing the settings for an activity.';
index 8601211..d5ef0ab 100644 (file)
@@ -221,7 +221,7 @@ class icon_system_fontawesome extends icon_system_font {
             'core:i/grade_correct' => 'fa-check text-success',
             'core:i/grade_incorrect' => 'fa-remove text-danger',
             'core:i/grade_partiallycorrect' => 'fa-check-square',
-            'core:i/grades' => 'fa-graduation-cap',
+            'core:i/grades' => 'fa-table',
             'core:i/groupevent' => 'fa-group',
             'core:i/groupn' => 'fa-user',
             'core:i/group' => 'fa-users',
@@ -334,7 +334,7 @@ class icon_system_fontawesome extends icon_system_font {
             'core:t/enrolusers' => 'fa-user-plus',
             'core:t/expanded' => 'fa-caret-down',
             'core:t/go' => 'fa-play',
-            'core:t/grades' => 'fa-graduation-cap',
+            'core:t/grades' => 'fa-table',
             'core:t/groupn' => 'fa-user',
             'core:t/groups' => 'fa-user-circle',
             'core:t/groupv' => 'fa-user-circle-o',
index 67ac2a4..9759ced 100644 (file)
@@ -1388,16 +1388,34 @@ function disable_output_buffering() {
 }
 
 /**
- * Check whether a major upgrade is needed. That is defined as an upgrade that
- * changes something really fundamental in the database, so nothing can possibly
- * work until the database has been updated, and that is defined by the hard-coded
- * version number in this function.
+ * Check whether a major upgrade is needed.
+ *
+ * That is defined as an upgrade that changes something really fundamental
+ * in the database, so nothing can possibly work until the database has
+ * been updated, and that is defined by the hard-coded version number in
+ * this function.
+ *
+ * @return bool
  */
-function redirect_if_major_upgrade_required() {
+function is_major_upgrade_required() {
     global $CFG;
     $lastmajordbchanges = 2017040403.00;
-    if (empty($CFG->version) or (float)$CFG->version < $lastmajordbchanges or
-            during_initial_install() or !empty($CFG->adminsetuppending)) {
+
+    $required = empty($CFG->version);
+    $required = $required || (float)$CFG->version < $lastmajordbchanges;
+    $required = $required || during_initial_install();
+    $required = $required || !empty($CFG->adminsetuppending);
+
+    return $required;
+}
+
+/**
+ * Redirect to the Notifications page if a major upgrade is required, and
+ * terminate the current user session.
+ */
+function redirect_if_major_upgrade_required() {
+    global $CFG;
+    if (is_major_upgrade_required()) {
         try {
             @\core\session\manager::terminate_current();
         } catch (Exception $e) {
index a26b39b..73718de 100644 (file)
@@ -35,9 +35,9 @@
 
     Example context (json):
     {
-        "col1content": "<div class='alert alert-error'>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam pellentesque id urna sit amet tempor.</div>",
-        "col2content": "<div class='alert alert-success'>Donec lacus nisl, molestie eget sodales non, sodales et nibh. Praesent dignissim placerat sodales.</div>",
-        "col3content": "<div class='alert alert-info'>Praesent sit amet ante odio. In mollis nisl at mi bibendum venenatis.</div>"
+        "col1content": "<div class='alert alert-error'>1. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam pellentesque id urna sit amet tempor.</div>",
+        "col2content": "<div class='alert alert-success'>2. Donec lacus nisl, molestie eget sodales non, sodales et nibh. Praesent dignissim placerat sodales.</div>",
+        "col3content": "<div class='alert alert-info'>3. Praesent sit amet ante odio. In mollis nisl at mi bibendum venenatis.</div>"
     }
 
 }}
index d94a83d..2413342 100644 (file)
@@ -34,8 +34,8 @@
 
     Example context (json):
     {
-        "col1content": "<div class='alert alert-info'>Lorem ipsum dolor sit amet, consectetur adipiscing elit. In porttitor vulputate turpis, quis tempor arcu.</div>",
-        "col2content": "<div class='alert alert-success'>Vivamus ac orci in velit fringilla aliquam a a nisl. Cras luctus quam laoreet magna pulvinar aliquet.</div>"
+        "col1content": "<div class='alert alert-info'>1. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In porttitor vulputate turpis, quis tempor arcu.</div>",
+        "col2content": "<div class='alert alert-success'>2. Vivamus ac orci in velit fringilla aliquam a a nisl. Cras luctus quam laoreet magna pulvinar aliquet.</div>"
     }
 
 }}
index 1978fae..d66b341 100644 (file)
@@ -34,8 +34,8 @@
 
     Example context (json):
     {
-        "col1content": "<div class='alert alert-info'>Lorem ipsum dolor sit amet, consectetur adipiscing elit. In porttitor vulputate turpis, quis tempor arcu.</div>",
-        "col2content": "<div class='alert alert-success'>Vivamus ac orci in velit fringilla aliquam a a nisl. Cras luctus quam laoreet magna pulvinar aliquet.</div>"
+        "col1content": "<div class='alert alert-info'>1. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In porttitor vulputate turpis, quis tempor arcu.</div>",
+        "col2content": "<div class='alert alert-success'>2. Vivamus ac orci in velit fringilla aliquam a a nisl. Cras luctus quam laoreet magna pulvinar aliquet.</div>"
     }
 }}
 <div class="row-fluid rtl-compatible">
index e145180..dc6774d 100644 (file)
@@ -97,5 +97,17 @@ $PAGE->set_heading($SITE->fullname);
 
 echo $OUTPUT->header();
 
-echo $OUTPUT->render($mform_signup);
+if ($mform_signup instanceof renderable) {
+    // Try and use the renderer from the auth plugin if it exists.
+    try {
+        $renderer = $PAGE->get_renderer('auth_' . $authplugin->authtype);
+    } catch (coding_exception $ce) {
+        // Fall back on the general renderer.
+        $renderer = $OUTPUT;
+    }
+    echo $renderer->render($mform_signup);
+} else {
+    // Fall back for auth plugins not using renderables.
+    $mform_signup->display();
+}
 echo $OUTPUT->footer();
index 91ceb02..33caa81 100644 (file)
@@ -209,14 +209,13 @@ class assign_grading_table extends table_sql implements renderable {
                       JOIN {groups_members} gm ON gm.groupid = g.id
                      WHERE go.assignid = :assignmentid6
                   )
-                ) AS merged
+                ) merged
                 GROUP BY merged.userid
               ) priority ON priority.userid = u.id
 
             JOIN (
               (SELECT 9999999 AS priority,
                       u.id AS userid,
-
                       a.allowsubmissionsfromdate,
                       a.duedate,
                       a.cutoffdate
@@ -226,7 +225,6 @@ class assign_grading_table extends table_sql implements renderable {
               UNION
               (SELECT 0 AS priority,
                       uo.userid,
-
                       uo.allowsubmissionsfromdate,
                       uo.duedate,
                       uo.cutoffdate
@@ -236,7 +234,6 @@ class assign_grading_table extends table_sql implements renderable {
               UNION
               (SELECT go.sortorder AS priority,
                       gm.userid,
-
                       go.allowsubmissionsfromdate,
                       go.duedate,
                       go.cutoffdate
@@ -246,7 +243,7 @@ class assign_grading_table extends table_sql implements renderable {
                 WHERE go.assignid = :assignmentid9
               )
 
-            ) AS effective ON effective.priority = priority.priority AND effective.userid = priority.userid ';
+            ) effective ON effective.priority = priority.priority AND effective.userid = priority.userid ';
         }
 
         if (!empty($this->assignment->get_instance()->blindmarking)) {
index 597a4d3..2c0e781 100644 (file)
@@ -94,9 +94,9 @@ class service_exception_handler {
     /**
      * Echo an exception message encapsulated in XML.
      *
-     * @param \Exception $exception The exception that was thrown
+     * @param \Exception|\Throwable $exception The exception that was thrown
      */
-    public function handle(\Exception $exception) {
+    public function handle($exception) {
         $message = $exception->getMessage();
 
         // Add the exception backtrace for developers.
index e78e94d..f1684b1 100644 (file)
@@ -95,7 +95,7 @@ $string['confirmloosetracks'] = 'WARNING: The package seems to be changed or mod
 $string['contents'] = 'Contents';
 $string['coursepacket'] = 'Course package';
 $string['coursestruct'] = 'Course structure';
-$string['crontask'] = 'Background processing for Scorm';
+$string['crontask'] = 'Background processing for SCORM';
 $string['currentwindow'] = 'Current window';
 $string['datadir'] = 'Filesystem error: Can\'t create course data directory';
 $string['defaultdisplaysettings'] = 'Default display settings';
index d9dfe62..83ab88a 100644 (file)
@@ -34,7 +34,7 @@ $string['importskydrivefiles'] = 'Import files from Microsoft SkyDrive repositor
 $string['internal'] = 'Internal (files stored in Moodle)';
 $string['issuer_help'] = 'Select the OAuth 2 service that is configured to talk to the OneDrive API. If the service does not exist yet, you will need to create it.';
 $string['issuer'] = 'OAuth 2 service';
-$string['mysitenotfound'] = 'You have never logged into OneDrive before. You must login to OneDrive at least once it before it can be used with Moodle.';
+$string['mysitenotfound'] = 'You have never logged into OneDrive before. You must log in to OneDrive at least once before it can be used with Moodle.';
 $string['oauth2serviceslink'] = '<a href="{$a}" title="Link to OAuth 2 services configuration">OAuth 2 services configuration</a>';
 $string['owner'] = 'Owned by: {$a}';
 $string['pluginname'] = 'Microsoft OneDrive';
diff --git a/theme/boost/templates/core/columns-1to1to1.mustache b/theme/boost/templates/core/columns-1to1to1.mustache
new file mode 100644 (file)
index 0000000..adb1038
--- /dev/null
@@ -0,0 +1,48 @@
+{{!
+    This file is part of Moodle - http://moodle.org/
+
+    Moodle is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Moodle is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template core/columns-1to1to1
+
+    Moodle columns-1to1to1 template.
+
+    The purpose of this template is to render a template with 3 columns.
+    On mobile the columns stack underneath each other.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * col1content Column 1 contents.
+    * col2content Column 2 contents.
+    * col3content Column 3 contents.
+
+    Example context (json):
+    {
+        "col1content": "<div class='alert alert-error'>1. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam pellentesque id urna sit amet tempor.</div>",
+        "col2content": "<div class='alert alert-success'>2. Donec lacus nisl, molestie eget sodales non, sodales et nibh. Praesent dignissim placerat sodales.</div>",
+        "col3content": "<div class='alert alert-info'>3. Praesent sit amet ante odio. In mollis nisl at mi bibendum venenatis.</div>"
+    }
+
+}}
+<div class="row">
+    <div class="col-md-4">{{$ column1 }}{{{ col1content }}}{{/ column1 }}</div>
+    <div class="col-md-4">{{$ column2 }}{{{ col2content }}}{{/ column2 }}</div>
+    <div class="col-md-4">{{$ column3 }}{{{ col3content }}}{{/ column3 }}</div>
+</div>
diff --git a/theme/boost/templates/core/columns-1to2.mustache b/theme/boost/templates/core/columns-1to2.mustache
new file mode 100644 (file)
index 0000000..114efcb
--- /dev/null
@@ -0,0 +1,45 @@
+{{!
+    This file is part of Moodle - http://moodle.org/
+
+    Moodle is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Moodle is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template core/columns-1to2
+
+    Moodle columns-1to2 template.
+
+    The purpose of this template is to render 2 columns where the second column has twice the width of the first one.
+    On mobile the second column collapses underneath the first.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * col1content Column 1 contents.
+    * col2content Column 2 contents.
+
+    Example context (json):
+    {
+        "col1content": "<div class='alert alert-info'>1. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In porttitor vulputate turpis, quis tempor arcu.</div>",
+        "col2content": "<div class='alert alert-success'>2. Vivamus ac orci in velit fringilla aliquam a a nisl. Cras luctus quam laoreet magna pulvinar aliquet.</div>"
+    }
+
+}}
+<div class="row">
+    <div class="col-md-4">{{$ column1 }}{{{ col1content }}}{{/ column1 }}</div>
+    <div class="col-md-8">{{$ column2 }}{{{ col2content }}}{{/ column2 }}</div>
+</div>
diff --git a/theme/boost/templates/core/columns-2to1.mustache b/theme/boost/templates/core/columns-2to1.mustache
new file mode 100644 (file)
index 0000000..68f6e76
--- /dev/null
@@ -0,0 +1,44 @@
+{{!
+    This file is part of Moodle - http://moodle.org/
+
+    Moodle is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Moodle is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template core/columns-2to1
+
+    Moodle columns-2to1 template.
+
+    The purpose of this template is to render 2 columns where the first column has twice the width of the second one.
+    On mobile the second column collapses underneath the first.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * col1content Column 1 contents.
+    * col2content Column 2 contents.
+
+    Example context (json):
+    {
+        "col1content": "<div class='alert alert-info'>1. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In porttitor vulputate turpis, quis tempor arcu.</div>",
+        "col2content": "<div class='alert alert-success'>2. Vivamus ac orci in velit fringilla aliquam a a nisl. Cras luctus quam laoreet magna pulvinar aliquet.</div>"
+    }
+}}
+<div class="row">
+    <div class="col-md-8">{{$ column1 }}{{{ col1content }}}{{/ column1 }}</div>
+    <div class="col-md-4">{{$ column2 }}{{{ col2content }}}{{/ column2 }}</div>
+</div>
index f6334be..fb888d9 100644 (file)
@@ -161,11 +161,11 @@ class webservice_xmlrpc_server extends webservice_base_server {
     /**
      * Generate the XML-RPC fault response.
      *
-     * @param Exception $ex The exception.
+     * @param Exception|Throwable $ex The exception.
      * @param int $faultcode The faultCode to be included in the fault response
      * @return string The XML-RPC fault response xml containing the faultCode and faultString.
      */
-    protected function generate_error(Exception $ex, $faultcode = 404) {
+    protected function generate_error($ex, $faultcode = 404) {
         $error = $ex->getMessage();
 
         if (!empty($ex->errorcode)) {