first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
<?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/>.
/**
* Auto-login end-point, a user can be fully authenticated in the site providing a valid key.
*
* @package tool_mobile
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../config.php');
$userid = required_param('userid', PARAM_INT); // The user id the key belongs to (for double-checking).
$key = required_param('key', PARAM_ALPHANUMEXT); // The key generated by the tool_mobile_external::get_autologin_key() external function.
$urltogo = optional_param('urltogo', $CFG->wwwroot, PARAM_LOCALURL); // URL to redirect.
$urltogo = $urltogo ?: $CFG->wwwroot;
$context = context_system::instance();
$PAGE->set_context($context);
// Check if the user is already logged-in.
if (isloggedin() and !isguestuser()) {
delete_user_key('tool_mobile', $userid);
if ($USER->id == $userid) {
redirect($urltogo);
} else {
throw new moodle_exception('alreadyloggedin', 'error', '', format_string(fullname($USER)));
}
}
tool_mobile\api::check_autologin_prerequisites($userid);
// Validate and delete the key.
$key = validate_user_key($key, 'tool_mobile', null);
delete_user_key('tool_mobile', $userid);
// Double check key belong to user.
if ($key->userid != $userid) {
throw new moodle_exception('invalidkey');
}
// Key validated, now require an active user: not guest, not suspended.
$user = core_user::get_user($key->userid, '*', MUST_EXIST);
core_user::require_active_user($user, true, true);
// Do the user log-in.
if (!$user = get_complete_user_data('id', $user->id)) {
throw new moodle_exception('cannotfinduser', '', '', $user->id);
}
complete_user_login($user);
\core\session\manager::apply_concurrent_login_limit($user->id, session_id());
redirect($urltogo);
+867
View File
@@ -0,0 +1,867 @@
<?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/>.
/**
* Class for Moodle Mobile tools.
*
* @package tool_mobile
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.1
*/
namespace tool_mobile;
use core_component;
use core_plugin_manager;
use context_system;
use moodle_url;
use moodle_exception;
use lang_string;
use curl;
use core_qrcode;
use stdClass;
/**
* API exposed by tool_mobile, to be used mostly by external functions and the plugin settings.
*
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.1
*/
class api {
/** @var int to identify the login via app. */
const LOGIN_VIA_APP = 1;
/** @var int to identify the login via browser. */
const LOGIN_VIA_BROWSER = 2;
/** @var int to identify the login via an embedded browser. */
const LOGIN_VIA_EMBEDDED_BROWSER = 3;
/** @var int seconds an auto-login key will expire. */
const LOGIN_KEY_TTL = 60;
/** @var string URL of the Moodle Apps Portal */
const MOODLE_APPS_PORTAL_URL = 'https://apps.moodle.com';
/** @var int default value in seconds a QR login key will expire. */
const LOGIN_QR_KEY_TTL = 600;
/** @var int QR code disabled value */
const QR_CODE_DISABLED = 0;
/** @var int QR code type URL value */
const QR_CODE_URL = 1;
/** @var int QR code type login value */
const QR_CODE_LOGIN = 2;
/** @var string Default Android app id */
const DEFAULT_ANDROID_APP_ID = 'com.moodle.moodlemobile';
/** @var string Default iOS app id */
const DEFAULT_IOS_APP_ID = '633359593';
/** @var int AUTOLOGOUT disabled value */
const AUTOLOGOUT_DISABLED = 0;
/** @var int AUTOLOGOUT type inmediate value */
const AUTOLOGOUT_INMEDIATE = 1;
/** @var int AUTOLOGOUT type custom value */
const AUTOLOGOUT_CUSTOM = 2;
/**
* Returns a list of Moodle plugins supporting the mobile app.
*
* @return array an array of objects containing the plugin information
*/
public static function get_plugins_supporting_mobile() {
global $CFG;
require_once($CFG->libdir . '/adminlib.php');
$cachekey = 'mobileplugins';
if (!isloggedin()) {
$cachekey = 'authmobileplugins'; // Use a different cache for not logged users.
}
// Check if we can return this from cache.
$cache = \cache::make('tool_mobile', 'plugininfo');
$pluginsinfo = $cache->get($cachekey);
if ($pluginsinfo !== false) {
return (array)$pluginsinfo;
}
$pluginsinfo = [];
// For not logged users return only auth plugins.
// This is to avoid anyone (not being a registered user) to obtain and download all the site remote add-ons.
if (!isloggedin()) {
$plugintypes = array('auth' => $CFG->dirroot.'/auth');
} else {
$plugintypes = core_component::get_plugin_types();
}
foreach ($plugintypes as $plugintype => $unused) {
// We need to include files here.
$pluginswithfile = core_component::get_plugin_list_with_file($plugintype, 'db' . DIRECTORY_SEPARATOR . 'mobile.php');
foreach ($pluginswithfile as $plugin => $notused) {
$path = core_component::get_plugin_directory($plugintype, $plugin);
$component = $plugintype . '_' . $plugin;
$version = get_component_version($component);
require("$path/db/mobile.php");
foreach ($addons as $addonname => $addoninfo) {
// Add handlers (for site add-ons).
$handlers = !empty($addoninfo['handlers']) ? $addoninfo['handlers'] : array();
$handlers = json_encode($handlers); // JSON formatted, since it is a complex structure that may vary over time.
// Now language strings used by the app.
$lang = array();
if (!empty($addoninfo['lang'])) {
$stringmanager = get_string_manager();
$langs = $stringmanager->get_list_of_translations(true);
foreach ($langs as $langid => $langname) {
foreach ($addoninfo['lang'] as $stringinfo) {
$lang[$langid][$stringinfo[0]] = $stringmanager->get_string(
$stringinfo[0],
$stringinfo[1] ?? '',
null,
$langid,
);
}
}
}
$lang = json_encode($lang);
$plugininfo = array(
'component' => $component,
'version' => $version,
'addon' => $addonname,
'dependencies' => !empty($addoninfo['dependencies']) ? $addoninfo['dependencies'] : array(),
'fileurl' => '',
'filehash' => '',
'filesize' => 0,
'handlers' => $handlers,
'lang' => $lang,
);
// All the mobile packages must be under the plugin mobile directory.
$package = $path . '/mobile/' . $addonname . '.zip';
if (file_exists($package)) {
$plugininfo['fileurl'] = $CFG->wwwroot . '' . str_replace($CFG->dirroot, '', $package);
$plugininfo['filehash'] = sha1_file($package);
$plugininfo['filesize'] = filesize($package);
}
$pluginsinfo[] = $plugininfo;
}
}
}
$cache->set($cachekey, $pluginsinfo);
return $pluginsinfo;
}
/**
* Returns a list of the site public settings, those not requiring authentication.
*
* @return array with the settings and warnings
*/
public static function get_public_config() {
global $CFG, $SITE, $PAGE, $OUTPUT;
require_once($CFG->libdir . '/authlib.php');
$context = context_system::instance();
// We need this to make work the format text functions.
$PAGE->set_context($context);
// Check if contacting site support is available to all visitors.
$sitesupportavailable = (isset($CFG->supportavailability) && $CFG->supportavailability == CONTACT_SUPPORT_ANYONE);
[$authinstructions] = \core_external\util::format_text($CFG->auth_instructions, FORMAT_MOODLE, $context->id);
[$maintenancemessage] = \core_external\util::format_text($CFG->maintenance_message, FORMAT_MOODLE, $context->id);
$settings = array(
'wwwroot' => $CFG->wwwroot,
'httpswwwroot' => $CFG->wwwroot,
'sitename' => \core_external\util::format_string($SITE->fullname, $context->id, true),
'guestlogin' => $CFG->guestloginbutton,
'rememberusername' => $CFG->rememberusername,
'authloginviaemail' => $CFG->authloginviaemail,
'registerauth' => $CFG->registerauth,
'forgottenpasswordurl' => clean_param($CFG->forgottenpasswordurl, PARAM_URL), // We may expect a mailto: here.
'authinstructions' => $authinstructions,
'authnoneenabled' => (int) is_enabled_auth('none'),
'enablewebservices' => $CFG->enablewebservices,
'enablemobilewebservice' => $CFG->enablemobilewebservice,
'maintenanceenabled' => $CFG->maintenance_enabled,
'maintenancemessage' => $maintenancemessage,
'mobilecssurl' => !empty($CFG->mobilecssurl) ? $CFG->mobilecssurl : '',
'tool_mobile_disabledfeatures' => get_config('tool_mobile', 'disabledfeatures'),
'country' => clean_param($CFG->country, PARAM_NOTAGS),
'agedigitalconsentverification' => \core_auth\digital_consent::is_age_digital_consent_verification_enabled(),
'autolang' => $CFG->autolang,
'lang' => clean_param($CFG->lang, PARAM_LANG), // Avoid breaking WS because of incorrect package langs.
'langmenu' => $CFG->langmenu,
'langlist' => $CFG->langlist,
'locale' => $CFG->locale,
'tool_mobile_minimumversion' => get_config('tool_mobile', 'minimumversion'),
'tool_mobile_iosappid' => get_config('tool_mobile', 'iosappid'),
'tool_mobile_androidappid' => get_config('tool_mobile', 'androidappid'),
'tool_mobile_setuplink' => clean_param(get_config('tool_mobile', 'setuplink'), PARAM_URL),
'tool_mobile_qrcodetype' => clean_param(get_config('tool_mobile', 'qrcodetype'), PARAM_INT),
'supportpage' => $sitesupportavailable ? clean_param($CFG->supportpage, PARAM_URL) : '',
'supportavailability' => clean_param($CFG->supportavailability, PARAM_INT),
);
$typeoflogin = get_config('tool_mobile', 'typeoflogin');
// Not found, edge case.
if ($typeoflogin === false) {
$typeoflogin = self::LOGIN_VIA_APP; // Defaults to via app.
}
$settings['typeoflogin'] = $typeoflogin;
// Check if the user can sign-up to return the launch URL in that case.
$cansignup = signup_is_enabled();
$url = new moodle_url("/$CFG->admin/tool/mobile/launch.php");
$settings['launchurl'] = $url->out(false);
// Check that we are receiving a moodle_url object, themes can override get_logo_url and may return incorrect values.
if (($logourl = $OUTPUT->get_logo_url()) && $logourl instanceof moodle_url) {
$settings['logourl'] = clean_param($logourl->out(false), PARAM_URL);
}
if (($compactlogourl = $OUTPUT->get_compact_logo_url()) && $compactlogourl instanceof moodle_url) {
$settings['compactlogourl'] = clean_param($compactlogourl->out(false), PARAM_URL);
}
// Identity providers.
$authsequence = get_enabled_auth_plugins();
$identityproviders = \auth_plugin_base::get_identity_providers($authsequence);
$identityprovidersdata = \auth_plugin_base::prepare_identity_providers_for_output($identityproviders, $OUTPUT);
if (!empty($identityprovidersdata)) {
$settings['identityproviders'] = $identityprovidersdata;
// Clean URLs to avoid breaking Web Services.
// We can't do it in prepare_identity_providers_for_output() because it may break the web output.
foreach ($settings['identityproviders'] as &$ip) {
$ip['url'] = (!empty($ip['url'])) ? clean_param($ip['url'], PARAM_URL) : '';
$ip['iconurl'] = (!empty($ip['iconurl'])) ? clean_param($ip['iconurl'], PARAM_URL) : '';
}
}
// If age is verified or support is available to all visitors, also return the admin contact details.
if ($settings['agedigitalconsentverification'] || $sitesupportavailable) {
$settings['supportname'] = clean_param($CFG->supportname, PARAM_NOTAGS);
$settings['supportemail'] = clean_param($CFG->supportemail, PARAM_EMAIL);
}
return $settings;
}
/**
* Returns a list of site configurations, filtering by section.
*
* @param string $section section name
* @return stdClass object containing the settings
*/
public static function get_config($section) {
global $CFG, $SITE;
$settings = new \stdClass;
$context = context_system::instance();
$isadmin = has_capability('moodle/site:config', $context);
if (empty($section) or $section == 'frontpagesettings') {
require_once($CFG->dirroot . '/course/format/lib.php');
// First settings that anyone can deduce.
$settings->fullname = \core_external\util::format_string($SITE->fullname, $context->id);
$settings->shortname = \core_external\util::format_string($SITE->shortname, $context->id);
// Return to a var instead of directly to $settings object because of differences between
// list() in php5 and php7. {@link http://php.net/manual/en/function.list.php}
$formattedsummary = \core_external\util::format_text($SITE->summary, $SITE->summaryformat,
$context->id);
$settings->summary = $formattedsummary[0];
$settings->summaryformat = $formattedsummary[1];
$settings->frontpage = $CFG->frontpage;
$settings->frontpageloggedin = $CFG->frontpageloggedin;
$settings->maxcategorydepth = $CFG->maxcategorydepth;
$settings->frontpagecourselimit = $CFG->frontpagecourselimit;
$settings->numsections = course_get_format($SITE)->get_last_section_number();
$settings->newsitems = $SITE->newsitems;
$settings->commentsperpage = $CFG->commentsperpage;
// Now, admin settings.
if ($isadmin) {
$settings->defaultfrontpageroleid = $CFG->defaultfrontpageroleid;
}
}
if (empty($section) or $section == 'sitepolicies') {
$manager = new \core_privacy\local\sitepolicy\manager();
$settings->sitepolicy = ($sitepolicy = $manager->get_embed_url()) ? $sitepolicy->out(false) : '';
$settings->sitepolicyhandler = $CFG->sitepolicyhandler;
$settings->disableuserimages = $CFG->disableuserimages;
}
if (empty($section) or $section == 'gradessettings') {
require_once($CFG->dirroot . '/user/lib.php');
$settings->mygradesurl = user_mygrades_url();
// The previous function may return moodle_url instances or plain string URLs.
if ($settings->mygradesurl instanceof moodle_url) {
$settings->mygradesurl = $settings->mygradesurl->out(false);
}
}
if (empty($section) or $section == 'mobileapp') {
$settings->tool_mobile_forcelogout = get_config('tool_mobile', 'forcelogout');
$settings->tool_mobile_customlangstrings = get_config('tool_mobile', 'customlangstrings');
$settings->tool_mobile_disabledfeatures = get_config('tool_mobile', 'disabledfeatures');
$settings->tool_mobile_filetypeexclusionlist = get_config('tool_mobile', 'filetypeexclusionlist');
$settings->tool_mobile_custommenuitems = get_config('tool_mobile', 'custommenuitems');
$settings->tool_mobile_apppolicy = get_config('tool_mobile', 'apppolicy');
// This setting could be not set in some edge cases such as bad upgrade.
$mintimereq = get_config('tool_mobile', 'autologinmintimebetweenreq');
$mintimereq = empty($mintimereq) ? 6 * MINSECS : $mintimereq;
$settings->tool_mobile_autologinmintimebetweenreq = $mintimereq;
$settings->tool_mobile_autologout = get_config('tool_mobile', 'autologout');
$settings->tool_mobile_autologouttime = get_config('tool_mobile', 'autologouttime');
}
if (empty($section) or $section == 'calendar') {
$settings->calendartype = $CFG->calendartype;
$settings->calendar_site_timeformat = $CFG->calendar_site_timeformat;
$settings->calendar_startwday = $CFG->calendar_startwday;
$settings->calendar_adminseesall = $CFG->calendar_adminseesall;
$settings->calendar_lookahead = $CFG->calendar_lookahead;
$settings->calendar_maxevents = $CFG->calendar_maxevents;
}
if (empty($section) or $section == 'coursecolors') {
$colornumbers = range(1, 10);
foreach ($colornumbers as $number) {
$settings->{'core_admin_coursecolor' . $number} = get_config('core_admin', 'coursecolor' . $number);
}
}
if (empty($section) or $section == 'supportcontact') {
$settings->supportavailability = $CFG->supportavailability;
if ($CFG->supportavailability == CONTACT_SUPPORT_DISABLED) {
$settings->supportname = null;
$settings->supportemail = null;
$settings->supportpage = null;
} else {
$settings->supportname = $CFG->supportname;
$settings->supportemail = $CFG->supportemail ?? null;
$settings->supportpage = $CFG->supportpage;
}
}
if (empty($section) || $section === 'graceperiodsettings') {
$settings->coursegraceperiodafter = $CFG->coursegraceperiodafter;
$settings->coursegraceperiodbefore = $CFG->coursegraceperiodbefore;
}
if (empty($section) || $section === 'navigation') {
$settings->enabledashboard = $CFG->enabledashboard;
}
if (empty($section) || ($section === 'themesettings' || $section === 'themesettingsadvanced')) {
$settings->customusermenuitems = $CFG->customusermenuitems;
}
if (empty($section) || $section === 'locationsettings') {
$settings->timezone = $CFG->timezone;
$settings->forcetimezone = $CFG->forcetimezone;
}
if (empty($section) || $section === 'manageglobalsearch') {
$settings->searchengine = $CFG->searchengine;
$settings->searchenablecategories = $CFG->searchenablecategories;
$settings->searchdefaultcategory = $CFG->searchdefaultcategory;
$settings->searchhideallcategory = $CFG->searchhideallcategory;
$settings->searchmaxtopresults = $CFG->searchmaxtopresults;
$settings->searchbannerenable = $CFG->searchbannerenable;
$settings->searchbanner = \core_external\util::format_text(
$CFG->searchbanner, FORMAT_HTML, $context)[0];
}
if (empty($section) || $section === 'privacysettings') {
$settings->tool_dataprivacy_contactdataprotectionofficer = get_config('tool_dataprivacy', 'contactdataprotectionofficer');
$settings->tool_dataprivacy_showdataretentionsummary = get_config('tool_dataprivacy', 'showdataretentionsummary');
}
if (empty($section) || $section === 'blog') {
$settings->useblogassociations = $CFG->useblogassociations;
$settings->bloglevel = $CFG->bloglevel;
$settings->blogusecomments = $CFG->blogusecomments;
}
if (empty($section) || $section === 'h5psettings') {
\core_h5p\local\library\autoloader::register();
$customcss = \core_h5p\file_storage::get_custom_styles();
if (!empty($customcss)) {
$settings->h5pcustomcssurl = $customcss['cssurl']->out() . '?ver=' . $customcss['cssversion'];
}
}
return $settings;
}
/*
* Check if all the required conditions are met to allow the auto-login process continue.
*
* @param int $userid current user id
* @since Moodle 3.2
* @throws moodle_exception
*/
public static function check_autologin_prerequisites($userid) {
global $CFG;
if (!$CFG->enablewebservices or !$CFG->enablemobilewebservice) {
throw new moodle_exception('enablewsdescription', 'webservice');
}
if (!is_https()) {
throw new moodle_exception('httpsrequired', 'tool_mobile');
}
if (has_capability('moodle/site:config', context_system::instance(), $userid) or is_siteadmin($userid)) {
throw new moodle_exception('autologinnotallowedtoadmins', 'tool_mobile');
}
}
/**
* Creates an auto-login key for the current user, this key is restricted by time and ip address.
* This key is used for automatically login the user in the site when the Moodle app opens the site in a mobile browser.
*
* @return string the key
* @since Moodle 3.2
*/
public static function get_autologin_key() {
global $USER;
// Delete previous keys.
delete_user_key('tool_mobile', $USER->id);
// Create a new key.
$iprestriction = getremoteaddr();
$validuntil = time() + self::LOGIN_KEY_TTL;
return create_user_key('tool_mobile', $USER->id, null, $iprestriction, $validuntil);
}
/**
* Creates a QR login key for the current user, this key is restricted by time and ip address.
* This key is used for automatically login the user in the site when the user scans a QR code in the Moodle app.
*
* @param stdClass $mobilesettings mobile app plugin settings
* @return string the key
* @since Moodle 3.9
*/
public static function get_qrlogin_key(stdClass $mobilesettings) {
global $USER;
// Delete previous keys.
delete_user_key('tool_mobile/qrlogin', $USER->id);
// Create a new key.
$iprestriction = !empty($mobilesettings->qrsameipcheck) ? getremoteaddr(null) : null;
$qrkeyttl = !empty($mobilesettings->qrkeyttl) ? $mobilesettings->qrkeyttl : self::LOGIN_QR_KEY_TTL;
$validuntil = time() + $qrkeyttl;
return create_user_key('tool_mobile/qrlogin', $USER->id, null, $iprestriction, $validuntil);
}
/**
* Get a list of the Mobile app features.
*
* @return array array with the features grouped by theirs ubication in the app.
* @since Moodle 3.3
*/
public static function get_features_list() {
global $CFG;
require_once($CFG->libdir . '/authlib.php');
$general = new lang_string('general');
$mainmenu = new lang_string('mainmenu', 'tool_mobile');
$course = new lang_string('course');
$modules = new lang_string('managemodules');
$blocks = new lang_string('blocks');
$useraccount = new lang_string('useraccount');
$participants = new lang_string('participants');
$files = new lang_string('files');
$remoteaddons = new lang_string('remoteaddons', 'tool_mobile');
$identityproviders = new lang_string('oauth2identityproviders', 'tool_mobile');
$availablemods = core_plugin_manager::instance()->get_plugins_of_type('mod');
$coursemodules = array();
$appsupportedmodules = array(
'assign', 'bigbluebuttonbn', 'book', 'chat', 'choice', 'data', 'feedback', 'folder', 'forum', 'glossary', 'h5pactivity',
'imscp', 'label', 'lesson', 'lti', 'page', 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop');
foreach ($availablemods as $mod) {
if (in_array($mod->name, $appsupportedmodules)) {
$coursemodules['$mmCourseDelegate_mmaMod' . ucfirst($mod->name)] = $mod->displayname;
}
}
asort($coursemodules);
$remoteaddonslist = array();
$mobileplugins = self::get_plugins_supporting_mobile();
foreach ($mobileplugins as $plugin) {
$displayname = core_plugin_manager::instance()->plugin_name($plugin['component']) . " - " . $plugin['addon'];
$remoteaddonslist['sitePlugin_' . $plugin['component'] . '_' . $plugin['addon']] = $displayname;
}
// Display blocks.
$availableblocks = core_plugin_manager::instance()->get_plugins_of_type('block');
$courseblocks = array();
$appsupportedblocks = array(
'activity_modules' => 'CoreBlockDelegate_AddonBlockActivityModules',
'activity_results' => 'CoreBlockDelegate_AddonBlockActivityResults',
'site_main_menu' => 'CoreBlockDelegate_AddonBlockSiteMainMenu',
'myoverview' => 'CoreBlockDelegate_AddonBlockMyOverview',
'course_list' => 'CoreBlockDelegate_AddonBlockCourseList',
'timeline' => 'CoreBlockDelegate_AddonBlockTimeline',
'recentlyaccessedcourses' => 'CoreBlockDelegate_AddonBlockRecentlyAccessedCourses',
'starredcourses' => 'CoreBlockDelegate_AddonBlockStarredCourses',
'recentlyaccesseditems' => 'CoreBlockDelegate_AddonBlockRecentlyAccessedItems',
'badges' => 'CoreBlockDelegate_AddonBlockBadges',
'blog_menu' => 'CoreBlockDelegate_AddonBlockBlogMenu',
'blog_recent' => 'CoreBlockDelegate_AddonBlockBlogRecent',
'blog_tags' => 'CoreBlockDelegate_AddonBlockBlogTags',
'calendar_month' => 'CoreBlockDelegate_AddonBlockCalendarMonth',
'calendar_upcoming' => 'CoreBlockDelegate_AddonBlockCalendarUpcoming',
'comments' => 'CoreBlockDelegate_AddonBlockComments',
'completionstatus' => 'CoreBlockDelegate_AddonBlockCompletionStatus',
'feedback' => 'CoreBlockDelegate_AddonBlockFeedback',
'globalsearch' => 'CoreBlockDelegate_AddonBlockGlobalSearch',
'glossary_random' => 'CoreBlockDelegate_AddonBlockGlossaryRandom',
'html' => 'CoreBlockDelegate_AddonBlockHtml',
'lp' => 'CoreBlockDelegate_AddonBlockLp',
'news_items' => 'CoreBlockDelegate_AddonBlockNewsItems',
'online_users' => 'CoreBlockDelegate_AddonBlockOnlineUsers',
'private_files' => 'CoreBlockDelegate_AddonBlockPrivateFiles',
'recent_activity' => 'CoreBlockDelegate_AddonBlockRecentActivity',
'rss_client' => 'CoreBlockDelegate_AddonBlockRssClient',
'search_forums' => 'CoreBlockDelegate_AddonBlockSearchForums',
'selfcompletion' => 'CoreBlockDelegate_AddonBlockSelfCompletion',
'tags' => 'CoreBlockDelegate_AddonBlockTags',
);
foreach ($availableblocks as $block) {
if (isset($appsupportedblocks[$block->name])) {
$courseblocks[$appsupportedblocks[$block->name]] = $block->displayname;
}
}
asort($courseblocks);
$features = array(
"$general" => array(
'NoDelegate_CoreOffline' => new lang_string('offlineuse', 'tool_mobile'),
'NoDelegate_SiteBlocks' => new lang_string('blocks'),
'NoDelegate_CoreComments' => new lang_string('comments'),
'NoDelegate_CoreRating' => new lang_string('ratings', 'rating'),
'NoDelegate_CoreTag' => new lang_string('tags'),
'$mmLoginEmailSignup' => new lang_string('startsignup'),
'NoDelegate_ForgottenPassword' => new lang_string('forgotten'),
'NoDelegate_ResponsiveMainMenuItems' => new lang_string('responsivemainmenuitems', 'tool_mobile'),
'NoDelegate_H5POffline' => new lang_string('h5poffline', 'tool_mobile'),
'NoDelegate_DarkMode' => new lang_string('darkmode', 'tool_mobile'),
'CoreFilterDelegate' => new lang_string('type_filter_plural', 'plugin'),
'CoreReportBuilderDelegate' => new lang_string('reportbuilder', 'core_reportbuilder'),
'NoDelegate_CoreUserSupport' => new lang_string('contactsitesupport', 'admin'),
'NoDelegate_GlobalSearch' => new lang_string('globalsearch', 'search'),
),
"$mainmenu" => array(
'$mmSideMenuDelegate_mmaFrontpage' => new lang_string('sitehome'),
'CoreMainMenuDelegate_CoreCoursesDashboard' => new lang_string('myhome'),
'$mmSideMenuDelegate_mmCourses' => new lang_string('mycourses'),
'$mmSideMenuDelegate_mmaMessages' => new lang_string('messages', 'message'),
'$mmSideMenuDelegate_mmaNotifications' => new lang_string('notifications', 'message'),
'$mmSideMenuDelegate_mmaCalendar' => new lang_string('calendar', 'calendar'),
'CoreMainMenuDelegate_AddonBlog' => new lang_string('blog', 'blog'),
'CoreMainMenuDelegate_CoreTag' => new lang_string('tags'),
'CoreMainMenuDelegate_QrReader' => new lang_string('scanqrcode', 'tool_mobile'),
),
"$useraccount" => array(
'$mmSideMenuDelegate_mmaGrades' => new lang_string('grades', 'grades'),
'$mmSideMenuDelegate_mmaFiles' => new lang_string('files'),
'CoreUserDelegate_AddonBadges:account' => new lang_string('badges', 'badges'),
'CoreUserDelegate_AddonBlog:account' => new lang_string('blog', 'blog'),
'$mmSideMenuDelegate_mmaCompetency' => new lang_string('myplans', 'tool_lp'),
'CoreUserDelegate_CorePolicy' => new lang_string('policiesagreements', 'tool_policy'),
'CoreUserDelegate_CoreDataPrivacy' => new lang_string('pluginname', 'tool_dataprivacy'),
'NoDelegate_SwitchAccount' => new lang_string('switchaccount', 'tool_mobile'),
),
"$course" => array(
'$mmCoursesDelegate_mmaParticipants' => new lang_string('participants'),
'$mmCoursesDelegate_mmaGrades' => new lang_string('grades', 'grades'),
'$mmCoursesDelegate_mmaCompetency' => new lang_string('competencies', 'competency'),
'$mmCoursesDelegate_mmaNotes' => new lang_string('notes', 'notes'),
'$mmCoursesDelegate_mmaCourseCompletion' => new lang_string('coursecompletion', 'completion'),
'NoDelegate_CourseBlocks' => new lang_string('blocks'),
'CoreCourseOptionsDelegate_AddonBlog' => new lang_string('blog', 'blog'),
'$mmCoursesDelegate_search' => new lang_string('search'),
'NoDelegate_CoreCourseDownload' => new lang_string('downloadcourse', 'tool_mobile'),
'NoDelegate_CoreCoursesDownload' => new lang_string('downloadcourses', 'tool_mobile'),
),
"$participants" => array(
'$mmUserDelegate_mmaGrades:viewGrades' => new lang_string('grades', 'grades'),
'$mmUserDelegate_mmaCourseCompletion:viewCompletion' => new lang_string('coursecompletion', 'completion'),
'$mmUserDelegate_mmaBadges' => new lang_string('badges', 'badges'),
'$mmUserDelegate_mmaNotes:addNote' => new lang_string('notes', 'notes'),
'CoreUserDelegate_AddonBlog:blogs' => new lang_string('blog', 'blog'),
'$mmUserDelegate_mmaCompetency:learningPlan' => new lang_string('competencies', 'competency'),
'$mmUserDelegate_mmaMessages:sendMessage' => new lang_string('sendmessage', 'message'),
'$mmUserDelegate_picture' => new lang_string('userpic'),
),
"$files" => array(
'files_privatefiles' => new lang_string('privatefiles'),
'files_sitefiles' => new lang_string('sitefiles'),
'files_upload' => new lang_string('upload'),
),
"$modules" => $coursemodules,
"$blocks" => $courseblocks,
);
if (!empty($remoteaddonslist)) {
$features["$remoteaddons"] = $remoteaddonslist;
}
if (!empty($availablemods['lti'])) {
$ltidisplayname = $availablemods['lti']->displayname;
$features["$ltidisplayname"]['CoreCourseModuleDelegate_AddonModLti:launchViaSite'] =
new lang_string('launchviasiteinbrowser', 'tool_mobile');
}
// Display OAuth 2 identity providers.
if (is_enabled_auth('oauth2')) {
$identityproviderslist = array();
$idps = \auth_plugin_base::get_identity_providers(['oauth2']);
foreach ($idps as $idp) {
// Only add identity providers that have an ID.
$id = isset($idp['url']) ? $idp['url']->get_param('id') : null;
if ($id != null) {
$identityproviderslist['NoDelegate_IdentityProvider_' . $id] = $idp['name'];
}
}
if (!empty($identityproviderslist)) {
$features["$identityproviders"] = array();
if (count($identityproviderslist) > 1) {
// Include an option to disable them all.
$features["$identityproviders"]['NoDelegate_IdentityProviders'] = new lang_string('all');
}
$features["$identityproviders"] = array_merge($features["$identityproviders"], $identityproviderslist);
}
}
return $features;
}
/**
* This function check the current site for potential configuration issues that may prevent the mobile app to work.
*
* @return array list of potential issues
* @since Moodle 3.4
*/
public static function get_potential_config_issues() {
global $CFG;
require_once($CFG->dirroot . "/lib/filelib.php");
require_once($CFG->dirroot . '/message/lib.php');
$warnings = array();
if (is_https()) {
$curl = new curl();
// Return certificate information and verify the certificate.
$curl->setopt(array('CURLOPT_CERTINFO' => 1, 'CURLOPT_SSL_VERIFYPEER' => true));
// Check https using a page not redirecting or returning exceptions.
$curl->head("$CFG->wwwroot/$CFG->admin/tool/mobile/mobile.webmanifest.php");
$info = $curl->get_info();
// Check the certificate is not self-signed or has an untrusted-root.
// This may be weak in some scenarios (when the curl SSL verifier is outdated).
if (empty($info['http_code']) || empty($info['certinfo'])) {
$warnings[] = ['selfsignedoruntrustedcertificatewarning', 'tool_mobile'];
} else {
$timenow = time();
$infokeys = array_keys($info['certinfo']);
$lastkey = end($infokeys);
if (count($info['certinfo']) == 1) {
// This will work in a normal browser because it will complete the chain, but not in a mobile app.
$warnings[] = ['invalidcertificatechainwarning', 'tool_mobile'];
}
foreach ($info['certinfo'] as $key => $cert) {
// Convert to lower case the keys, some OS/curl implementations differ.
$cert = array_change_key_case($cert, CASE_LOWER);
// Due to a bug in certain curl/openssl versions the signature algorithm isn't always correctly parsed.
// See https://github.com/curl/curl/issues/3706 for reference.
if (!array_key_exists('signature algorithm', $cert)) {
// The malformed field that does contain the algorithm we're looking for looks like the following:
// <WHITESPACE>Signature Algorithm: <ALGORITHM><CRLF><ALGORITHM>.
preg_match('/\s+Signature Algorithm: (?<algorithm>[^\s]+)/', $cert['public key algorithm'], $matches);
$signaturealgorithm = $matches['algorithm'] ?? '';
} else {
$signaturealgorithm = $cert['signature algorithm'];
}
// Check if the signature algorithm is weak (Android won't work with SHA-1).
if ($key != $lastkey &&
($signaturealgorithm == 'sha1WithRSAEncryption' || $signaturealgorithm == 'sha1WithRSA')) {
$warnings['insecurealgorithmwarning'] = ['insecurealgorithmwarning', 'tool_mobile'];
}
// Check certificate start date.
if (strtotime($cert['start date']) > $timenow) {
$warnings['invalidcertificatestartdatewarning'] = ['invalidcertificatestartdatewarning', 'tool_mobile'];
}
// Check certificate end date.
if (strtotime($cert['expire date']) < $timenow) {
$warnings['invalidcertificateexpiredatewarning'] = ['invalidcertificateexpiredatewarning', 'tool_mobile'];
}
}
}
} else {
// Warning for non https sites.
$warnings[] = ['nohttpsformobilewarning', 'admin'];
}
// Check ADOdb debug enabled.
if (get_config('auth_db', 'debugauthdb') || get_config('enrol_database', 'debugdb')) {
$warnings[] = ['adodbdebugwarning', 'tool_mobile'];
}
// Check display errors on.
if (!empty($CFG->debugdisplay)) {
$warnings[] = ['displayerrorswarning', 'tool_mobile'];
}
// Check mobile notifications.
$processors = get_message_processors();
$enabled = false;
foreach ($processors as $processor => $status) {
if ($processor == 'airnotifier' && $status->enabled) {
$enabled = true;
}
}
if (!$enabled) {
$warnings[] = ['mobilenotificationsdisabledwarning', 'tool_mobile'];
}
return $warnings;
}
/**
* Generates a QR code with the site URL or for automatic login from the mobile app.
*
* @param stdClass $mobilesettings tool_mobile settings
* @return string base64 data image contents, null if qr disabled
*/
public static function generate_login_qrcode(stdClass $mobilesettings) {
global $CFG, $USER;
if ($mobilesettings->qrcodetype == static::QR_CODE_DISABLED) {
return null;
}
$urlscheme = !empty($mobilesettings->forcedurlscheme) ? $mobilesettings->forcedurlscheme : 'moodlemobile';
$data = $urlscheme . '://' . $CFG->wwwroot;
if ($mobilesettings->qrcodetype == static::QR_CODE_LOGIN) {
$qrloginkey = static::get_qrlogin_key($mobilesettings);
$data .= '?qrlogin=' . $qrloginkey . '&userid=' . $USER->id;
}
$qrcode = new core_qrcode($data);
$imagedata = 'data:image/png;base64,' . base64_encode($qrcode->getBarcodePngData(5, 5));
return $imagedata;
}
/**
* Gets Moodle app plan subscription information for the current site as it is returned by the Apps Portal.
*
* @return array Subscription information
*/
public static function get_subscription_information(): ?array {
global $CFG;
// Use session cache to prevent multiple requests.
$cache = \cache::make('tool_mobile', 'subscriptiondata');
$subscriptiondata = $cache->get(0);
if ($subscriptiondata !== false) {
return $subscriptiondata;
}
$mobilesettings = get_config('tool_mobile');
// To validate that the requests come from this site we need to send some private information that only is known by the
// Moodle Apps portal or the Sites registration database.
$credentials = [];
if (!empty($CFG->airnotifieraccesskey)) {
$credentials[] = ['type' => 'airnotifieraccesskey', 'value' => $CFG->airnotifieraccesskey];
}
if (\core\hub\registration::is_registered()) {
$credentials[] = ['type' => 'siteid', 'value' => $CFG->siteidentifier];
}
// Generate a hash key for validating that the request is coming from this site via WS.
$key = complex_random_string(32);
$sitesubscriptionkey = json_encode(['validuntil' => time() + 10 * MINSECS, 'key' => $key]);
set_config('sitesubscriptionkey', $sitesubscriptionkey, 'tool_mobile');
$credentials[] = ['type' => 'sitesubscriptionkey', 'value' => $key];
// Parameters for the WebService returning site information.
$androidappid = empty($mobilesettings->androidappid) ? static::DEFAULT_ANDROID_APP_ID : $mobilesettings->androidappid;
$iosappid = empty($mobilesettings->iosappid) ? static::DEFAULT_IOS_APP_ID : $mobilesettings->iosappid;
$fnparams = (object) [
'siteurl' => $CFG->wwwroot,
'appids' => [$androidappid, $iosappid],
'credentials' => $credentials,
];
// Prepare the arguments for a request to the AJAX nologin endpoint.
$args = [
(object) [
'index' => 0,
'methodname' => 'local_apps_get_site_info',
'args' => $fnparams,
]
];
// Ask the Moodle Apps Portal for the subscription information.
$curl = new curl();
$curl->setopt(array('CURLOPT_TIMEOUT' => 10, 'CURLOPT_CONNECTTIMEOUT' => 10));
$serverurl = static::MOODLE_APPS_PORTAL_URL . "/lib/ajax/service-nologin.php";
$query = 'args=' . urlencode(json_encode($args));
$wsresponse = @json_decode($curl->post($serverurl, $query), true);
$info = $curl->get_info();
if ($curlerrno = $curl->get_errno()) {
// CURL connection error.
debugging("Unexpected response from the Moodle Apps Portal server, CURL error number: $curlerrno");
return null;
} else if ($info['http_code'] != 200) {
// Unexpected error from server.
debugging('Unexpected response from the Moodle Apps Portal server, HTTP code:' . $info['httpcode']);
return null;
} else if (!empty($wsresponse[0]['error'])) {
// Unexpected error from Moodle Apps Portal.
debugging('Unexpected response from the Moodle Apps Portal server:' . json_encode($wsresponse[0]));
return null;
} else if (empty($wsresponse[0]['data'])) {
debugging('Unexpected response from the Moodle Apps Portal server:' . json_encode($wsresponse));
return null;
}
$cache->set(0, $wsresponse[0]['data']);
return $wsresponse[0]['data'];
}
}
+760
View File
@@ -0,0 +1,760 @@
<?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 is the external API for this tool.
*
* @package tool_mobile
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_mobile;
defined('MOODLE_INTERNAL') || die();
require_once("$CFG->dirroot/webservice/lib.php");
use core_external\external_api;
use core_external\external_files;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_settings;
use core_external\external_value;
use core_external\external_warnings;
use context_system;
use moodle_exception;
use moodle_url;
use core_user;
use coding_exception;
/**
* This is the external API for this tool.
*
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external extends external_api {
/**
* Returns description of get_plugins_supporting_mobile() parameters.
*
* @return external_function_parameters
* @since Moodle 3.1
*/
public static function get_plugins_supporting_mobile_parameters() {
return new external_function_parameters(array());
}
/**
* Returns a list of Moodle plugins supporting the mobile app.
*
* @return array an array of warnings and objects containing the plugin information
* @since Moodle 3.1
*/
public static function get_plugins_supporting_mobile() {
return array(
'plugins' => api::get_plugins_supporting_mobile(),
'warnings' => array(),
);
}
/**
* Returns description of get_plugins_supporting_mobile() result value.
*
* @return \core_external\external_description
* @since Moodle 3.1
*/
public static function get_plugins_supporting_mobile_returns() {
return new external_single_structure(
array(
'plugins' => new external_multiple_structure(
new external_single_structure(
array(
'component' => new external_value(PARAM_COMPONENT, 'The plugin component name.'),
'version' => new external_value(PARAM_NOTAGS, 'The plugin version number.'),
'addon' => new external_value(PARAM_COMPONENT, 'The Mobile addon (package) name.'),
'dependencies' => new external_multiple_structure(
new external_value(PARAM_COMPONENT, 'Mobile addon name.'),
'The list of Mobile addons this addon depends on.'
),
'fileurl' => new external_value(PARAM_URL, 'The addon package url for download
or empty if it doesn\'t exist.'),
'filehash' => new external_value(PARAM_RAW, 'The addon package hash or empty if it doesn\'t exist.'),
'filesize' => new external_value(PARAM_INT, 'The addon package size or empty if it doesn\'t exist.'),
'handlers' => new external_value(PARAM_RAW, 'Handlers definition (JSON)', VALUE_OPTIONAL),
'lang' => new external_value(PARAM_RAW, 'Language strings used by the handlers (JSON)', VALUE_OPTIONAL),
)
)
),
'warnings' => new external_warnings(),
)
);
}
/**
* Returns description of get_public_config() parameters.
*
* @return external_function_parameters
* @since Moodle 3.2
*/
public static function get_public_config_parameters() {
return new external_function_parameters(array());
}
/**
* Returns a list of the site public settings, those not requiring authentication.
*
* @return array with the settings and warnings
* @since Moodle 3.2
*/
public static function get_public_config() {
$result = api::get_public_config();
$result['warnings'] = array();
return $result;
}
/**
* Returns description of get_public_config() result value.
*
* @return \core_external\external_description
* @since Moodle 3.2
*/
public static function get_public_config_returns() {
return new external_single_structure(
array(
'wwwroot' => new external_value(PARAM_RAW, 'Site URL.'),
'httpswwwroot' => new external_value(PARAM_RAW, 'Site https URL (if httpslogin is enabled).'),
'sitename' => new external_value(PARAM_RAW, 'Site name.'),
'guestlogin' => new external_value(PARAM_INT, 'Whether guest login is enabled.'),
'rememberusername' => new external_value(PARAM_INT, 'Values: 0 for No, 1 for Yes, 2 for optional.'),
'authloginviaemail' => new external_value(PARAM_INT, 'Whether log in via email is enabled.'),
'registerauth' => new external_value(PARAM_PLUGIN, 'Authentication method for user registration.'),
'forgottenpasswordurl' => new external_value(PARAM_URL, 'Forgotten password URL.'),
'authinstructions' => new external_value(PARAM_RAW, 'Authentication instructions.'),
'authnoneenabled' => new external_value(PARAM_INT, 'Whether auth none is enabled.'),
'enablewebservices' => new external_value(PARAM_INT, 'Whether Web Services are enabled.'),
'enablemobilewebservice' => new external_value(PARAM_INT, 'Whether the Mobile service is enabled.'),
'maintenanceenabled' => new external_value(PARAM_INT, 'Whether site maintenance is enabled.'),
'maintenancemessage' => new external_value(PARAM_RAW, 'Maintenance message.'),
'logourl' => new external_value(PARAM_URL, 'The site logo URL', VALUE_OPTIONAL),
'compactlogourl' => new external_value(PARAM_URL, 'The site compact logo URL', VALUE_OPTIONAL),
'typeoflogin' => new external_value(PARAM_INT, 'The type of login. 1 for app, 2 for browser, 3 for embedded.'),
'launchurl' => new external_value(PARAM_URL, 'SSO login launch URL.', VALUE_OPTIONAL),
'mobilecssurl' => new external_value(PARAM_URL, 'Mobile custom CSS theme', VALUE_OPTIONAL),
'tool_mobile_disabledfeatures' => new external_value(PARAM_RAW, 'Disabled features in the app', VALUE_OPTIONAL),
'identityproviders' => new external_multiple_structure(
new external_single_structure(
array(
'name' => new external_value(PARAM_TEXT, 'The identity provider name.'),
'iconurl' => new external_value(PARAM_URL, 'The icon URL for the provider.'),
'url' => new external_value(PARAM_URL, 'The URL of the provider.'),
)
),
'Identity providers', VALUE_OPTIONAL
),
'country' => new external_value(PARAM_NOTAGS, 'Default site country', VALUE_OPTIONAL),
'agedigitalconsentverification' => new external_value(PARAM_BOOL, 'Whether age digital consent verification
is enabled.', VALUE_OPTIONAL),
'supportname' => new external_value(PARAM_NOTAGS, 'Site support contact name
(only if age verification is enabled).', VALUE_OPTIONAL),
'supportemail' => new external_value(PARAM_EMAIL, 'Site support contact email
(only if age verification is enabled).', VALUE_OPTIONAL),
'supportpage' => new external_value(PARAM_URL, 'Site support page link.', VALUE_OPTIONAL),
'supportavailability' => new external_value(PARAM_INT, 'Determines who has access to contact site support.',
VALUE_OPTIONAL),
'autolang' => new external_value(PARAM_INT, 'Whether to detect default language
from browser setting.', VALUE_OPTIONAL),
'lang' => new external_value(PARAM_LANG, 'Default language for the site.', VALUE_OPTIONAL),
'langmenu' => new external_value(PARAM_INT, 'Whether the language menu should be displayed.', VALUE_OPTIONAL),
'langlist' => new external_value(PARAM_RAW, 'Languages on language menu.', VALUE_OPTIONAL),
'locale' => new external_value(PARAM_RAW, 'Sitewide locale.', VALUE_OPTIONAL),
'tool_mobile_minimumversion' => new external_value(PARAM_NOTAGS, 'Minimum required version to access.',
VALUE_OPTIONAL),
'tool_mobile_iosappid' => new external_value(PARAM_ALPHANUM, 'iOS app\'s unique identifier.',
VALUE_OPTIONAL),
'tool_mobile_androidappid' => new external_value(PARAM_NOTAGS, 'Android app\'s unique identifier.',
VALUE_OPTIONAL),
'tool_mobile_setuplink' => new external_value(PARAM_URL, 'App download page.', VALUE_OPTIONAL),
'tool_mobile_qrcodetype' => new external_value(PARAM_INT, 'QR login configuration.', VALUE_OPTIONAL),
'warnings' => new external_warnings(),
)
);
}
/**
* Returns description of get_config() parameters.
*
* @return external_function_parameters
* @since Moodle 3.2
*/
public static function get_config_parameters() {
return new external_function_parameters(
array(
'section' => new external_value(PARAM_ALPHANUMEXT, 'Settings section name.', VALUE_DEFAULT, ''),
)
);
}
/**
* Returns a list of site settings, filtering by section.
*
* @param string $section settings section name
* @return array with the settings and warnings
* @since Moodle 3.2
*/
public static function get_config($section = '') {
$params = self::validate_parameters(self::get_config_parameters(), array('section' => $section));
$settings = api::get_config($params['section']);
$result['settings'] = array();
foreach ($settings as $name => $value) {
$result['settings'][] = array(
'name' => $name,
'value' => $value,
);
}
$result['warnings'] = array();
return $result;
}
/**
* Returns description of get_config() result value.
*
* @return \core_external\external_description
* @since Moodle 3.2
*/
public static function get_config_returns() {
return new external_single_structure(
array(
'settings' => new external_multiple_structure(
new external_single_structure(
array(
'name' => new external_value(PARAM_RAW, 'The name of the setting'),
'value' => new external_value(PARAM_RAW, 'The value of the setting'),
)
),
'Settings'
),
'warnings' => new external_warnings(),
)
);
}
/**
* Returns description of get_autologin_key() parameters.
*
* @return external_function_parameters
* @since Moodle 3.2
*/
public static function get_autologin_key_parameters() {
return new external_function_parameters (
array(
'privatetoken' => new external_value(PARAM_ALPHANUM, 'Private token, usually generated by login/token.php'),
)
);
}
/**
* Creates an auto-login key for the current user. Is created only in https sites and is restricted by time and ip address.
*
* Please note that it only works if the request comes from the Moodle mobile or desktop app.
*
* @param string $privatetoken the user private token for validating the request
* @return array with the settings and warnings
* @since Moodle 3.2
*/
public static function get_autologin_key($privatetoken) {
global $CFG, $DB, $USER;
$params = self::validate_parameters(self::get_autologin_key_parameters(), array('privatetoken' => $privatetoken));
$privatetoken = $params['privatetoken'];
$context = context_system::instance();
// We must toletare these two exceptions: forcepasswordchangenotice and usernotfullysetup.
try {
self::validate_context($context);
} catch (moodle_exception $e) {
if ($e->errorcode != 'usernotfullysetup' && $e->errorcode != 'forcepasswordchangenotice') {
// In case we receive a different exception, throw it.
throw $e;
}
}
// Only requests from the Moodle mobile or desktop app. This enhances security to avoid any type of XSS attack.
// This code goes intentionally here and not inside the check_autologin_prerequisites() function because it
// is used by other PHP scripts that can be opened in any browser.
if (!\core_useragent::is_moodle_app()) {
throw new moodle_exception('apprequired', 'tool_mobile');
}
api::check_autologin_prerequisites($USER->id);
if (isset($_GET['privatetoken']) or empty($privatetoken)) {
throw new moodle_exception('invalidprivatetoken', 'tool_mobile');
}
// Check the request counter, we must limit the number of times the privatetoken is sent.
// Between each request 6 minutes are required.
$last = get_user_preferences('tool_mobile_autologin_request_last', 0, $USER);
// Check if we must reset the count.
$mintimereq = get_config('tool_mobile', 'autologinmintimebetweenreq');
$mintimereq = empty($mintimereq) ? 6 * MINSECS : $mintimereq;
$timenow = time();
if ($timenow - $last < $mintimereq) {
$minutes = $mintimereq / MINSECS;
throw new moodle_exception('autologinkeygenerationlockout', 'tool_mobile', '', $minutes);
}
set_user_preference('tool_mobile_autologin_request_last', $timenow, $USER);
// We are expecting a privatetoken linked to the current token being used.
// This WS is only valid when using mobile services via REST (this is intended).
$currenttoken = required_param('wstoken', PARAM_ALPHANUM);
$conditions = array(
'userid' => $USER->id,
'token' => $currenttoken,
'privatetoken' => $privatetoken,
);
if (!$token = $DB->get_record('external_tokens', $conditions)) {
throw new moodle_exception('invalidprivatetoken', 'tool_mobile');
}
$result = array();
$result['key'] = api::get_autologin_key();
$autologinurl = new moodle_url("/$CFG->admin/tool/mobile/autologin.php");
$result['autologinurl'] = $autologinurl->out(false);
$result['warnings'] = array();
return $result;
}
/**
* Returns description of get_autologin_key() result value.
*
* @return \core_external\external_description
* @since Moodle 3.2
*/
public static function get_autologin_key_returns() {
return new external_single_structure(
array(
'key' => new external_value(PARAM_ALPHANUMEXT, 'Auto-login key for a single usage with time expiration.'),
'autologinurl' => new external_value(PARAM_URL, 'Auto-login URL.'),
'warnings' => new external_warnings(),
)
);
}
/**
* Returns description of get_content() parameters
*
* @return external_function_parameters
* @since Moodle 3.5
*/
public static function get_content_parameters() {
return new external_function_parameters(
array(
'component' => new external_value(PARAM_COMPONENT, 'Component where the class is e.g. mod_assign.'),
'method' => new external_value(PARAM_ALPHANUMEXT, 'Method to execute in class \$component\output\mobile.'),
'args' => new external_multiple_structure(
new external_single_structure(
array(
'name' => new external_value(PARAM_ALPHANUMEXT, 'Param name.'),
'value' => new external_value(PARAM_RAW, 'Param value.')
)
), 'Args for the method are optional.', VALUE_OPTIONAL
)
)
);
}
/**
* Returns a piece of content to be displayed in the Mobile app, it usually returns a template, javascript and
* other structured data that will be used to render a view in the Mobile app.
*
* Callbacks (placed in \$component\output\mobile) that are called by this web service are responsible for doing the
* appropriate security checks to access the information to be returned.
*
* @param string $component name of the component.
* @param string $method function method name in class \$component\output\mobile.
* @param array $args optional arguments for the method.
* @return array HTML, JavaScript and other required data and information to create a view in the app.
* @since Moodle 3.5
* @throws coding_exception
*/
public static function get_content($component, $method, $args = array()) {
global $OUTPUT, $PAGE, $USER;
$params = self::validate_parameters(self::get_content_parameters(),
array(
'component' => $component,
'method' => $method,
'args' => $args
)
);
// Reformat arguments into something less unwieldy.
$arguments = array();
foreach ($params['args'] as $paramargument) {
$arguments[$paramargument['name']] = $paramargument['value'];
}
// The component was validated via the PARAM_COMPONENT parameter type.
$classname = '\\' . $params['component'] .'\output\mobile';
if (!method_exists($classname, $params['method'])) {
throw new coding_exception("Missing method in $classname");
}
$result = call_user_func_array(array($classname, $params['method']), array($arguments));
// Populate otherdata.
$otherdata = array();
if (!empty($result['otherdata'])) {
$result['otherdata'] = (array) $result['otherdata'];
foreach ($result['otherdata'] as $name => $value) {
$otherdata[] = array(
'name' => $name,
'value' => $value
);
}
}
return array(
'templates' => !empty($result['templates']) ? $result['templates'] : array(),
'javascript' => !empty($result['javascript']) ? $result['javascript'] : '',
'otherdata' => $otherdata,
'files' => !empty($result['files']) ? $result['files'] : array(),
'restrict' => !empty($result['restrict']) ? $result['restrict'] : array(),
'disabled' => !empty($result['disabled']) ? true : false,
);
}
/**
* Returns description of get_content() result value
*
* @return array
* @since Moodle 3.5
*/
public static function get_content_returns() {
return new external_single_structure(
array(
'templates' => new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_TEXT, 'ID of the template.'),
'html' => new external_value(PARAM_RAW, 'HTML code.'),
)
),
'Templates required by the generated content.'
),
'javascript' => new external_value(PARAM_RAW, 'JavaScript code.'),
'otherdata' => new external_multiple_structure(
new external_single_structure(
array(
'name' => new external_value(PARAM_RAW, 'Field name.'),
'value' => new external_value(PARAM_RAW, 'Field value.')
)
),
'Other data that can be used or manipulated by the template via 2-way data-binding.'
),
'files' => new external_files('Files in the content.'),
'restrict' => new external_single_structure(
array(
'users' => new external_multiple_structure(
new external_value(PARAM_INT, 'user id'), 'List of allowed users.', VALUE_OPTIONAL
),
'courses' => new external_multiple_structure(
new external_value(PARAM_INT, 'course id'), 'List of allowed courses.', VALUE_OPTIONAL
),
),
'Restrict this content to certain users or courses.'
),
'disabled' => new external_value(PARAM_BOOL, 'Whether we consider this disabled or not.', VALUE_OPTIONAL),
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.7
*/
public static function call_external_functions_parameters() {
return new external_function_parameters([
'requests' => new external_multiple_structure(
new external_single_structure([
'function' => new external_value(PARAM_ALPHANUMEXT, 'Function name'),
'arguments' => new external_value(PARAM_RAW, 'JSON-encoded object with named arguments', VALUE_DEFAULT, '{}'),
'settingraw' => new external_value(PARAM_BOOL, 'Return raw text', VALUE_DEFAULT, false),
'settingfilter' => new external_value(PARAM_BOOL, 'Filter text', VALUE_DEFAULT, false),
'settingfileurl' => new external_value(PARAM_BOOL, 'Rewrite plugin file URLs', VALUE_DEFAULT, true),
'settinglang' => new external_value(PARAM_LANG, 'Session language', VALUE_DEFAULT, ''),
])
)
]);
}
/**
* Call multiple external functions and return all responses.
*
* @param array $requests List of requests.
* @return array Responses.
* @since Moodle 3.7
*/
public static function call_external_functions($requests) {
global $SESSION;
$params = self::validate_parameters(self::call_external_functions_parameters(), ['requests' => $requests]);
// We need to check if the functions being called are included in the service of the current token.
// This function only works when using mobile services via REST (this is intended).
$webservicemanager = new \webservice;
$token = $webservicemanager->get_user_ws_token(required_param('wstoken', PARAM_ALPHANUM));
$settings = external_settings::get_instance();
$defaultlang = current_language();
$responses = [];
foreach ($params['requests'] as $request) {
// Some external functions modify _GET or $_POST data, we need to restore the original data after each call.
$originalget = fullclone($_GET);
$originalpost = fullclone($_POST);
// Set external settings and language.
$settings->set_raw($request['settingraw']);
$settings->set_filter($request['settingfilter']);
$settings->set_fileurl($request['settingfileurl']);
$settings->set_lang($request['settinglang']);
$SESSION->lang = $request['settinglang'] ?: $defaultlang;
// Parse arguments to an array, validation is done in external_api::call_external_function.
$args = @json_decode($request['arguments'], true);
if (!is_array($args)) {
$args = [];
}
if ($webservicemanager->service_function_exists($request['function'], $token->externalserviceid)) {
$response = external_api::call_external_function($request['function'], $args, false);
} else {
// Function not included in the service, return an access exception.
$response = [
'error' => true,
'exception' => [
'errorcode' => 'accessexception',
'module' => 'webservice'
]
];
if (debugging('', DEBUG_DEVELOPER)) {
$response['exception']['debuginfo'] = 'Access to the function is not allowed.';
}
}
if (isset($response['data'])) {
$response['data'] = json_encode($response['data']);
}
if (isset($response['exception'])) {
$response['exception'] = json_encode($response['exception']);
}
$responses[] = $response;
// Restore original $_GET and $_POST.
$_GET = $originalget;
$_POST = $originalpost;
if ($response['error']) {
// Do not process the remaining requests.
break;
}
}
return ['responses' => $responses];
}
/**
* Returns description of method result value
*
* @return external_single_structure
* @since Moodle 3.7
*/
public static function call_external_functions_returns() {
return new external_function_parameters([
'responses' => new external_multiple_structure(
new external_single_structure([
'error' => new external_value(PARAM_BOOL, 'Whether an exception was thrown.'),
'data' => new external_value(PARAM_RAW, 'JSON-encoded response data', VALUE_OPTIONAL),
'exception' => new external_value(PARAM_RAW, 'JSON-encoed exception info', VALUE_OPTIONAL),
])
)
]);
}
/**
* Returns description of get_tokens_for_qr_login() parameters.
*
* @return external_function_parameters
* @since Moodle 3.9
*/
public static function get_tokens_for_qr_login_parameters() {
return new external_function_parameters (
[
'qrloginkey' => new external_value(PARAM_ALPHANUMEXT, 'The user key for validating the request.'),
'userid' => new external_value(PARAM_INT, 'The user the key belongs to.'),
]
);
}
/**
* Returns a WebService token (and private token) for QR login
*
* @param string $qrloginkey the user key generated and embedded into the QR code for validating the request
* @param int $userid the user the key belongs to
* @return array with the tokens and warnings
* @since Moodle 3.9
*/
public static function get_tokens_for_qr_login($qrloginkey, $userid) {
global $PAGE, $DB;
$params = self::validate_parameters(self::get_tokens_for_qr_login_parameters(),
['qrloginkey' => $qrloginkey, 'userid' => $userid]);
$context = context_system::instance();
// We need this to make work the format text functions.
$PAGE->set_context($context);
$qrcodetype = get_config('tool_mobile', 'qrcodetype');
if ($qrcodetype != api::QR_CODE_LOGIN) {
throw new moodle_exception('qrcodedisabled', 'tool_mobile');
}
// Only requests from the Moodle mobile or desktop app. This enhances security to avoid any type of XSS attack.
// This code goes intentionally here and not inside the check_autologin_prerequisites() function because it
// is used by other PHP scripts that can be opened in any browser.
if (!\core_useragent::is_moodle_app()) {
throw new moodle_exception('apprequired', 'tool_mobile');
}
api::check_autologin_prerequisites($params['userid']); // Checks https, avoid site admins using this...
// Validate and delete the key.
$key = validate_user_key($params['qrloginkey'], 'tool_mobile/qrlogin', null);
delete_user_key('tool_mobile/qrlogin', $params['userid']);
// Double check key belong to user.
if ($key->userid != $params['userid']) {
throw new moodle_exception('invalidkey');
}
// Key validated, check user.
$user = core_user::get_user($key->userid, '*', MUST_EXIST);
core_user::require_active_user($user, true, true);
// Generate WS tokens.
\core\session\manager::set_user($user);
// Check if the service exists and is enabled.
$service = $DB->get_record('external_services', ['shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE, 'enabled' => 1]);
if (empty($service)) {
// will throw exception if no token found
throw new moodle_exception('servicenotavailable', 'webservice');
}
// Get an existing token or create a new one.
$token = \core_external\util::generate_token_for_current_user($service);
$privatetoken = $token->privatetoken; // Save it here, the next function removes it.
\core_external\util::log_token_request($token);
$result = [
'token' => $token->token,
'privatetoken' => $privatetoken ?: '',
'warnings' => [],
];
return $result;
}
/**
* Returns description of get_tokens_for_qr_login() result value.
*
* @return \core_external\external_description
* @since Moodle 3.9
*/
public static function get_tokens_for_qr_login_returns() {
return new external_single_structure(
[
'token' => new external_value(PARAM_ALPHANUM, 'A valid WebService token for the official mobile app service.'),
'privatetoken' => new external_value(PARAM_ALPHANUM, 'Private token used for auto-login processes.'),
'warnings' => new external_warnings(),
]
);
}
/**
* Returns description of validate_subscription_key() parameters.
*
* @return external_function_parameters
* @since Moodle 3.9
*/
public static function validate_subscription_key_parameters() {
return new external_function_parameters(
[
'key' => new external_value(PARAM_RAW, 'Site subscription temporary key.'),
]
);
}
/**
* Check if the given site subscription key is valid
*
* @param string $key subscriptiion temporary key
* @return array with the settings and warnings
* @since Moodle 3.9
*/
public static function validate_subscription_key(string $key): array {
global $CFG, $PAGE;
$params = self::validate_parameters(self::validate_subscription_key_parameters(), ['key' => $key]);
$context = context_system::instance();
$PAGE->set_context($context);
$validated = false;
$sitesubscriptionkey = get_config('tool_mobile', 'sitesubscriptionkey');
if (!empty($sitesubscriptionkey) && $CFG->enablemobilewebservice && empty($CFG->disablemobileappsubscription)) {
$sitesubscriptionkey = json_decode($sitesubscriptionkey);
$validated = time() < $sitesubscriptionkey->validuntil && $params['key'] === $sitesubscriptionkey->key;
// Delete existing, even if not validated to enforce security and attacks prevention.
unset_config('sitesubscriptionkey', 'tool_mobile');
}
return [
'validated' => $validated,
'warnings' => [],
];
}
/**
* Returns description of validate_subscription_key() result value.
*
* @return \core_external\external_description
* @since Moodle 3.9
*/
public static function validate_subscription_key_returns() {
return new external_single_structure(
[
'validated' => new external_value(PARAM_BOOL, 'Whether the key is validated or not.'),
'warnings' => new external_warnings(),
]
);
}
}
@@ -0,0 +1,111 @@
<?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/>.
namespace tool_mobile;
use core\session\utility\cookie_helper;
use html_writer;
/**
* Allows plugins to add any elements to the footer.
*
* @package tool_mobile
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class hook_callbacks {
/**
* Callback to add head elements.
*
* @param \core\hook\output\before_standard_head_html_generation $hook
*/
public static function before_standard_head_html_generation(
\core\hook\output\before_standard_head_html_generation $hook,
): void {
global $CFG, $PAGE;
// Smart App Banners meta tag is only displayed if mobile services are enabled and configured.
if (!empty($CFG->enablemobilewebservice)) {
$mobilesettings = get_config('tool_mobile');
if (!empty($mobilesettings->enablesmartappbanners)) {
if (!empty($mobilesettings->iosappid)) {
$hook->add_html(
'<meta name="apple-itunes-app" content="app-id=' . s($mobilesettings->iosappid) . ', ' .
'app-argument=' . $PAGE->url->out() . '"/>'
);
}
if (!empty($mobilesettings->androidappid)) {
$mobilemanifesturl = "$CFG->wwwroot/$CFG->admin/tool/mobile/mobile.webmanifest.php";
$hook->add_html('<link rel="manifest" href="' . $mobilemanifesturl . '" />');
}
}
}
}
/**
* Callback to add head elements.
*
* @param \core\hook\output\before_standard_footer_html_generation $hook
*/
public static function before_standard_footer_html_generation(
\core\hook\output\before_standard_footer_html_generation $hook,
): void {
global $CFG;
require_once(__DIR__ . '/../lib.php');
if (empty($CFG->enablemobilewebservice)) {
return;
}
$url = tool_mobile_create_app_download_url();
if (empty($url)) {
return;
}
$hook->add_html(
html_writer::div(
html_writer::link($url, get_string('getmoodleonyourmobile', 'tool_mobile'), ['class' => 'mobilelink']),
),
);
}
/**
* Callback to recover $SESSION->wantsurl.
*
* @param \core_user\hook\after_login_completed $hook
*/
public static function after_login_completed(
\core_user\hook\after_login_completed $hook,
): void {
global $SESSION, $CFG;
// Check if the user is doing a mobile app launch, if that's the case, ensure $SESSION->wantsurl is correctly set.
if (!NO_MOODLE_COOKIES && !empty($_COOKIE['tool_mobile_launch'])) {
if (empty($SESSION->wantsurl) || strpos($SESSION->wantsurl, '/tool/mobile/launch.php') === false) {
$params = json_decode($_COOKIE['tool_mobile_launch'], true);
$SESSION->wantsurl = (new \moodle_url("/$CFG->admin/tool/mobile/launch.php", $params))->out(false);
}
}
// Set Partitioned and Secure attributes to the MoodleSession cookie if the user is using the Moodle app.
if (\core_useragent::is_moodle_app()) {
cookie_helper::add_attributes_to_cookie_response_header(
'MoodleSession' . $CFG->sessioncookie,
['Secure', 'Partitioned'],
);
}
}
}
@@ -0,0 +1,41 @@
<?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/>.
namespace tool_mobile\local\hooks\output;
use core\session\utility\cookie_helper;
/**
* Allows plugins to modify headers.
*
* @package tool_mobile
* @copyright 2024 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class before_http_headers {
/**
* Callback to allow modify headers.
*
* @param \core\hook\output\before_http_headers $hook
*/
public static function callback(\core\hook\output\before_http_headers $hook): void {
global $CFG;
// Set Partitioned and Secure attributes to the MoodleSession cookie if the user is using the Moodle app.
if (\core_useragent::is_moodle_app()) {
cookie_helper::add_attributes_to_cookie_response_header('MoodleSession'.$CFG->sessioncookie, ['Secure', 'Partitioned']);
}
}
}
@@ -0,0 +1,51 @@
<?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/>.
namespace tool_mobile\local\hooks\user;
/**
* Handles mobile app launches when third-party auth plugins are put in front of MFA.
*
* @package tool_mobile
* @copyright 2024 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class after_user_passed_mfa {
/**
* Callback to recover $SESSION->wantsurl.
*
* @param \tool_mfa\hook\after_user_passed_mfa $hook
*/
public static function callback(\tool_mfa\hook\after_user_passed_mfa $hook): void {
global $SESSION, $CFG;
// Check if the user is doing a mobile app launch, if that's the case, ensure $SESSION->wantsurl is correctly set.
if (!NO_MOODLE_COOKIES && !empty($_COOKIE['tool_mobile_launch'])) {
if (empty($SESSION->wantsurl) || strpos($SESSION->wantsurl, '/tool/mobile/launch.php') === false) {
$params = json_decode($_COOKIE['tool_mobile_launch'], true);
$SESSION->wantsurl = (new \moodle_url("/$CFG->admin/tool/mobile/launch.php", $params))->out(false);
$SESSION->tool_mfa_has_been_redirected = true; // Indicate MFA that they need to follow $SESSION->wantsurl.
}
// Invalidate cookie as we won't be needing it anymore.
unset($_COOKIE['tool_mobile_launch']);
if (!headers_sent()) { // Just be very cautios as this is a critical code.
setcookie('tool_mobile_launch', '', -1, $CFG->sessioncookiepath);
}
}
}
}
@@ -0,0 +1,50 @@
<?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/>.
/**
* Renderer.
*
* @package tool_mobile
* @copyright 2020 Moodle Pty Ltd
* @author <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_mobile\output;
use plugin_renderer_base;
/**
* Renderer class.
*
* @package tool_mobile
* @copyright 2020 Moodle Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Defer to template.
*
* @param \tool_mobile\output\subscription $subscription Subscription
* @return string HTML
*/
protected function render_subscription(\tool_mobile\output\subscription $subscription): string {
$data = $subscription->export_for_template($this);
return parent::render_from_template('tool_mobile/subscription', $data);
}
}
@@ -0,0 +1,213 @@
<?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/>.
/**
* Subscription page.
*
* @package tool_mobile
* @copyright 2020 Moodle Pty Ltd
* @author <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_mobile\output;
/**
* Subscription page.
*
* @package tool_mobile
* @copyright 2020 Moodle Pty Ltd
* @author <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class subscription implements \renderable, \templatable {
/**
* Subscription data.
*
* @var array subscription data
*/
protected $subscriptiondata;
/**
* Constructor for the class, sets the subscription data.
*
* @param array $subscriptiondata subscription data
* @return void
*/
public function __construct(array $subscriptiondata) {
$this->subscriptiondata = $subscriptiondata;
}
/**
* Exports the data.
*
* @param \renderer_base $output
* @return array with the subscription information
*/
public function export_for_template(\renderer_base $output): array {
global $CFG;
$ms = get_config('tool_mobile'); // Get mobile settings.
$data = $this->subscriptiondata;
$data['appsportalurl'] = \tool_mobile\api::MOODLE_APPS_PORTAL_URL;
// First prepare messages that may come from the WS.
if (!empty($data['messages'])) {
foreach ($data['messages'] as $msg) {
$data['messages' . $msg['type']][] = ['message' => $msg['message']];
}
}
unset($data['messages']);
// Now prepare statistics information.
if (isset($data['statistics']['notifications'])) {
$data['notifications'] = $data['statistics']['notifications'];
unset($data['statistics']['notifications']);
// Find current month data.
$data['notifications']['currentactivedevices'] = 0;
if (isset($data['notifications']['monthly'][0])) {
$currentmonth = $data['notifications']['monthly'][0];
$data['notifications']['currentactivedevices'] = $currentmonth['activedevices'];
if (!empty($currentmonth['limitreachedtime'])) {
$data['notifications']['limitreachedtime'] = $currentmonth['limitreachedtime'];
$data['notifications']['ignorednotificationswarning'] = [
'message' => get_string('notificationslimitreached', 'tool_mobile', $data['appsportalurl'])
];
}
}
}
// Review features.
foreach ($data['subscription']['features'] as &$feature) {
// Check the type of features, if it is a limitation or functionality feature.
if (array_key_exists('limit', $feature)) {
if (empty($feature['limit'])) { // Unlimited, no need to calculate current values.
$feature['humanstatus'] = get_string('unlimited');
$feature['showbar'] = 0;
continue;
}
switch ($feature['name']) {
// Check active devices.
case 'pushnotificationsdevices':
if (isset($data['notifications']['currentactivedevices'])) {
$feature['status'] = $data['notifications']['currentactivedevices'];
}
break;
// Check menu items.
case 'custommenuitems':
$custommenuitems = [];
$els = rtrim($ms->custommenuitems, "\n");
if (!empty($els)) {
$custommenuitems = explode("\n", $els);
// Get unique custom menu urls.
$custommenuitems = array_flip(
array_map(function($val) {
return explode('|', $val)[1];
}, $custommenuitems)
);
}
$feature['status'] = count($custommenuitems);
break;
// Check language strings.
case 'customlanguagestrings':
$langstrings = [];
$els = rtrim($ms->customlangstrings, "\n");
if (!empty($els)) {
$langstrings = explode("\n", $els);
// Get unique language string ids.
$langstrings = array_flip(
array_map(function($val) {
return explode('|', $val)[0];
}, $langstrings)
);
}
$feature['status'] = count($langstrings);
break;
// Check disabled features strings.
case 'disabledfeatures':
$feature['status'] = empty($ms->disabledfeatures) ? 0 : count(explode(',', $ms->disabledfeatures));
break;
}
$feature['humanstatus'] = '?/' . $feature['limit'];
// Check if we should display the bar and how.
if (isset($feature['status']) && is_int($feature['status'])) {
$feature['humanstatus'] = $feature['status'] . '/' . $feature['limit'];
$feature['showbar'] = 1;
if ($feature['status'] == $feature['limit']) {
$feature['barclass'] = 'bg-warning';
}
if ($feature['status'] > $feature['limit']) {
$feature['barclass'] = 'bg-danger';
$feature['humanstatus'] .= ' - ' . get_string('subscriptionlimitsurpassed', 'tool_mobile');
}
}
} else {
$feature['humanstatus'] = empty($feature['enabled']) ? get_string('notincluded') : get_string('included');
if (empty($feature['enabled'])) {
switch ($feature['name']) {
// Check remote themes.
case 'remotethemes':
if (!empty($CFG->mobilecssurl)) {
$feature['message'] = [
'type' => 'danger', 'message' => get_string('subscriptionfeaturenotapplied', 'tool_mobile')];
}
break;
// Check site logo.
case 'sitelogo':
if ($output->get_logo_url() || $output->get_compact_logo_url()) {
$feature['message'] = [
'type' => 'danger', 'message' => get_string('subscriptionfeaturenotapplied', 'tool_mobile')];
}
break;
// Check QR automatic login.
case 'qrautomaticlogin':
if ($ms->qrcodetype == \tool_mobile\api::QR_CODE_LOGIN) {
$feature['message'] = [
'type' => 'danger', 'message' => get_string('subscriptionfeaturenotapplied', 'tool_mobile')];
}
break;
}
}
}
}
usort($data['subscription']['features'],
function (array $featurea, array $featureb) {
$isfeaturea = !array_key_exists('limit', $featurea);
$isfeatureb = !array_key_exists('limit', $featureb);
if (!$isfeaturea && $isfeatureb) {
return 1;
}
return 0;
}
);
return $data;
}
}
@@ -0,0 +1,187 @@
<?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/>.
/**
* Privacy Subsystem implementation for tool_mobile.
*
* @package tool_mobile
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_mobile\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\local\request\writer;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\userlist;
/**
* Privacy provider for tool_mobile.
*
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\user_preference_provider,
\core_privacy\local\request\plugin\provider {
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised item collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
// There is a one user preference.
$collection->add_user_preference('tool_mobile_autologin_request_last',
'privacy:metadata:preference:tool_mobile_autologin_request_last');
$collection->add_subsystem_link('core_userkey', [], 'privacy:metadata:core_userkey');
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
$sql = "SELECT ctx.id
FROM {user_private_key} k
JOIN {user} u ON k.userid = u.id
JOIN {context} ctx ON ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel
WHERE k.userid = :userid AND (k.script = 'tool_mobile' OR k.script = 'tool_mobile/qrlogin')";
$params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER];
$contextlist = new contextlist();
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if (!is_a($context, \context_user::class)) {
return;
}
// Add users based on userkey.
\core_userkey\privacy\provider::get_user_contexts_with_script($userlist, $context, 'tool_mobile');
\core_userkey\privacy\provider::get_user_contexts_with_script($userlist, $context, 'tool_mobile/qrlogin');
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
// If the user has data, then only the CONTEXT_USER should be present so get the first context.
$contexts = $contextlist->get_contexts();
if (count($contexts) == 0) {
return;
}
$context = reset($contexts);
// Sanity check that context is at the user context level, then get the userid.
if ($context->contextlevel !== CONTEXT_USER) {
return;
}
// Export associated userkeys.
\core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile');
\core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile/qrlogin');
}
/**
* Export all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
$autologinrequestlast = get_user_preferences('tool_mobile_autologin_request_last', null, $userid);
if ($autologinrequestlast !== null) {
$time = transform::datetime($autologinrequestlast);
writer::export_user_preference('tool_mobile',
'tool_mobile_autologin_request_last',
$time,
get_string('privacy:metadata:preference:tool_mobile_autologin_request_last', 'tool_mobile')
);
}
}
/**
* Delete all use data which matches the specified deletion_criteria.
*
* @param context $context A user context.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
// Sanity check that context is at the user context level, then get the userid.
if ($context->contextlevel !== CONTEXT_USER) {
return;
}
$userid = $context->instanceid;
// Delete all the userkeys.
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
// If the user has data, then only the user context should be present so get the first context.
$contexts = $contextlist->get_contexts();
if (count($contexts) == 0) {
return;
}
$context = reset($contexts);
// Sanity check that context is at the user context level, then get the userid.
if ($context->contextlevel !== CONTEXT_USER) {
return;
}
$userid = $context->instanceid;
// Delete all the userkeys.
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;
$context = $userlist->get_context();
$userids = $userlist->get_userids();
$userid = reset($userids);
// Only deleting data for the user ID in that user's user context should be valid.
if ($context->contextlevel !== CONTEXT_USER || count($userids) != 1 || $userid != $context->instanceid) {
return;
}
// Delete all the userkeys.
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
}
}
+39
View File
@@ -0,0 +1,39 @@
<?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/>.
/**
* Mobile cache definitions.
*
* @package tool_mobile
* @copyright 2017 Skylar Kelty <S.Kelty@kent.ac.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$definitions = array(
'plugininfo' => array(
'mode' => cache_store::MODE_APPLICATION,
'simplekeys' => true,
'staticacceleration' => true,
'staticaccelerationsize' => 1
),
'subscriptiondata' => array(
'mode' => cache_store::MODE_SESSION,
'simplekeys' => true,
'simpledata' => false,
),
);
+51
View File
@@ -0,0 +1,51 @@
<?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/>.
/**
* Hook callbacks for Moodle app tools
*
* @package tool_mobile
* @copyright 2023 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$callbacks = [
[
'hook' => \core\hook\output\before_standard_head_html_generation::class,
'callback' => [\tool_mobile\hook_callbacks::class, 'before_standard_head_html_generation'],
],
[
'hook' => \core\hook\output\before_standard_footer_html_generation::class,
'callback' => [\tool_mobile\hook_callbacks::class, 'before_standard_footer_html_generation'],
'priority' => 0,
],
[
'hook' => \core_user\hook\after_login_completed::class,
'callback' => [\tool_mobile\hook_callbacks::class, 'after_login_completed'],
'priority' => 500,
],
[
'hook' => tool_mfa\hook\after_user_passed_mfa::class,
'callback' => 'tool_mobile\local\hooks\user\after_user_passed_mfa::callback',
'priority' => 500,
],
[
'hook' => \core\hook\output\before_http_headers::class,
'callback' => [\tool_mobile\local\hooks\output\before_http_headers::class, 'callback'],
],
];
+101
View File
@@ -0,0 +1,101 @@
<?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/>.
/**
* Moodle Mobile tools webservice definitions.
*
*
* @package tool_mobile
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$functions = array(
'tool_mobile_get_plugins_supporting_mobile' => array(
'classname' => 'tool_mobile\external',
'methodname' => 'get_plugins_supporting_mobile',
'description' => 'Returns a list of Moodle plugins supporting the mobile app.',
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
'ajax' => true,
'loginrequired' => false,
),
'tool_mobile_get_public_config' => array(
'classname' => 'tool_mobile\external',
'methodname' => 'get_public_config',
'description' => 'Returns a list of the site public settings, those not requiring authentication.',
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
'ajax' => true,
'loginrequired' => false,
),
'tool_mobile_get_config' => array(
'classname' => 'tool_mobile\external',
'methodname' => 'get_config',
'description' => 'Returns a list of the site configurations, filtering by section.',
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_mobile_get_autologin_key' => array(
'classname' => 'tool_mobile\external',
'methodname' => 'get_autologin_key',
'description' => 'Creates an auto-login key for the current user.
Is created only in https sites and is restricted by time, ip address and only works if the request
comes from the Moodle mobile or desktop app.',
'type' => 'write',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_mobile_get_content' => array(
'classname' => 'tool_mobile\external',
'methodname' => 'get_content',
'description' => 'Returns a piece of content to be displayed in the Mobile app.',
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_mobile_call_external_functions' => array(
'classname' => 'tool_mobile\external',
'methodname' => 'call_external_functions',
'description' => 'Call multiple external functions and return all responses.',
'type' => 'write',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'tool_mobile_validate_subscription_key' => array(
'classname' => 'tool_mobile\external',
'methodname' => 'validate_subscription_key',
'description' => 'Check if the given site subscription key is valid.',
'type' => 'write',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
'ajax' => true,
'loginrequired' => false,
),
'tool_mobile_get_tokens_for_qr_login' => array(
'classname' => 'tool_mobile\external',
'methodname' => 'get_tokens_for_qr_login',
'description' => 'Returns a WebService token (and private token) for QR login.',
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
'ajax' => true,
'loginrequired' => false,
),
);
+45
View File
@@ -0,0 +1,45 @@
<?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/>.
/**
* Mobile app support.
*
* @package tool_mobile
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Upgrade the plugin.
*
* @param int $oldversion
* @return bool always true
*/
function xmldb_tool_mobile_upgrade($oldversion) {
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+158
View File
@@ -0,0 +1,158 @@
<?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/>.
/**
* Strings for component 'tool_mobile', language 'en'
*
* @package tool_mobile
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['adodbdebugwarning'] = 'ADOdb debugging is enabled. It should be disabled in the external database authentication or external database enrolment plugin settings.';
$string['androidappid'] = 'Android app\'s unique identifier';
$string['androidappid_desc'] = 'This setting may be left as default unless you have a custom Android app.';
$string['apppolicy'] = 'App policy URL';
$string['apppolicy_help'] = 'The URL of a policy for app users which is listed on the About page in the app. If the field is left empty, the site policy URL will be used instead.';
$string['apprequired'] = 'This functionality is only available when accessed via the Moodle mobile or desktop app.';
$string['autologinkeygenerationlockout'] = 'Auto-login key generation is blocked. You need to wait {$a} minutes between requests.';
$string['autologinmintimebetweenreq'] = 'Minimum time between auto-login requests';
$string['autologinmintimebetweenreq_desc'] = 'The minimum time between auto-login requests from the mobile app. If app users are frequently asked to enter their credentials when viewing content embedded from the site, then set a lower value.';
$string['autologinnotallowedtoadmins'] = 'Auto-login is not allowed for site admins.';
$string['autologout'] = 'Enforce auto logout for your users';
$string['autologout_desc'] = 'For security reasons, you can enforce automatic logout for your users when they leave or close the app, or it goes to background. Users will have to log in again when they return to the app.';
$string['autologoutcustom'] = 'Custom time after users leave or close the app';
$string['autologoutinmediate'] = 'Immediately after users leave or close the app';
$string['autologouttime'] = 'Auto logout timer';
$string['cachedef_plugininfo'] = 'This stores the list of plugins with mobile addons';
$string['cachedef_subscriptiondata'] = 'This stores the Moodle app subscription information.';
$string['clickheretolaunchtheapp'] = 'Click here if the app does not open automatically.';
$string['configmobilecssurl'] = 'A CSS file to customise your mobile app interface.';
$string['customlangstrings'] = 'Custom language strings';
$string['customlangstrings_desc'] = 'Words and phrases displayed in the app can be customised here. Enter each custom language string on a new line with format: string identifier, custom language string and language code, separated by pipe characters. For example:
<pre>
mm.user.student|Learner|en
mm.user.student|Aprendiz|es
</pre>
For a complete list of string identifiers, see the documentation.';
$string['custommenuitems'] = 'Custom menu items';
$string['custommenuitems_desc'] = 'Additional items can be added to the app\'s main menu by specifying them here. Enter each custom menu item on a new line with format: item text, link URL, link-opening method and language code (optional, for displaying the item to users of the specified language only), separated by pipe characters.
Link-opening methods are: app (for linking to an activity supported by the app), inappbrowser (for opening a link in a browser without leaving the app), browser (for opening the link in the device default browser outside the app) and embedded (for displaying the link in an iframe in a new page in the app).
When items are missing a translation for a given language, they will use other languages as fallback unless "_only" is appended to the language code.
For example:
<pre>
App help|https://someurl.xyz/help|inappbrowser
My grades|https://someurl.xyz/local/mygrades/index.php|embedded|en
Mis calificaciones|https://someurl.xyz/local/mygrades/index.php|embedded|es
You will only see this in English|https://someurl.xyz/english|browser|en_only
</pre>';
$string['darkmode'] = 'Dark mode';
$string['disabledfeatures'] = 'Disabled features';
$string['disabledfeatures_desc'] = 'Select here the features you want to disable in the Mobile app for your site. Please note that some features listed here could be already disabled via other site settings. You will have to log out and log in again in the app to see the changes.';
$string['displayerrorswarning'] = 'Display debug messages (debugdisplay) is enabled. It should be disabled.';
$string['downloadcourse'] = 'Download course';
$string['downloadcourses'] = 'Download courses';
$string['enablesmartappbanners'] = 'Enable App Banners';
$string['enablesmartappbanners_desc'] = 'If enabled, a banner promoting the mobile app will be displayed when accessing the site using a mobile browser.';
$string['filetypeexclusionlist'] = 'File type exclusion list';
$string['filetypeexclusionlist_desc'] = 'Select all file types which are not for use on a mobile device. Such files will be listed in the course, then if a user attempts to open them, a warning will be displayed advising that the file type is not intended for use on a mobile device. The user can then cancel or ignore the warning and open the file anyway.';
$string['filetypeexclusionlistplaceholder'] = 'Mobile file type exclusion list';
$string['forcedurlscheme'] = 'If you want to allow only your custom branded app to be opened via a browser window, then specify its URL scheme here. If you want to allow only the official app, then set the default value. Leave the field empty if you want to allow any app.';
$string['forcedurlscheme_key'] = 'URL scheme';
$string['forcelogout'] = 'Force log out';
$string['forcelogout_desc'] = 'If enabled, users will be always completely logged out even when switching accounts. They must then re-enter their password the next time they wish to access the site.';
$string['h5poffline'] = 'View H5P content offline';
$string['httpsrequired'] = 'HTTPS required';
$string['insecurealgorithmwarning'] = 'It seems that the HTTPS certificate uses an insecure algorithm for signing (SHA-1). Please try updating the certificate.';
$string['invalidcertificatechainwarning'] = 'It seems that the certificate chain is invalid. This certificate might work for a browser but not for a mobile app.';
$string['invalidcertificateexpiredatewarning'] = 'It seems that the HTTPS certificate for the site has expired.';
$string['invalidcertificatestartdatewarning'] = 'It seems that the HTTPS certificate for the site is not yet valid (with a start date in the future).';
$string['invalidprivatetoken'] = 'Invalid private token. Token should not be empty or passed via GET parameter.';
$string['invaliduserquotawarning'] = 'The user quota (userquota) is set to an invalid number. It should be set to a valid number (an integer value) in Site security settings.';
$string['iosappid'] = 'iOS app\'s unique identifier';
$string['iosappid_desc'] = 'This setting may be left as default unless you have a custom iOS app.';
$string['launchviasiteinbrowser'] = 'Launch via site in system browser';
$string['loginintheapp'] = 'Via the app';
$string['logininthebrowser'] = 'Via a browser window (for SSO plugins)';
$string['loginintheembeddedbrowser'] = 'Via an embedded browser (for SSO plugins)';
$string['logoutconfirmation'] = 'Are you sure you want to log out from the mobile app on your mobile devices? By logging out, you will then need to re-enter your username and password in the mobile app on all devices where you have the app installed.';
$string['mainmenu'] = 'Main menu';
$string['managefiletypes'] = 'Manage file types';
$string['minimumversion'] = 'If an app version is specified (3.8.0 or higher), any users using an older app version will be prompted to upgrade their app before being allowed access to the site.';
$string['minimumversion_key'] = 'Minimum app version required';
$string['mobileapp'] = 'Mobile app';
$string['mobileappenabled'] = 'This site has mobile app access enabled.<br /><a href="{$a}">Download the mobile app</a>.';
$string['mobileappearance'] = 'Mobile appearance';
$string['mobileappsubscription'] = 'Moodle app subscription';
$string['mobileauthentication'] = 'Mobile authentication';
$string['mobilecssurl'] = 'CSS';
$string['mobilefeatures'] = 'Mobile features';
$string['mobilenotificationsdisabledwarning'] = 'Mobile notifications are not enabled. They should be enabled in Notification settings.';
$string['mobilesettings'] = 'Mobile settings';
$string['moodleappsportalfeatureswarning'] = 'Please note that some features may be restricted depending on your Moodle app subscription. For details, visit the <a href="{$a}" target="_blank">Moodle Apps Portal</a>.';
$string['notifications'] = 'Notifications';
$string['notificationsactivedevices'] = 'Active devices';
$string['notificationsignorednotifications'] = 'Notifications not sent';
$string['notificationslimitreached'] = 'The monthly active user devices limit has been exceeded. Notifications for some users will not be sent. It is recommended that you upgrade your app plan in the <a href="{$a}" target="_blank">Moodle Apps Portal</a>.';
$string['notificationsmissingwarning'] = 'Moodle app notification statistics could not be retrieved. This is most likely because mobile notifications are not yet enabled on the site. You can enable them in Site Administration / Messaging / Mobile.';
$string['notificationsnewdevices'] = 'New devices';
$string['notificationsseemore'] = 'Note: Moodle app usage statistics are not calculated in real time. To access more detailed statistics, including data from previous months, please log in to the <a href="{$a}" target="_blank">Moodle Apps Portal</a>.';
$string['notificationssentnotifications'] = 'Notifications sent';
$string['notificationscurrentactivedevices'] = 'Devices receiving notifications this month';
$string['oauth2identityproviders'] = 'OAuth 2 identity providers';
$string['offlineuse'] = 'Offline use';
$string['pluginname'] = 'Moodle app tools';
$string['pluginnotenabledorconfigured'] = 'Plugin not enabled or configured.';
$string['qrcodedisabled'] = 'Access via QR code disabled';
$string['qrcodeformobileappaccess'] = 'QR code for mobile app access';
$string['qrcodeformobileapploginabout'] = 'Scan the QR code with your mobile app and you will be automatically logged in. The QR code will expire in {$a}.';
$string['qrcodeformobileappurlabout'] = 'Scan the QR code with your mobile app to fill in the site URL in your app.';
$string['qrsiteadminsnotallowed'] = 'For security reasons login via QR code is not allowed for site administrators or if you are logged in as another user.';
$string['qrcodetype'] = 'QR code access';
$string['qrcodetype_desc'] = 'A QR code can be provided for mobile app users to scan. This can be used to fill in the site URL, or where the site is secured using HTTPS, to automatically log the user in without having to enter their username and password.';
$string['qrcodetypeurl'] = 'QR code with site URL';
$string['qrcodetypelogin'] = 'QR code with automatic login';
$string['qrkeyttl'] = 'QR authentication key duration';
$string['qrkeyttl_desc'] = 'The length of time for which a QR code for automatic login is valid.';
$string['qrsameipcheck'] = 'QR authentication same IP check';
$string['qrsameipcheck_desc'] = 'Whether users must use the same network for both generating and scanning a QR code for login. Only disable it if users report issues with the QR login.';
$string['readingthisemailgettheapp'] = 'Are you reading this in an email? <a href="{$a}">Download the mobile app and receive notifications on your mobile device</a>.';
$string['remoteaddons'] = 'Remote add-ons';
$string['scanqrcode'] = 'Scan QR code';
$string['selfsignedoruntrustedcertificatewarning'] = 'It seems that the HTTPS certificate is self-signed or not trusted. The mobile app will only work with trusted sites. Please use any online SSL checker to diagnose the problem. If it indicates that your certificate is OK, you can ignore this warning.';
$string['setuplink'] = 'App download page';
$string['setuplink_desc'] = 'URL of page with options to download the mobile app from the App Store and Google Play. The app download page link is displayed in the page footer and in a user\'s profile. Leave blank to not display a link.';
$string['smartappbanners'] = 'App Banners';
$string['subscription'] = 'Subscription';
$string['subscriptioncreated'] = 'Start date';
$string['subscriptionerrorrequest'] = 'There was an unexpected error when trying to retrieve your Moodle app subscription information.';
$string['subscriptionexpiration'] = 'Expiry date';
$string['subscriptionfeaturenotapplied'] = 'This feature is configured on your site but it is not included in your Moodle app plan. Thus, the setting will have no effect.';
$string['subscriptionfeatures'] = 'Subscription features';
$string['subscriptionlimitsurpassed'] = 'Subscription limit exceeded';
$string['subscriptionregister'] = 'For details of the various app plans, and to access Moodle app usage statistics, please visit the <a href="{$a}" target="_blank">Moodle Apps Portal</a>.';
$string['subscriptionsseemore'] = 'Note: The information displayed is not updated in real time. You may need to log out and log in again to see updates. For information on upgrading your app plan, please log in to the <a href="{$a}" target="_blank">Moodle Apps Portal</a>.';
$string['typeoflogin'] = 'Type of login';
$string['typeoflogin_desc'] = 'If the site uses a SSO authentication method, then select via a browser window or via an embedded browser. An embedded browser provides a better user experience, though it doesn\'t work with all SSO plugins.';
$string['getmoodleonyourmobile'] = 'Get the mobile app';
$string['privacy:metadata:preference:tool_mobile_autologin_request_last'] = 'The date of the last auto-login key request. Between each request 6 minutes are required.';
$string['privacy:metadata:core_userkey'] = 'User\'s keys used to create auto-login key for the current user.';
$string['responsivemainmenuitems'] = 'Responsive menu items';
$string['switchaccount'] = 'Switch account';
$string['viewqrcode'] = 'View QR code';
+158
View File
@@ -0,0 +1,158 @@
<?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/>.
/**
* Launch page, launch the app using custom URL schemes.
*
* If the user is not logged when visiting this page, he will be redirected to the login page.
* Once he is logged, he will be redirected again to this page and the app launched via custom URL schemes.
*
* @package tool_mobile
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../config.php');
$serviceshortname = required_param('service', PARAM_ALPHANUMEXT);
$passport = required_param('passport', PARAM_RAW); // Passport send from the app to validate the response URL.
$urlscheme = optional_param('urlscheme', 'moodlemobile', PARAM_NOTAGS); // The URL scheme the app supports.
$confirmed = optional_param('confirmed', false, PARAM_BOOL); // If we are being redirected after user confirmation.
$oauthsso = optional_param('oauthsso', 0, PARAM_INT); // Id of the OpenID issuer (for OAuth direct SSO).
// Validate that the urlscheme is valid.
if (!preg_match('/^[a-zA-Z][a-zA-Z0-9-\+\.]*$/', $urlscheme)) {
throw new moodle_exception('Invalid parameter: the value of urlscheme isn\'t valid. ' .
'It should start with a letter and can only contain letters, numbers and the characters "." "+" "-".');
}
// Check web services enabled.
if (!$CFG->enablewebservices) {
throw new moodle_exception('enablewsdescription', 'webservice');
}
// Check if the service exists and is enabled.
$service = $DB->get_record('external_services', ['shortname' => $serviceshortname, 'enabled' => 1]);
if (empty($service)) {
throw new moodle_exception('servicenotavailable', 'webservice');
}
// Set a cookie indicating that there was a launch to authenticate via the site from the app.
$ldata = json_encode(['service' => $serviceshortname, 'passport' => $passport,
'urlscheme' => $urlscheme, 'confirmed' => (int) $confirmed, 'oauthsso' => $oauthsso]);
$expires = time() + (15 * MINSECS); // 15 minutes for authentication should be enough.
setcookie('tool_mobile_launch', $ldata, $expires, $CFG->sessioncookiepath, $CFG->sessioncookiedomain);
// We have been requested to start a SSO process via OpenID.
if (!empty($oauthsso) && is_enabled_auth('oauth2')) {
$wantsurl = new moodle_url('/admin/tool/mobile/launch.php',
array('service' => $serviceshortname, 'passport' => $passport, 'urlscheme' => $urlscheme, 'confirmed' => $confirmed));
$oauthurl = new moodle_url('/auth/oauth2/login.php',
array('id' => $oauthsso, 'sesskey' => sesskey(), 'wantsurl' => $wantsurl));
header('Location: ' . $oauthurl->out(false));
die;
}
// Check if the plugin is properly configured.
$typeoflogin = get_config('tool_mobile', 'typeoflogin');
if (empty($SESSION->justloggedin) &&
!is_enabled_auth('oauth2') &&
$typeoflogin != tool_mobile\api::LOGIN_VIA_BROWSER &&
$typeoflogin != tool_mobile\api::LOGIN_VIA_EMBEDDED_BROWSER) {
throw new moodle_exception('pluginnotenabledorconfigured', 'tool_mobile');
}
// If the user is using the inapp (embedded) browser, we need to set the Secure and Partitioned attributes to the session cookie.
if (\core_useragent::is_moodle_app()) {
\core\session\utility\cookie_helper::add_attributes_to_cookie_response_header(
"MoodleSession{$CFG->sessioncookie}",
['Secure', 'Partitioned'],
);
}
require_login(0, false);
// Require an active user: not guest, not suspended.
core_user::require_active_user($USER);
// Remove cookie.
unset($_COOKIE['tool_mobile_launch']);
setcookie('tool_mobile_launch', '', -1, $CFG->sessioncookiepath);
// Get an existing token or create a new one.
$timenow = time();
$token = \core_external\util::generate_token_for_current_user($service);
$privatetoken = $token->privatetoken;
\core_external\util::log_token_request($token);
// Don't return the private token if the user didn't just log in and a new token wasn't created.
if (empty($SESSION->justloggedin) and $token->timecreated < $timenow) {
$privatetoken = null;
}
$siteadmin = has_capability('moodle/site:config', context_system::instance(), $USER->id);
// Passport is generated in the mobile app, so the app opening can be validated using that variable.
// Passports are valid only one time, it's deleted in the app once used.
$siteid = md5($CFG->wwwroot . $passport);
$apptoken = $siteid . ':::' . $token->token;
if ($privatetoken and is_https() and !$siteadmin) {
$apptoken .= ':::' . $privatetoken;
}
$apptoken = base64_encode($apptoken);
// Redirect using the custom URL scheme checking first if a URL scheme is forced in the site settings.
$forcedurlscheme = get_config('tool_mobile', 'forcedurlscheme');
if (!empty($forcedurlscheme)) {
$urlscheme = $forcedurlscheme;
}
$location = "$urlscheme://token=$apptoken";
// For iOS 10 onwards, we have to simulate a user click.
// If we come from the confirmation page, we should display a nicer page.
$isios = core_useragent::is_ios();
if ($confirmed or $isios) {
$PAGE->set_context(context_system::instance());
$PAGE->set_heading($COURSE->fullname);
$params = array('service' => $serviceshortname, 'passport' => $passport, 'urlscheme' => $urlscheme, 'confirmed' => $confirmed);
$PAGE->set_url("/$CFG->admin/tool/mobile/launch.php", $params);
echo $OUTPUT->header();
if ($confirmed) {
$confirmedstr = get_string('confirmed');
$PAGE->navbar->add($confirmedstr);
$PAGE->set_title($confirmedstr);
echo $OUTPUT->notification($confirmedstr, \core\output\notification::NOTIFY_SUCCESS);
echo $OUTPUT->box_start('generalbox centerpara boxwidthnormal boxaligncenter');
echo $OUTPUT->single_button(new moodle_url('/course/'), get_string('courses'));
echo $OUTPUT->box_end();
}
$notice = get_string('clickheretolaunchtheapp', 'tool_mobile');
echo $OUTPUT->box(html_writer::link($location, $notice, ['id' => 'launchapp']), 'generalbox warning centerpara');
echo html_writer::script(
"window.onload = function() {
document.getElementById('launchapp').click();
};"
);
echo $OUTPUT->footer();
} else {
// For Android a http redirect will do fine.
header('Location: ' . $location);
die;
}
+231
View File
@@ -0,0 +1,231 @@
<?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/>.
/**
* Lib functions, mostly callbacks.
*
* @package tool_mobile
* @copyright 2017 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Generate the app download url to promote moodle mobile.
*
* @return moodle_url|void App download moodle_url object or return if setuplink is not set.
*/
function tool_mobile_create_app_download_url() {
global $CFG;
$mobilesettings = get_config('tool_mobile');
if (empty($mobilesettings->setuplink)) {
return;
}
$downloadurl = new moodle_url($mobilesettings->setuplink);
// Do not update the URL if it is a custom one (we may break it completely).
if ($mobilesettings->setuplink != 'https://download.moodle.org/mobile') {
return $downloadurl;
}
$downloadurl->param('version', $CFG->version);
$downloadurl->param('lang', current_language());
if (!empty($mobilesettings->iosappid)) {
$downloadurl->param('iosappid', $mobilesettings->iosappid);
}
if (!empty($mobilesettings->androidappid)) {
$downloadurl->param('androidappid', $mobilesettings->androidappid);
}
// For privacy reasons, add siteurl param only if the site is registered.
// This is to implement Google Play Referrer (so the site url is automatically populated in the app after installation).
if (\core\hub\registration::is_registered()) {
$downloadurl->param('siteurl', $CFG->wwwroot);
}
return $downloadurl;
}
/**
* Return the user mobile app WebService access token.
*
* @param int $userid the user to return the token from
* @return stdClass|false the token or false if the token doesn't exists
* @since 3.10
*/
function tool_mobile_get_token($userid) {
global $DB;
$sql = "SELECT t.*
FROM {external_tokens} t, {external_services} s
WHERE t.externalserviceid = s.id
AND s.enabled = 1
AND s.shortname IN ('moodle_mobile_app', 'local_mobile')
AND t.userid = ?";
return $DB->get_record_sql($sql, [$userid], IGNORE_MULTIPLE);
}
/**
* Checks if the given user has a mobile token (has used recently the app).
*
* @param int $userid the user to check
* @return bool true if the user has a token, false otherwise.
*/
function tool_mobile_user_has_token($userid) {
return !empty(tool_mobile_get_token($userid));
}
/**
* User profile page callback.
*
* Used add a section about the moodle mobile app.
*
* @param \core_user\output\myprofile\tree $tree My profile tree where the setting will be added.
* @param stdClass $user The user object.
* @param bool $iscurrentuser Is this the current user viewing
* @return void Return if the mobile web services setting is disabled or if not the current user.
*/
function tool_mobile_myprofile_navigation(\core_user\output\myprofile\tree $tree, $user, $iscurrentuser) {
global $CFG;
if (empty($CFG->enablemobilewebservice)) {
return;
}
$newnodes = [];
$mobilesettings = get_config('tool_mobile');
// Check if we should display a QR code.
if ($iscurrentuser && !empty($mobilesettings->qrcodetype)) {
$mobileqr = null;
$qrcodeforappstr = get_string('qrcodeformobileappaccess', 'tool_mobile');
if ($mobilesettings->qrcodetype == tool_mobile\api::QR_CODE_LOGIN && is_https()) {
if (is_siteadmin() || \core\session\manager::is_loggedinas()) {
$mobileqr = get_string('qrsiteadminsnotallowed', 'tool_mobile');
} else {
$qrcodeimg = tool_mobile\api::generate_login_qrcode($mobilesettings);
$qrkeyttl = !empty($mobilesettings->qrkeyttl) ? $mobilesettings->qrkeyttl : tool_mobile\api::LOGIN_QR_KEY_TTL;
$mobileqr = html_writer::tag('p', get_string('qrcodeformobileapploginabout', 'tool_mobile',
format_time($qrkeyttl)));
$mobileqr .= html_writer::link('#qrcode', get_string('viewqrcode', 'tool_mobile'),
['class' => 'btn btn-primary mt-2', 'data-toggle' => 'collapse',
'role' => 'button', 'aria-expanded' => 'false']);
$mobileqr .= html_writer::div(html_writer::img($qrcodeimg, $qrcodeforappstr), 'collapse mt-4', ['id' => 'qrcode']);
}
} else if ($mobilesettings->qrcodetype == tool_mobile\api::QR_CODE_URL) {
$qrcodeimg = tool_mobile\api::generate_login_qrcode($mobilesettings);
$mobileqr = get_string('qrcodeformobileappurlabout', 'tool_mobile');
$mobileqr .= html_writer::div(html_writer::img($qrcodeimg, $qrcodeforappstr));
}
if ($mobileqr) {
$newnodes[] = new core_user\output\myprofile\node('mobile', 'mobileappqr', $qrcodeforappstr, null, null, $mobileqr);
}
}
// Check if the user is using the app, encouraging him to use it otherwise.
if ($iscurrentuser || is_siteadmin()) {
$usertoken = tool_mobile_get_token($user->id);
$mobilestrconnected = null;
$mobilelastaccess = null;
if ($usertoken) {
$mobilestrconnected = get_string('lastsiteaccess');
if ($usertoken->lastaccess) {
$mobilelastaccess = userdate($usertoken->lastaccess) . "&nbsp; (" . format_time(time() - $usertoken->lastaccess) . ")";
// Logout link.
$validtoken = empty($usertoken->validuntil) || time() < $usertoken->validuntil;
if ($iscurrentuser && $validtoken) {
$url = new moodle_url('/'.$CFG->admin.'/tool/mobile/logout.php', ['sesskey' => sesskey()]);
$logoutlink = html_writer::link($url, get_string('logout'));
$mobilelastaccess .= "&nbsp; ($logoutlink)";
}
} else {
// We should not reach this point.
$mobilelastaccess = get_string("never");
}
} else if ($url = tool_mobile_create_app_download_url()) {
$mobilestrconnected = get_string('mobileappenabled', 'tool_mobile', $url->out());
}
if ($mobilestrconnected) {
$newnodes[] = new core_user\output\myprofile\node('mobile', 'mobileappnode', $mobilestrconnected, null, null,
$mobilelastaccess);
}
}
// Add nodes, if any.
if (!empty($newnodes)) {
$mobilecat = new core_user\output\myprofile\category('mobile', get_string('mobileapp', 'tool_mobile'), 'loginactivity');
$tree->add_category($mobilecat);
foreach ($newnodes as $node) {
$tree->add_node($node);
}
}
}
/**
* Callback to be able to change a message/notification data per processor.
*
* @param str $procname processor name
* @param stdClass $data message or notification data
*/
function tool_mobile_pre_processor_message_send($procname, $data) {
global $CFG;
if (empty($CFG->enablemobilewebservice)) {
return;
}
if (empty($data->userto)) {
return;
}
// Only hack email.
if ($procname == 'email') {
// Send a message only when there is an HTML version of the email, mobile services are enabled,
// the user receiving the message has not used the app and there is an app download URL set.
if (empty($data->fullmessagehtml)) {
return;
}
if (!$url = tool_mobile_create_app_download_url()) {
return;
}
$userto = is_object($data->userto) ? $data->userto->id : $data->userto;
if (tool_mobile_user_has_token($userto)) {
return;
}
$data->fullmessagehtml .= html_writer::tag('p', get_string('readingthisemailgettheapp', 'tool_mobile', $url->out()));
}
}
+70
View File
@@ -0,0 +1,70 @@
<?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/>.
/**
* Log out a user from his external mobile devices (phones, tables, Moodle Desktop app, etc..)
*
* @package tool_mobile
* @copyright 2020 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__ . '/../../../config.php');
require_once($CFG->dirroot . '/admin/tool/mobile/lib.php');
require_once($CFG->dirroot . '/webservice/lib.php');
if (!$CFG->enablemobilewebservice) {
throw new \moodle_exception('enablewsdescription', 'webservice');
}
require_login(null, false);
// Require an active user: not guest, not suspended.
core_user::require_active_user($USER);
$redirecturl = new \moodle_url('/user/profile.php');
if (optional_param('confirm', 0, PARAM_INT) && data_submitted()) {
require_sesskey();
// Get the mobile service token to be deleted.
$token = tool_mobile_get_token($USER->id);
if ($token) {
$webservicemanager = new webservice();
$webservicemanager->delete_user_ws_token($token->id);
}
redirect($redirecturl);
}
// Page settings.
$title = get_string('logout');
$context = context_system::instance();
$PAGE->set_url(new \moodle_url('/'.$CFG->admin.'/tool/mobile/logout.php'));
$PAGE->navbar->add($title);
$PAGE->set_context($context);
$PAGE->set_title($title);
// Display the page.
echo $OUTPUT->header();
$message = get_string('logoutconfirmation', 'tool_mobile');
$confirmurl = new \moodle_url('logout.php', ['confirm' => 1]);
$yesbutton = new single_button($confirmurl, get_string('yes'), 'post');
$nobutton = new single_button($redirecturl, get_string('no'));
echo $OUTPUT->confirm($message, $yesbutton, $nobutton);
echo $OUTPUT->footer();
+61
View File
@@ -0,0 +1,61 @@
<?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/>.
/**
* Web manifest for including a native app banner.
*
* The banner is only displayed if the user has visited the site twice over two
* separate days during the course of two weeks. There is an experimental chrome
* flag to allow testing.
* More information here: https://developer.android.com/distribute/users/banners.html
*
* @package tool_mobile
* @copyright 2017 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('NO_DEBUG_DISPLAY', true);
require_once(__DIR__ . '/../../../config.php');
header('Content-Type: application/json; charset: utf-8');
$mobilesettings = get_config('tool_mobile');
// Display manifest contents only if all the required conditions are met.
if (!empty($CFG->enablemobilewebservice) && !empty($mobilesettings->enablesmartappbanners) &&
!empty($mobilesettings->androidappid)) {
$manifest = new StdClass;
$manifest->short_name = format_string($SITE->shortname, true, [
'context' => \core\context\system::instance(),
]);
$manifest->prefer_related_applications = true;
$manifest->icons = [(object)
[
'sizes' => '144x144',
'type' => 'image/png',
'src' => "$CFG->wwwroot/$CFG->admin/tool/mobile/pix/icon_144.png"
]
];
$manifest->related_applications = [(object)
[
'platform' => 'play',
'id' => $mobilesettings->androidappid,
]
];
echo json_encode($manifest);
}
die;
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+266
View File
@@ -0,0 +1,266 @@
<?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/>.
/**
* Settings
*
* This file contains settings used by tool_mobile
*
* @package tool_mobile
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
use core_admin\local\settings\autocomplete;
if ($hassiteconfig) {
// We should wait to the installation to finish since we depend on some configuration values that are set once
// the admin user profile is configured.
if (!during_initial_install()) {
$enablemobiledocurl = new moodle_url(get_docs_url('Enable_mobile_web_services'));
$enablemobiledoclink = html_writer::link($enablemobiledocurl, new lang_string('documentation'));
$default = is_https() ? 1 : 0;
$optionalsubsystems = $ADMIN->locate('optionalsubsystems');
$optionalsubsystems->add(new admin_setting_enablemobileservice('enablemobilewebservice',
new lang_string('enablemobilewebservice', 'admin'),
new lang_string('configenablemobilewebservice', 'admin', $enablemobiledoclink), $default));
}
$ismobilewsdisabled = empty($CFG->enablemobilewebservice);
$ADMIN->add('root',
new admin_category('mobileapp', new lang_string('mobileapp', 'tool_mobile'), $ismobilewsdisabled),
'development'
);
$temp = new admin_settingpage('mobilesettings',
new lang_string('mobilesettings', 'tool_mobile'),
'moodle/site:config',
$ismobilewsdisabled
);
$temp->add(new admin_setting_configtext('tool_mobile/apppolicy', new lang_string('apppolicy', 'tool_mobile'),
new lang_string('apppolicy_help', 'tool_mobile'), '', PARAM_URL));
$ADMIN->add('mobileapp', $temp);
$featuresnotice = null;
if (empty($CFG->disablemobileappsubscription)) {
// General notification about limited features due to app restrictions.
$subscriptionurl = (new moodle_url("/$CFG->admin/tool/mobile/subscription.php"))->out(false);
$notify = new \core\output\notification(
get_string('moodleappsportalfeatureswarning', 'tool_mobile', $subscriptionurl),
\core\output\notification::NOTIFY_WARNING);
$featuresnotice = $OUTPUT->render($notify);
}
$hideappsubscription = (isset($CFG->disablemobileappsubscription) && !empty($CFG->disablemobileappsubscription));
$hideappsubscription = $ismobilewsdisabled || $hideappsubscription;
$ADMIN->add(
'mobileapp',
new admin_externalpage(
'mobileappsubscription',
new lang_string('mobileappsubscription', 'tool_mobile'),
"$CFG->wwwroot/$CFG->admin/tool/mobile/subscription.php",
'moodle/site:config',
$hideappsubscription
)
);
// Type of login.
$temp = new admin_settingpage(
'mobileauthentication',
new lang_string('mobileauthentication', 'tool_mobile'),
'moodle/site:config',
$ismobilewsdisabled
);
$temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeaturesauth', '', $featuresnotice));
$options = array(
tool_mobile\api::LOGIN_VIA_APP => new lang_string('loginintheapp', 'tool_mobile'),
tool_mobile\api::LOGIN_VIA_BROWSER => new lang_string('logininthebrowser', 'tool_mobile'),
tool_mobile\api::LOGIN_VIA_EMBEDDED_BROWSER => new lang_string('loginintheembeddedbrowser', 'tool_mobile'),
);
$temp->add(new admin_setting_configselect('tool_mobile/typeoflogin',
new lang_string('typeoflogin', 'tool_mobile'),
new lang_string('typeoflogin_desc', 'tool_mobile'), 1, $options));
$options = [
tool_mobile\api::AUTOLOGOUT_DISABLED => new lang_string('never'),
tool_mobile\api::AUTOLOGOUT_INMEDIATE => new lang_string('autologoutinmediate', 'tool_mobile'),
tool_mobile\api::AUTOLOGOUT_CUSTOM => new lang_string('autologoutcustom', 'tool_mobile'),
];
$temp->add(new admin_setting_configselect('tool_mobile/autologout',
new lang_string('autologout', 'tool_mobile'),
new lang_string('autologout_desc', 'tool_mobile'), 0, $options));
$temp->add(new admin_setting_configduration('tool_mobile/autologouttime',
new lang_string('autologouttime', 'tool_mobile'), '', DAYSECS));
$temp->hide_if('tool_mobile/autologouttime', 'tool_mobile/autologout', 'neq', tool_mobile\api::AUTOLOGOUT_CUSTOM);
$options = [
tool_mobile\api::QR_CODE_DISABLED => new lang_string('qrcodedisabled', 'tool_mobile'),
tool_mobile\api::QR_CODE_URL => new lang_string('qrcodetypeurl', 'tool_mobile'),
];
$qrcodetypedefault = tool_mobile\api::QR_CODE_URL;
if (is_https()) { // Allow QR login for https sites.
$options[tool_mobile\api::QR_CODE_LOGIN] = new lang_string('qrcodetypelogin', 'tool_mobile');
$qrcodetypedefault = tool_mobile\api::QR_CODE_LOGIN;
}
$temp->add(new admin_setting_configselect('tool_mobile/qrcodetype',
new lang_string('qrcodetype', 'tool_mobile'),
new lang_string('qrcodetype_desc', 'tool_mobile'), $qrcodetypedefault, $options));
$temp->add(new admin_setting_configduration('tool_mobile/qrkeyttl',
new lang_string('qrkeyttl', 'tool_mobile'),
new lang_string('qrkeyttl_desc', 'tool_mobile'), tool_mobile\api::LOGIN_QR_KEY_TTL, MINSECS));
$temp->hide_if('tool_mobile/qrkeyttl', 'tool_mobile/qrcodetype', 'neq', tool_mobile\api::QR_CODE_LOGIN);
$temp->add(new admin_setting_configcheckbox('tool_mobile/qrsameipcheck',
new lang_string('qrsameipcheck', 'tool_mobile'),
new lang_string('qrsameipcheck_desc', 'tool_mobile'), 1));
$temp->hide_if('tool_mobile/qrsameipcheck', 'tool_mobile/qrcodetype', 'neq', tool_mobile\api::QR_CODE_LOGIN);
$temp->add(new admin_setting_configtext('tool_mobile/forcedurlscheme',
new lang_string('forcedurlscheme_key', 'tool_mobile'),
new lang_string('forcedurlscheme', 'tool_mobile'), 'moodlemobile', PARAM_NOTAGS));
$temp->add(new admin_setting_configtext('tool_mobile/minimumversion',
new lang_string('minimumversion_key', 'tool_mobile'),
new lang_string('minimumversion', 'tool_mobile'), '', PARAM_NOTAGS));
$options = [
60 => new lang_string('numminutes', '', 1),
180 => new lang_string('numminutes', '', 3),
360 => new lang_string('numminutes', '', 6),
900 => new lang_string('numminutes', '', 15),
1800 => new lang_string('numminutes', '', 30),
3600 => new lang_string('numminutes', '', 60)
];
$temp->add(new admin_setting_configselect('tool_mobile/autologinmintimebetweenreq',
new lang_string('autologinmintimebetweenreq', 'tool_mobile'),
new lang_string('autologinmintimebetweenreq_desc', 'tool_mobile'), 360, $options));
$ADMIN->add('mobileapp', $temp);
// Appearance related settings.
$temp = new admin_settingpage(
'mobileappearance',
new lang_string('mobileappearance', 'tool_mobile'),
'moodle/site:config',
$ismobilewsdisabled
);
if (!empty($featuresnotice)) {
$temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeaturesappearance', '', $featuresnotice));
}
$temp->add(new admin_setting_configtext('mobilecssurl', new lang_string('mobilecssurl', 'tool_mobile'),
new lang_string('configmobilecssurl', 'tool_mobile'), '', PARAM_URL));
// Reference to Branded Mobile App.
if (empty($CFG->disableserviceads_branded)) {
$temp->add(new admin_setting_description('moodlebrandedappreference',
new lang_string('moodlebrandedapp', 'admin'),
new lang_string('moodlebrandedappreference', 'admin')
));
}
$temp->add(new admin_setting_heading('tool_mobile/smartappbanners',
new lang_string('smartappbanners', 'tool_mobile'), ''));
$temp->add(new admin_setting_configcheckbox('tool_mobile/enablesmartappbanners',
new lang_string('enablesmartappbanners', 'tool_mobile'),
new lang_string('enablesmartappbanners_desc', 'tool_mobile'), 0));
$temp->add(new admin_setting_configtext('tool_mobile/iosappid', new lang_string('iosappid', 'tool_mobile'),
new lang_string('iosappid_desc', 'tool_mobile'), tool_mobile\api::DEFAULT_IOS_APP_ID, PARAM_ALPHANUM));
$temp->add(new admin_setting_configtext('tool_mobile/androidappid', new lang_string('androidappid', 'tool_mobile'),
new lang_string('androidappid_desc', 'tool_mobile'), tool_mobile\api::DEFAULT_ANDROID_APP_ID, PARAM_NOTAGS));
$temp->add(new admin_setting_configtext('tool_mobile/setuplink', new lang_string('setuplink', 'tool_mobile'),
new lang_string('setuplink_desc', 'tool_mobile'), 'https://download.moodle.org/mobile', PARAM_URL));
$ADMIN->add('mobileapp', $temp);
// Features related settings.
$temp = new admin_settingpage(
'mobilefeatures',
new lang_string('mobilefeatures', 'tool_mobile'),
'moodle/site:config',
$ismobilewsdisabled
);
if (!empty($featuresnotice)) {
$temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeatures', '', $featuresnotice));
}
$temp->add(new admin_setting_heading('tool_mobile/logout',
new lang_string('logout'), ''));
$temp->add(new admin_setting_configcheckbox('tool_mobile/forcelogout',
new lang_string('forcelogout', 'tool_mobile'),
new lang_string('forcelogout_desc', 'tool_mobile'), 0));
$temp->add(new admin_setting_heading('tool_mobile/features',
new lang_string('mobilefeatures', 'tool_mobile'), ''));
$options = tool_mobile\api::get_features_list();
$temp->add(new admin_setting_configmultiselect('tool_mobile/disabledfeatures',
new lang_string('disabledfeatures', 'tool_mobile'),
new lang_string('disabledfeatures_desc', 'tool_mobile'), array(), $options));
$temp->add(new admin_setting_configtextarea('tool_mobile/custommenuitems',
new lang_string('custommenuitems', 'tool_mobile'),
new lang_string('custommenuitems_desc', 'tool_mobile'), '', PARAM_RAW, '50', '10'));
// File type exclusionlist.
$choices = [];
foreach (core_filetypes::get_types() as $key => $info) {
$text = '.' . $key;
if (!empty($info['type'])) {
$text .= ' (' . $info['type'] . ')';
}
$choices[$key] = $text;
}
$attributes = [
'manageurl' => new \moodle_url('/admin/tool/filetypes/index.php'),
'managetext' => get_string('managefiletypes', 'tool_mobile'),
'multiple' => true,
'delimiter' => ',',
'placeholder' => get_string('filetypeexclusionlistplaceholder', 'tool_mobile')
];
$temp->add(new autocomplete('tool_mobile/filetypeexclusionlist',
new lang_string('filetypeexclusionlist', 'tool_mobile'),
new lang_string('filetypeexclusionlist_desc', 'tool_mobile'), array(), $choices, $attributes));
$temp->add(new admin_setting_heading('tool_mobile/language',
new lang_string('language'), ''));
$temp->add(new admin_setting_configtextarea('tool_mobile/customlangstrings',
new lang_string('customlangstrings', 'tool_mobile'),
new lang_string('customlangstrings_desc', 'tool_mobile'), '', PARAM_RAW, '50', '10'));
$ADMIN->add('mobileapp', $temp);
}
+32
View File
@@ -0,0 +1,32 @@
/**
* Styles for admin tool mobile.
*/
#page-admin-tool-mobile-subscription dl dt {
clear: both;
display: inline-block;
width: 40%;
min-width: 100px;
vertical-align: top;
padding-top: 1px;
}
#page-admin-tool-mobile-subscription dl dd {
display: inline-block;
width: 59%;
margin-left: 1%;
vertical-align: top;
padding-top: 1px;
}
#page-admin-tool-mobile-subscription dl.list-narrow dt {
width: 30%;
}
#page-admin-tool-mobile-subscription dl.list-narrow dd {
width: 69%;
}
#page-admin-tool-mobile-subscription progress {
width: 100%;
}
+51
View File
@@ -0,0 +1,51 @@
<?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/>.
/**
* Moodle app subscription information for the current site.
*
* @package tool_mobile
* @copyright 2020 Moodle Pty Ltd
* @author <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->libdir . '/adminlib.php');
admin_externalpage_setup('mobileappsubscription', '', null, '');
// Check Mobile web services enabled. This page should not be linked in that case, but avoid just in case.
if (!$CFG->enablemobilewebservice) {
throw new \moodle_exception('enablewsdescription', 'webservice');
}
// Check is this feature is globaly disabled.
if (!empty($CFG->disablemobileappsubscription)) {
throw new \moodle_exception('disabled', 'admin');
}
$subscriptiondata = \tool_mobile\api::get_subscription_information();
echo $OUTPUT->header();
if (empty($subscriptiondata)) {
echo $OUTPUT->notification(get_string('subscriptionerrorrequest', 'tool_mobile'), \core\output\notification::NOTIFY_ERROR);
} else {
$templatable = new \tool_mobile\output\subscription($subscriptiondata);
echo $PAGE->get_renderer('tool_mobile')->render($templatable);
}
echo $OUTPUT->footer();
@@ -0,0 +1,229 @@
{{!
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 tool_mobile/subscription
Template for subscription information.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* registered - Whether the site is registered
* appsportalurl - Apps portal url
Example context (json):
{
"registered" : true,
"appsportalurl": "https://apps.moodle.com",
"subscription": {
"name": "Pro",
"description": "This subscription has a Moodle Product Premium plan free of charge",
"timecreated": 1587548810,
"expiretime": 1618963200,
"features": [
{
"name": "multimediapushnotifications",
"enabled": true,
"description": "Multimedia push notifications",
"humanstatus": "Enabled",
"message": {
"type" : "warning",
"message" : "Temporary disabled for a promotion"
}
},
{
"name": "pushnotificationsdevices",
"enabled": false,
"limit": 50,
"showbar": 1,
"description": "Active user devices for notifications",
"status": 55,
"humanstatus": "55/50",
"barclass": "bg-danger"
},
{
"name": "custommenuitems",
"enabled": false,
"limit": 4,
"showbar": 1,
"description": "Custom menu items",
"status": 2,
"humanstatus": "2/4"
}
]
},
"messageswarning": [
{
"message" : "You have surpassed your monthly active user devices limit, some messages are beign ignored. We recommend you to upgrade to a paid plan."
}
],
"notifications": {
"totalsentnotifications" : 7600,
"totaldevices" : 60,
"currentactivedevices" : 55,
"ignorednotificationswarning": {
"message" : "You have surpassed your monthly active user devices limit, some messages are beign ignored. We recommend you to upgrade to a paid plan."
},
"monthly" : [
{
"year": 2020,
"month": 4,
"sentnotifications": 4500,
"newdevices": 20,
"activedevices": 55,
"ignorednotifications": 40,
"limitreachedtime": 1586548810
},
{
"year": 2020,
"month": 3,
"sentnotifications": 4500,
"newdevices":10,
"activedevices": 45,
"ignorednotifications": 0,
"limitreachedtime": 0
}
]
}
}
}}
{{#messageserror}}
{{> core/notification_error}}
{{/messageserror}}
{{#messagessuccess}}
{{> core/notification_success}}
{{/messagessuccess}}
{{#messageswarning}}
{{> core/notification_warning}}
{{/messageswarning}}
{{#messagesinfo}}
{{> core/notification_info}}
{{/messagesinfo}}
<div id="subscription-overview" class="box">
<h2>{{# str }} mobileappsubscription, tool_mobile {{/ str }}</h2>
{{#messageshtml}}
{{{message}}}
{{/messageshtml}}
{{#subscription}}
<dl class="list-narrow">
<dt>{{# str }} name {{/ str }}</dt><dd>{{name}}</dd>
<dt>{{# str }} description {{/ str }}</dt><dd>{{description}}</dd>
<dt>{{# str }} subscriptioncreated, tool_mobile {{/ str }}</dt><dd>{{#userdate}} {{timecreated}}, {{#str}} strftimedate {{/str}} {{/userdate}}</dd>
{{#expiretime}}
<dt>{{# str }} subscriptionexpiration, tool_mobile {{/ str }}</dt><dd>{{#userdate}} {{expiretime}}, {{#str}} strftimedate {{/str}} {{/userdate}}</dd>
{{/expiretime}}
</dl>
{{^registered}}
{{# str }} subscriptionregister, tool_mobile, {{ appsportalurl }} {{/ str }}
{{/registered}}
<h3>{{# str }} subscriptionfeatures, tool_mobile {{/ str }}</h3>
{{#features}}
<dl>
{{^limit}}
<dt>{{{description}}}</dt><dd>{{{humanstatus}}}</dd>
{{/limit}}
{{#limit}}
<dt>{{{description}}}</dt><dd>
{{#showbar}}
<div class="progress">
<div class="progress-bar progress-bar-animated {{barclass}}" role="progressbar" style="width: 100%" aria-valuenow="{{status}}" aria-valuemin="0" aria-valuemax="{{limit}}">{{humanstatus}}
</div>
</div>
{{/showbar}}
{{^showbar}}
{{humanstatus}}
{{/showbar}}
</dd>
{{/limit}}
{{#message}}
<span class="badge badge-{{type}}">{{message}}</span>
{{/message}}
</dl>
{{/features}}
{{#registered}}
{{# str }} subscriptionsseemore, tool_mobile, {{ appsportalurl }} {{/ str }}
{{/registered}}
{{/subscription}}
</div>
<div id="notifications-overview" class="box">
<h3>{{# str }} notifications, tool_mobile {{/ str }}</h3>
{{^registered}}
{{# str }} subscriptionregister, tool_mobile, {{ appsportalurl }} {{/ str }}
{{/registered}}
{{#notifications}}
{{#ignorednotificationswarning}}
{{> core/notification_error}}
{{/ignorednotificationswarning}}
<dl>
<dt>{{# str }} notificationscurrentactivedevices, tool_mobile {{/ str }}</dt><dd>{{currentactivedevices}}</dd>
</dl>
<table id="notificationstable" class="generaltable fullwidth">
<thead>
<tr>
<th class="text-center" scope="col">{{#str}}year, form{{/str}}</th>
<th class="text-center" scope="col">{{#str}}month{{/str}}</th>
<th class="text-center" scope="col">{{#str}}notificationssentnotifications, tool_mobile{{/str}}</th>
<th class="text-center" scope="col">{{#str}}notificationsactivedevices, tool_mobile{{/str}}</th>
<th class="text-center" scope="col">{{#str}}notificationsnewdevices, tool_mobile{{/str}}</th>
<th class="text-center" scope="col">{{#str}}notificationsignorednotifications, tool_mobile{{/str}}</th>
</tr>
</thead>
<tbody>
{{#notifications.monthly}}
<tr>
<td class="text-center">{{year}}</td>
<td class="text-center">{{month}}</td>
<td class="text-center">{{sentnotifications}}</td>
<td class="text-center">{{activedevices}}</td>
<td class="text-center">{{newdevices}}</td>
<td class="text-center">{{ignorednotifications}}</td>
</tr>
{{/notifications.monthly}}
</tbody>
</table>
{{#registered}}
{{# str }} notificationsseemore, tool_mobile, {{ appsportalurl }} {{/ str }}
{{/registered}}
{{/notifications}}
{{^notifications}}
{{# str }} notificationsmissingwarning, tool_mobile {{/ str }}
{{/notifications}}
</div>
+159
View File
@@ -0,0 +1,159 @@
<?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/>.
namespace tool_mobile;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
/**
* Moodle Mobile admin tool api tests.
*
* @package tool_mobile
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.1
*/
class api_test extends \externallib_advanced_testcase {
/**
* Test get_autologin_key.
*/
public function test_get_autologin_key(): void {
global $USER, $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
// Set server timezone for test.
$this->setTimezone('UTC');
// SEt user to GMT+5.
$USER->timezone = 5;
$timenow = $this->setCurrentTimeStart();
$key = api::get_autologin_key();
$key = $DB->get_record('user_private_key', array('value' => $key), '*', MUST_EXIST);
$this->assertTimeCurrent($key->validuntil - api::LOGIN_KEY_TTL);
$this->assertEquals('0.0.0.0', $key->iprestriction);
}
/**
* Test get_potential_config_issues.
*/
public function test_get_potential_config_issues(): void {
global $CFG;
$this->resetAfterTest(true);
$this->setAdminUser();
// Set non-SSL wwwroot, to avoid spurious certificate checking.
$CFG->wwwroot = 'http://www.example.com';
$CFG->debugdisplay = 1;
set_config('debugauthdb', 1, 'auth_db');
set_config('debugdb', 1, 'enrol_database');
// Get potential issues, obtain their keys for comparison.
$issues = api::get_potential_config_issues();
$issuekeys = array_column($issues, 0);
$this->assertEqualsCanonicalizing([
'nohttpsformobilewarning',
'adodbdebugwarning',
'displayerrorswarning',
], $issuekeys);
}
/**
* Test pre_processor_message_send callback.
*/
public function test_pre_processor_message_send_callback(): void {
global $DB, $CFG;
$this->preventResetByRollback();
$this->resetAfterTest();
// Enable mobile services and required configuration.
$CFG->enablewebservices = 1;
$CFG->enablemobilewebservice = 1;
$mobileappdownloadpage = 'htt://mobileappdownloadpage';
set_config('setuplink', $mobileappdownloadpage, 'tool_mobile');
$user1 = $this->getDataGenerator()->create_user(array('maildisplay' => 1));
$user2 = $this->getDataGenerator()->create_user();
set_config('allowedemaildomains', 'example.com');
$DB->set_field_select('message_processors', 'enabled', 0, "name <> 'email'");
set_user_preference('message_provider_moodle_instantmessage_enabled', 'email', $user2);
// Extra content for all types of messages.
$message = new \core\message\message();
$message->courseid = 1;
$message->component = 'moodle';
$message->name = 'instantmessage';
$message->userfrom = $user1;
$message->userto = $user2;
$message->subject = 'message subject 1';
$message->fullmessage = 'message body';
$message->fullmessageformat = FORMAT_MARKDOWN;
$message->fullmessagehtml = '<p>message body</p>';
$message->smallmessage = 'small message';
$message->notification = '0';
$content = array('*' => array('header' => ' test ', 'footer' => ' test '));
$message->set_additional_content('email', $content);
$sink = $this->redirectEmails();
$messageid = message_send($message);
$emails = $sink->get_messages();
$this->assertCount(1, $emails);
$email = reset($emails);
// Check we got the promotion text.
$this->assertStringContainsString($mobileappdownloadpage, quoted_printable_decode($email->body));
$sink->clear();
// Disable mobile so we don't get mobile promotions.
$CFG->enablemobilewebservice = 0;
$messageid = message_send($message);
$emails = $sink->get_messages();
$this->assertCount(1, $emails);
$email = reset($emails);
// Check we don't get the promotion text.
$this->assertStringNotContainsString($mobileappdownloadpage, quoted_printable_decode($email->body));
$sink->clear();
// Enable mobile again and set current user mobile token so we don't get mobile promotions.
$CFG->enablemobilewebservice = 1;
$user3 = $this->getDataGenerator()->create_user();
$this->setUser($user3);
$service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
$token = \core_external\util::generate_token_for_current_user($service);
$message->userto = $user3;
$messageid = message_send($message);
$emails = $sink->get_messages();
$this->assertCount(1, $emails);
$email = reset($emails);
// Check we don't get the promotion text.
$this->assertStringNotContainsString($mobileappdownloadpage, quoted_printable_decode($email->body));
$sink->clear();
$sink->close();
}
}
@@ -0,0 +1,929 @@
<?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/>.
namespace tool_mobile;
use externallib_advanced_testcase;
use core_external\external_api;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
require_once($CFG->dirroot . '/admin/tool/mobile/tests/fixtures/output/mobile.php');
require_once($CFG->dirroot . '/webservice/lib.php');
/**
* Moodle Mobile admin tool external functions tests.
*
* @package tool_mobile
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.1
*/
class externallib_test extends externallib_advanced_testcase {
/**
* Test get_plugins_supporting_mobile.
* This is a very basic test because currently there aren't plugins supporting Mobile in core.
*/
public function test_get_plugins_supporting_mobile(): void {
$result = external::get_plugins_supporting_mobile();
$result = external_api::clean_returnvalue(external::get_plugins_supporting_mobile_returns(), $result);
$this->assertCount(0, $result['warnings']);
$this->assertArrayHasKey('plugins', $result);
$this->assertTrue(is_array($result['plugins']));
}
public function test_get_public_config(): void {
global $CFG, $SITE, $OUTPUT;
$this->resetAfterTest(true);
$result = external::get_public_config();
$result = external_api::clean_returnvalue(external::get_public_config_returns(), $result);
// Test default values.
$context = \context_system::instance();
[$authinstructions] = \core_external\util::format_text(
$CFG->auth_instructions,
FORMAT_MOODLE,
$context->id
);
[$maintenancemessage] = \core_external\util::format_text(
$CFG->maintenance_message,
FORMAT_MOODLE,
$context->id
);
$expected = array(
'wwwroot' => $CFG->wwwroot,
'httpswwwroot' => $CFG->wwwroot,
'sitename' => \core_external\util::format_string($SITE->fullname, $context->id, true),
'guestlogin' => $CFG->guestloginbutton,
'rememberusername' => $CFG->rememberusername,
'authloginviaemail' => $CFG->authloginviaemail,
'registerauth' => $CFG->registerauth,
'forgottenpasswordurl' => $CFG->forgottenpasswordurl,
'authinstructions' => $authinstructions,
'authnoneenabled' => (int) is_enabled_auth('none'),
'enablewebservices' => $CFG->enablewebservices,
'enablemobilewebservice' => $CFG->enablemobilewebservice,
'maintenanceenabled' => $CFG->maintenance_enabled,
'maintenancemessage' => $maintenancemessage,
'typeoflogin' => api::LOGIN_VIA_APP,
'mobilecssurl' => '',
'tool_mobile_disabledfeatures' => '',
'launchurl' => "$CFG->wwwroot/$CFG->admin/tool/mobile/launch.php",
'country' => $CFG->country,
'agedigitalconsentverification' => \core_auth\digital_consent::is_age_digital_consent_verification_enabled(),
'autolang' => $CFG->autolang,
'lang' => $CFG->lang,
'langmenu' => $CFG->langmenu,
'langlist' => $CFG->langlist,
'locale' => $CFG->locale,
'tool_mobile_minimumversion' => '',
'tool_mobile_iosappid' => get_config('tool_mobile', 'iosappid'),
'tool_mobile_androidappid' => get_config('tool_mobile', 'androidappid'),
'tool_mobile_setuplink' => get_config('tool_mobile', 'setuplink'),
'tool_mobile_qrcodetype' => get_config('tool_mobile', 'qrcodetype'),
'supportpage' => $CFG->supportpage,
'supportavailability' => $CFG->supportavailability,
'warnings' => array()
);
$this->assertEquals($expected, $result);
$this->setAdminUser();
// Change some values.
set_config('registerauth', 'email');
$authinstructions = 'Something with <b>html tags</b>';
set_config('auth_instructions', $authinstructions);
set_config('typeoflogin', api::LOGIN_VIA_BROWSER, 'tool_mobile');
set_config('logo', 'mock.png', 'core_admin');
set_config('logocompact', 'mock.png', 'core_admin');
set_config('forgottenpasswordurl', 'mailto:fake@email.zy'); // Test old hack.
set_config('agedigitalconsentverification', 1);
set_config('autolang', 1);
set_config('lang', 'a_b'); // Set invalid lang.
set_config('disabledfeatures', 'myoverview', 'tool_mobile');
set_config('minimumversion', '3.8.0', 'tool_mobile');
set_config('supportemail', 'test@test.com');
set_config('supportavailability', CONTACT_SUPPORT_ANYONE);
// Enable couple of issuers.
$issuer = \core\oauth2\api::create_standard_issuer('google');
$irecord = $issuer->to_record();
$irecord->clientid = 'mock';
$irecord->clientsecret = 'mock';
\core\oauth2\api::update_issuer($irecord);
set_config('hostname', 'localhost', 'auth_cas');
set_config('auth_logo', 'http://invalidurl.com//invalid/', 'auth_cas');
set_config('auth_name', 'CAS', 'auth_cas');
set_config('auth', 'oauth2,cas');
list($authinstructions, $notusedformat) = \core_external\util::format_text($authinstructions, FORMAT_MOODLE, $context->id);
$expected['registerauth'] = 'email';
$expected['authinstructions'] = $authinstructions;
$expected['typeoflogin'] = api::LOGIN_VIA_BROWSER;
$expected['forgottenpasswordurl'] = ''; // Expect empty when it's not an URL.
$expected['agedigitalconsentverification'] = true;
$expected['supportname'] = $CFG->supportname;
$expected['supportemail'] = $CFG->supportemail;
$expected['supportavailability'] = $CFG->supportavailability;
$expected['autolang'] = '1';
$expected['lang'] = ''; // Expect empty because it was set to an invalid lang.
$expected['tool_mobile_disabledfeatures'] = 'myoverview';
$expected['tool_mobile_minimumversion'] = '3.8.0';
if ($logourl = $OUTPUT->get_logo_url()) {
$expected['logourl'] = $logourl->out(false);
}
if ($compactlogourl = $OUTPUT->get_compact_logo_url()) {
$expected['compactlogourl'] = $compactlogourl->out(false);
}
$result = external::get_public_config();
$result = external_api::clean_returnvalue(external::get_public_config_returns(), $result);
// First check providers.
$identityproviders = $result['identityproviders'];
unset($result['identityproviders']);
$this->assertEquals('Google', $identityproviders[0]['name']);
$this->assertEquals($irecord->image, $identityproviders[0]['iconurl']);
$this->assertStringContainsString($CFG->wwwroot, $identityproviders[0]['url']);
$this->assertEquals('CAS', $identityproviders[1]['name']);
$this->assertEmpty($identityproviders[1]['iconurl']);
$this->assertStringContainsString($CFG->wwwroot, $identityproviders[1]['url']);
$this->assertEquals($expected, $result);
// Change providers img.
$newurl = 'validimage.png';
set_config('auth_logo', $newurl, 'auth_cas');
$result = external::get_public_config();
$result = external_api::clean_returnvalue(external::get_public_config_returns(), $result);
$this->assertStringContainsString($newurl, $result['identityproviders'][1]['iconurl']);
}
/**
* Test get_config
*
* @covers \tool_mobile\external::get_config
*/
public function test_get_config(): void {
global $CFG, $SITE;
require_once($CFG->dirroot . '/course/format/lib.php');
$this->resetAfterTest(true);
$mysitepolicy = 'http://mysite.is/policy/';
set_config('sitepolicy', $mysitepolicy);
set_config('supportemail', 'test@test.com');
$result = external::get_config();
$result = external_api::clean_returnvalue(external::get_config_returns(), $result);
// SITE summary is null in phpunit which gets transformed to an empty string by format_text.
[$sitesummary, $summaryformat] = \core_external\util::format_text(
$SITE->summary,
$SITE->summaryformat,
\context_system::instance()->id
);
// Test default values.
$context = \context_system::instance();
$expected = array(
array('name' => 'fullname', 'value' => $SITE->fullname),
array('name' => 'shortname', 'value' => $SITE->shortname),
array('name' => 'summary', 'value' => $sitesummary),
array('name' => 'summaryformat', 'value' => $summaryformat),
array('name' => 'frontpage', 'value' => $CFG->frontpage),
array('name' => 'frontpageloggedin', 'value' => $CFG->frontpageloggedin),
array('name' => 'maxcategorydepth', 'value' => $CFG->maxcategorydepth),
array('name' => 'frontpagecourselimit', 'value' => $CFG->frontpagecourselimit),
array('name' => 'numsections', 'value' => course_get_format($SITE)->get_last_section_number()),
array('name' => 'newsitems', 'value' => $SITE->newsitems),
array('name' => 'commentsperpage', 'value' => $CFG->commentsperpage),
array('name' => 'sitepolicy', 'value' => $mysitepolicy),
array('name' => 'sitepolicyhandler', 'value' => ''),
array('name' => 'disableuserimages', 'value' => $CFG->disableuserimages),
array('name' => 'mygradesurl', 'value' => user_mygrades_url()->out(false)),
array('name' => 'tool_mobile_forcelogout', 'value' => 0),
array('name' => 'tool_mobile_customlangstrings', 'value' => ''),
array('name' => 'tool_mobile_disabledfeatures', 'value' => ''),
array('name' => 'tool_mobile_filetypeexclusionlist', 'value' => ''),
array('name' => 'tool_mobile_custommenuitems', 'value' => ''),
array('name' => 'tool_mobile_apppolicy', 'value' => ''),
array('name' => 'tool_mobile_autologinmintimebetweenreq', 'value' => 6 * MINSECS),
array('name' => 'tool_mobile_autologout', 'value' => get_config('tool_mobile', 'autologout')),
array('name' => 'tool_mobile_autologouttime', 'value' => get_config('tool_mobile', 'autologouttime')),
array('name' => 'calendartype', 'value' => $CFG->calendartype),
array('name' => 'calendar_site_timeformat', 'value' => $CFG->calendar_site_timeformat),
array('name' => 'calendar_startwday', 'value' => $CFG->calendar_startwday),
array('name' => 'calendar_adminseesall', 'value' => $CFG->calendar_adminseesall),
array('name' => 'calendar_lookahead', 'value' => $CFG->calendar_lookahead),
array('name' => 'calendar_maxevents', 'value' => $CFG->calendar_maxevents),
);
$colornumbers = range(1, 10);
foreach ($colornumbers as $number) {
$expected[] = [
'name' => 'core_admin_coursecolor' . $number,
'value' => get_config('core_admin', 'coursecolor' . $number)
];
}
$expected[] = ['name' => 'supportavailability', 'value' => $CFG->supportavailability];
$expected[] = ['name' => 'supportname', 'value' => $CFG->supportname];
$expected[] = ['name' => 'supportemail', 'value' => $CFG->supportemail];
$expected[] = ['name' => 'supportpage', 'value' => $CFG->supportpage];
$expected[] = ['name' => 'coursegraceperiodafter', 'value' => $CFG->coursegraceperiodafter];
$expected[] = ['name' => 'coursegraceperiodbefore', 'value' => $CFG->coursegraceperiodbefore];
$expected[] = ['name' => 'enabledashboard', 'value' => $CFG->enabledashboard];
$expected[] = ['name' => 'customusermenuitems', 'value' => $CFG->customusermenuitems];
$expected[] = ['name' => 'timezone', 'value' => $CFG->timezone];
$expected[] = ['name' => 'forcetimezone', 'value' => $CFG->forcetimezone];
$expected[] = ['name' => 'searchengine', 'value' => $CFG->searchengine];
$expected[] = ['name' => 'searchenablecategories', 'value' => $CFG->searchenablecategories];
$expected[] = ['name' => 'searchdefaultcategory', 'value' => $CFG->searchdefaultcategory];
$expected[] = ['name' => 'searchhideallcategory', 'value' => $CFG->searchhideallcategory];
$expected[] = ['name' => 'searchmaxtopresults', 'value' => $CFG->searchmaxtopresults];
$expected[] = ['name' => 'searchbannerenable', 'value' => $CFG->searchbannerenable];
$expected[] = ['name' => 'searchbanner', 'value' => $CFG->searchbanner];
$expected[] = ['name' => 'tool_dataprivacy_contactdataprotectionofficer', 'value' => get_config('tool_dataprivacy', 'contactdataprotectionofficer')];
$expected[] = ['name' => 'tool_dataprivacy_showdataretentionsummary', 'value' => get_config('tool_dataprivacy', 'showdataretentionsummary')];
$expected[] = ['name' => 'useblogassociations', 'value' => $CFG->useblogassociations];
$expected[] = ['name' => 'bloglevel', 'value' => $CFG->bloglevel];
$expected[] = ['name' => 'blogusecomments', 'value' => $CFG->blogusecomments];
$this->assertCount(0, $result['warnings']);
$this->assertEquals($expected, $result['settings']);
// H5P custom CSS.
set_config('h5pcustomcss', '.debug { color: #fab; }', 'core_h5p');
\core_h5p\local\library\autoloader::register();
\core_h5p\file_storage::generate_custom_styles();
$result = external::get_config();
$result = external_api::clean_returnvalue(external::get_config_returns(), $result);
$customcss = \core_h5p\file_storage::get_custom_styles();
$expected[] = ['name' => 'h5pcustomcssurl', 'value' => $customcss['cssurl']->out() . '?ver=' . $customcss['cssversion']];
$this->assertCount(0, $result['warnings']);
$this->assertEquals($expected, $result['settings']);
// Change a value and retrieve filtering by section.
set_config('commentsperpage', 1);
$expected[10]['value'] = 1;
// Remove not expected elements.
array_splice($expected, 11);
$result = external::get_config('frontpagesettings');
$result = external_api::clean_returnvalue(external::get_config_returns(), $result);
$this->assertCount(0, $result['warnings']);
$this->assertEquals($expected, $result['settings']);
}
/*
* Test get_autologin_key.
*/
public function test_get_autologin_key(): void {
global $DB, $CFG, $USER;
$this->resetAfterTest(true);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
$token = \core_external\util::generate_token_for_current_user($service);
// Check we got the private token.
$this->assertTrue(isset($token->privatetoken));
// Enable requeriments.
$_GET['wstoken'] = $token->token; // Mock parameters.
// Fake the app.
\core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
// Even if we force the password change for the current user we should be able to retrieve the key.
set_user_preference('auth_forcepasswordchange', 1, $user->id);
$this->setCurrentTimeStart();
$result = external::get_autologin_key($token->privatetoken);
$result = external_api::clean_returnvalue(external::get_autologin_key_returns(), $result);
// Validate the key.
$this->assertEquals(32, \core_text::strlen($result['key']));
$key = $DB->get_record('user_private_key', array('value' => $result['key']));
$this->assertEquals($USER->id, $key->userid);
$this->assertTimeCurrent($key->validuntil - api::LOGIN_KEY_TTL);
// Now, try with an invalid private token.
set_user_preference('tool_mobile_autologin_request_last', time() - HOURSECS, $USER);
$this->expectException('moodle_exception');
$this->expectExceptionMessage(get_string('invalidprivatetoken', 'tool_mobile'));
$result = external::get_autologin_key(random_string('64'));
}
/**
* Test get_autologin_key missing ws.
*/
public function test_get_autologin_key_missing_ws(): void {
global $CFG;
$this->resetAfterTest(true);
// Fake the app.
\core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
// Need to disable webservices to verify that's checked.
$CFG->enablewebservices = 0;
$CFG->enablemobilewebservice = 0;
$this->setAdminUser();
$this->expectException('moodle_exception');
$this->expectExceptionMessage(get_string('enablewsdescription', 'webservice'));
$result = external::get_autologin_key('');
}
/**
* Test get_autologin_key missing https.
*/
public function test_get_autologin_key_missing_https(): void {
global $CFG;
// Fake the app.
\core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
// Need to simulate a non HTTPS site here.
$CFG->wwwroot = str_replace('https:', 'http:', $CFG->wwwroot);
$this->resetAfterTest(true);
$this->setAdminUser();
$this->expectException('moodle_exception');
$this->expectExceptionMessage(get_string('httpsrequired', 'tool_mobile'));
$result = external::get_autologin_key('');
}
/**
* Test get_autologin_key missing admin.
*/
public function test_get_autologin_key_missing_admin(): void {
global $CFG;
$this->resetAfterTest(true);
$this->setAdminUser();
// Fake the app.
\core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
$this->expectException('moodle_exception');
$this->expectExceptionMessage(get_string('autologinnotallowedtoadmins', 'tool_mobile'));
$result = external::get_autologin_key('');
}
/**
* Test get_autologin_key locked.
*/
public function test_get_autologin_key_missing_locked(): void {
global $CFG, $DB, $USER;
$this->resetAfterTest(true);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
$token = \core_external\util::generate_token_for_current_user($service);
$_GET['wstoken'] = $token->token; // Mock parameters.
// Fake the app.
\core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
$result = external::get_autologin_key($token->privatetoken);
$result = external_api::clean_returnvalue(external::get_autologin_key_returns(), $result);
// Mock last time request.
$mocktime = time() - 7 * MINSECS;
set_user_preference('tool_mobile_autologin_request_last', $mocktime, $USER);
$result = external::get_autologin_key($token->privatetoken);
$result = external_api::clean_returnvalue(external::get_autologin_key_returns(), $result);
// Change min time between requests to 3 minutes.
set_config('autologinmintimebetweenreq', 3 * MINSECS, 'tool_mobile');
// Mock a previous request, 4 minutes ago.
$mocktime = time() - (4 * MINSECS);
set_user_preference('tool_mobile_autologin_request_last', $mocktime, $USER);
$result = external::get_autologin_key($token->privatetoken);
$result = external_api::clean_returnvalue(external::get_autologin_key_returns(), $result);
// We just requested one token, we must wait.
$this->expectException('moodle_exception');
$this->expectExceptionMessage(get_string('autologinkeygenerationlockout', 'tool_mobile', 3));
$result = external::get_autologin_key($token->privatetoken);
}
/**
* Test get_autologin_key missing app_request.
*/
public function test_get_autologin_key_missing_app_request(): void {
global $CFG;
$this->resetAfterTest(true);
$this->setAdminUser();
$this->expectException('moodle_exception');
$this->expectExceptionMessage(get_string('apprequired', 'tool_mobile'));
$result = external::get_autologin_key('');
}
/**
* Test get_content.
*/
public function test_get_content(): void {
$paramval = 16;
$result = external::get_content('tool_mobile', 'test_view', array(array('name' => 'param1', 'value' => $paramval)));
$result = external_api::clean_returnvalue(external::get_content_returns(), $result);
$this->assertCount(1, $result['templates']);
$this->assertCount(1, $result['otherdata']);
$this->assertCount(2, $result['restrict']['users']);
$this->assertCount(2, $result['restrict']['courses']);
$this->assertEquals('alert();', $result['javascript']);
$this->assertEquals('main', $result['templates'][0]['id']);
$this->assertEquals('The HTML code', $result['templates'][0]['html']);
$this->assertEquals('otherdata1', $result['otherdata'][0]['name']);
$this->assertEquals($paramval, $result['otherdata'][0]['value']);
$this->assertEquals(array(1, 2), $result['restrict']['users']);
$this->assertEquals(array(3, 4), $result['restrict']['courses']);
$this->assertEmpty($result['files']);
$this->assertFalse($result['disabled']);
}
/**
* Test get_content disabled.
*/
public function test_get_content_disabled(): void {
$paramval = 16;
$result = external::get_content('tool_mobile', 'test_view_disabled',
array(array('name' => 'param1', 'value' => $paramval)));
$result = external_api::clean_returnvalue(external::get_content_returns(), $result);
$this->assertTrue($result['disabled']);
}
/**
* Test get_content non existent function in valid component.
*/
public function test_get_content_non_existent_function(): void {
$this->expectException('coding_exception');
$result = external::get_content('tool_mobile', 'test_blahblah');
}
/**
* Test get_content incorrect component.
*/
public function test_get_content_invalid_component(): void {
$this->expectException('moodle_exception');
$result = external::get_content('tool_mobile\hack', 'test_view');
}
/**
* Test get_content non existent component.
*/
public function test_get_content_non_existent_component(): void {
$this->expectException('moodle_exception');
$result = external::get_content('tool_blahblahblah', 'test_view');
}
public function test_call_external_functions(): void {
global $SESSION;
$this->resetAfterTest(true);
$category = self::getDataGenerator()->create_category(array('name' => 'Category 1'));
$course = self::getDataGenerator()->create_course([
'category' => $category->id,
'shortname' => 'c1',
'summary' => '<span lang="en" class="multilang">Course summary</span>'
. '<span lang="eo" class="multilang">Kurso resumo</span>'
. '@@PLUGINFILE@@/filename.txt'
. '<!-- Comment stripped when formatting text -->',
'summaryformat' => FORMAT_MOODLE
]);
$user1 = self::getDataGenerator()->create_user(['username' => 'user1', 'lastaccess' => time()]);
$user2 = self::getDataGenerator()->create_user(['username' => 'user2', 'lastaccess' => time()]);
self::setUser($user1);
// Setup WS token.
$webservicemanager = new \webservice;
$service = $webservicemanager->get_external_service_by_shortname(MOODLE_OFFICIAL_MOBILE_SERVICE);
$token = \core_external\util::generate_token_for_current_user($service);
$_POST['wstoken'] = $token->token;
// Workaround for external_api::call_external_function requiring sesskey.
$_POST['sesskey'] = sesskey();
// Call some functions.
$requests = [
[
'function' => 'core_course_get_courses_by_field',
'arguments' => json_encode(['field' => 'id', 'value' => $course->id])
],
[
'function' => 'core_user_get_users_by_field',
'arguments' => json_encode(['field' => 'id', 'values' => [$user1->id]])
],
[
'function' => 'core_user_get_user_preferences',
'arguments' => json_encode(['name' => 'some_setting', 'userid' => $user2->id])
],
[
'function' => 'core_course_get_courses_by_field',
'arguments' => json_encode(['field' => 'shortname', 'value' => $course->shortname])
],
];
$result = external::call_external_functions($requests);
// We need to execute the return values cleaning process to simulate the web service server.
$result = external_api::clean_returnvalue(external::call_external_functions_returns(), $result);
// Only 3 responses, the 4th request is not executed because the 3rd throws an exception.
$this->assertCount(3, $result['responses']);
$this->assertFalse($result['responses'][0]['error']);
$coursedata = external_api::clean_returnvalue(
\core_course_external::get_courses_by_field_returns(),
\core_course_external::get_courses_by_field('id', $course->id));
$this->assertEquals(json_encode($coursedata), $result['responses'][0]['data']);
$this->assertFalse($result['responses'][1]['error']);
$userdata = external_api::clean_returnvalue(
\core_user_external::get_users_by_field_returns(),
\core_user_external::get_users_by_field('id', [$user1->id]));
$this->assertEquals(json_encode($userdata), $result['responses'][1]['data']);
$this->assertTrue($result['responses'][2]['error']);
$exception = json_decode($result['responses'][2]['exception'], true);
$this->assertEquals('nopermissions', $exception['errorcode']);
// Call a function not included in the external service.
$_POST['wstoken'] = $token->token;
$functions = $webservicemanager->get_not_associated_external_functions($service->id);
$requests = [['function' => current($functions)->name]];
$result = external::call_external_functions($requests);
$this->assertTrue($result['responses'][0]['error']);
$exception = json_decode($result['responses'][0]['exception'], true);
$this->assertEquals('accessexception', $exception['errorcode']);
$this->assertEquals('webservice', $exception['module']);
// Call a function with different external settings.
filter_set_global_state('multilang', TEXTFILTER_ON);
$_POST['wstoken'] = $token->token;
$SESSION->lang = 'eo'; // Change default language, so we can test changing it to "en".
$requests = [
[
'function' => 'core_course_get_courses_by_field',
'arguments' => json_encode(['field' => 'id', 'value' => $course->id]),
],
[
'function' => 'core_course_get_courses_by_field',
'arguments' => json_encode(['field' => 'id', 'value' => $course->id]),
'settingraw' => '1'
],
[
'function' => 'core_course_get_courses_by_field',
'arguments' => json_encode(['field' => 'id', 'value' => $course->id]),
'settingraw' => '1',
'settingfileurl' => '0'
],
[
'function' => 'core_course_get_courses_by_field',
'arguments' => json_encode(['field' => 'id', 'value' => $course->id]),
'settingfilter' => '1',
'settinglang' => 'en'
],
];
$result = external::call_external_functions($requests);
$this->assertCount(4, $result['responses']);
$context = \context_course::instance($course->id);
$pluginfile = 'webservice/pluginfile.php';
$this->assertFalse($result['responses'][0]['error']);
$data = json_decode($result['responses'][0]['data']);
$expected = file_rewrite_pluginfile_urls($course->summary, $pluginfile, $context->id, 'course', 'summary', null);
$expected = format_text($expected, $course->summaryformat, ['para' => false, 'filter' => false]);
$this->assertEquals($expected, $data->courses[0]->summary);
$this->assertFalse($result['responses'][1]['error']);
$data = json_decode($result['responses'][1]['data']);
$expected = file_rewrite_pluginfile_urls($course->summary, $pluginfile, $context->id, 'course', 'summary', null);
$this->assertEquals($expected, $data->courses[0]->summary);
$this->assertFalse($result['responses'][2]['error']);
$data = json_decode($result['responses'][2]['data']);
$this->assertEquals($course->summary, $data->courses[0]->summary);
$this->assertFalse($result['responses'][3]['error']);
$data = json_decode($result['responses'][3]['data']);
$expected = file_rewrite_pluginfile_urls($course->summary, $pluginfile, $context->id, 'course', 'summary', null);
$SESSION->lang = 'en'; // We expect filtered text in english.
$expected = format_text($expected, $course->summaryformat, ['para' => false, 'filter' => true]);
$this->assertEquals($expected, $data->courses[0]->summary);
}
/*
* Test get_tokens_for_qr_login.
*/
public function test_get_tokens_for_qr_login(): void {
global $DB, $CFG, $USER;
$this->resetAfterTest(true);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$mobilesettings = get_config('tool_mobile');
$mobilesettings->qrsameipcheck = 1;
$qrloginkey = api::get_qrlogin_key($mobilesettings);
// Generate new tokens, the ones we expect to receive.
$service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
$token = \core_external\util::generate_token_for_current_user($service);
// Fake the app.
\core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
$result = external::get_tokens_for_qr_login($qrloginkey, $USER->id);
$result = external_api::clean_returnvalue(external::get_tokens_for_qr_login_returns(), $result);
$this->assertEmpty($result['warnings']);
$this->assertEquals($token->token, $result['token']);
$this->assertEquals($token->privatetoken, $result['privatetoken']);
// Now, try with an invalid key.
$this->expectException('moodle_exception');
$this->expectExceptionMessage(get_string('invalidkey', 'error'));
$result = external::get_tokens_for_qr_login(random_string('64'), $user->id);
}
/*
* Test get_tokens_for_qr_login ignore ip check.
*/
public function test_get_tokens_for_qr_login_ignore_ip_check(): void {
global $DB, $CFG, $USER;
$this->resetAfterTest(true);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$mobilesettings = get_config('tool_mobile');
$mobilesettings->qrsameipcheck = 0;
$qrloginkey = api::get_qrlogin_key($mobilesettings);
$key = $DB->get_record('user_private_key', ['value' => $qrloginkey]);
$this->assertNull($key->iprestriction);
// Generate new tokens, the ones we expect to receive.
$service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
$token = \core_external\util::generate_token_for_current_user($service);
// Fake the app.
\core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
$result = external::get_tokens_for_qr_login($qrloginkey, $USER->id);
$result = external_api::clean_returnvalue(external::get_tokens_for_qr_login_returns(), $result);
$this->assertEmpty($result['warnings']);
$this->assertEquals($token->token, $result['token']);
$this->assertEquals($token->privatetoken, $result['privatetoken']);
// Now, try with an invalid key.
$this->expectException('moodle_exception');
$this->expectExceptionMessage(get_string('invalidkey', 'error'));
$result = external::get_tokens_for_qr_login(random_string('64'), $user->id);
}
/*
* Test get_tokens_for_qr_login ip check fails.
*/
public function test_get_tokens_for_qr_login_ip_check_mismatch(): void {
global $DB, $CFG, $USER;
$this->resetAfterTest(true);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$mobilesettings = get_config('tool_mobile');
$mobilesettings->qrsameipcheck = 1;
$qrloginkey = api::get_qrlogin_key($mobilesettings);
// Alter expected ip.
$DB->set_field('user_private_key', 'iprestriction', '6.6.6.6', ['value' => $qrloginkey]);
// Generate new tokens, the ones we expect to receive.
$service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
$token = \core_external\util::generate_token_for_current_user($service);
// Fake the app.
\core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
$this->expectException('moodle_exception');
$this->expectExceptionMessage(get_string('ipmismatch', 'error'));
$result = external::get_tokens_for_qr_login($qrloginkey, $USER->id);
}
/**
* Test get_tokens_for_qr_login missing QR code enabled.
*/
public function test_get_tokens_for_qr_login_missing_enableqr(): void {
global $CFG, $USER;
$this->resetAfterTest(true);
$this->setAdminUser();
set_config('qrcodetype', api::QR_CODE_DISABLED, 'tool_mobile');
$this->expectExceptionMessage(get_string('qrcodedisabled', 'tool_mobile'));
$result = external::get_tokens_for_qr_login('', $USER->id);
}
/**
* Test get_tokens_for_qr_login missing ws.
*/
public function test_get_tokens_for_qr_login_missing_ws(): void {
global $CFG;
$this->resetAfterTest(true);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
// Fake the app.
\core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
// Need to disable webservices to verify that's checked.
$CFG->enablewebservices = 0;
$CFG->enablemobilewebservice = 0;
$this->setAdminUser();
$this->expectException('moodle_exception');
$this->expectExceptionMessage(get_string('enablewsdescription', 'webservice'));
$result = external::get_tokens_for_qr_login('', $user->id);
}
/**
* Test get_tokens_for_qr_login missing https.
*/
public function test_get_tokens_for_qr_login_missing_https(): void {
global $CFG, $USER;
// Fake the app.
\core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
// Need to simulate a non HTTPS site here.
$CFG->wwwroot = str_replace('https:', 'http:', $CFG->wwwroot);
$this->resetAfterTest(true);
$this->setAdminUser();
$this->expectException('moodle_exception');
$this->expectExceptionMessage(get_string('httpsrequired', 'tool_mobile'));
$result = external::get_tokens_for_qr_login('', $USER->id);
}
/**
* Test get_tokens_for_qr_login missing admin.
*/
public function test_get_tokens_for_qr_login_missing_admin(): void {
global $CFG, $USER;
$this->resetAfterTest(true);
$this->setAdminUser();
// Fake the app.
\core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
$this->expectException('moodle_exception');
$this->expectExceptionMessage(get_string('autologinnotallowedtoadmins', 'tool_mobile'));
$result = external::get_tokens_for_qr_login('', $USER->id);
}
/**
* Test get_tokens_for_qr_login missing app_request.
*/
public function test_get_tokens_for_qr_login_missing_app_request(): void {
global $CFG, $USER;
$this->resetAfterTest(true);
$this->setAdminUser();
$this->expectException('moodle_exception');
$this->expectExceptionMessage(get_string('apprequired', 'tool_mobile'));
$result = external::get_tokens_for_qr_login('', $USER->id);
}
/**
* Test validate subscription key.
*/
public function test_validate_subscription_key_valid(): void {
$this->resetAfterTest(true);
$sitesubscriptionkey = ['validuntil' => time() + MINSECS, 'key' => complex_random_string(32)];
set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile');
$result = external::validate_subscription_key($sitesubscriptionkey['key']);
$result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result);
$this->assertEmpty($result['warnings']);
$this->assertTrue($result['validated']);
}
/**
* Test validate subscription key invalid first and then a valid one.
*/
public function test_validate_subscription_key_invalid_key_first(): void {
$this->resetAfterTest(true);
$sitesubscriptionkey = ['validuntil' => time() + MINSECS, 'key' => complex_random_string(32)];
set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile');
$result = external::validate_subscription_key('fakekey');
$result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result);
$this->assertEmpty($result['warnings']);
$this->assertFalse($result['validated']);
// The valid one has been invalidated because the previous attempt.
$result = external::validate_subscription_key($sitesubscriptionkey['key']);
$result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result);
$this->assertEmpty($result['warnings']);
$this->assertFalse($result['validated']);
}
/**
* Test validate subscription key invalid.
*/
public function test_validate_subscription_key_invalid_key(): void {
$this->resetAfterTest(true);
$result = external::validate_subscription_key('fakekey');
$result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result);
$this->assertEmpty($result['warnings']);
$this->assertFalse($result['validated']);
}
/**
* Test validate subscription key invalid.
*/
public function test_validate_subscription_key_outdated(): void {
$this->resetAfterTest(true);
$sitesubscriptionkey = ['validuntil' => time() - MINSECS, 'key' => complex_random_string(32)];
set_config('sitesubscriptionkey', json_encode($sitesubscriptionkey), 'tool_mobile');
$result = external::validate_subscription_key($sitesubscriptionkey['key']);
$result = external_api::clean_returnvalue(external::validate_subscription_key_returns(), $result);
$this->assertEmpty($result['warnings']);
$this->assertFalse($result['validated']);
}
}
+82
View File
@@ -0,0 +1,82 @@
<?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/>.
/**
* Mock class for get_content.
*
* @package tool_mobile
* @copyright 2018 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_mobile\output;
defined('MOODLE_INTERNAL') || die;
/**
* Mock class for get_content.
*
* @package tool_mobile
* @copyright 2018 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mobile {
/**
* Returns a test view.
* @param array $args Arguments from tool_mobile_get_content WS
* @return array HTML, javascript and otherdata
*/
public static function test_view($args) {
$args = (object) $args;
return array(
'templates' => array(
array(
'id' => 'main',
'html' => 'The HTML code',
),
),
'javascript' => 'alert();',
'otherdata' => array('otherdata1' => $args->param1),
'restrict' => array('users' => array(1, 2), 'courses' => array(3, 4)),
'files' => array()
);
}
/**
* Returns a test view disabled.
* @param array $args Arguments from tool_mobile_get_content WS
* @return array HTML, javascript and otherdata
*/
public static function test_view_disabled($args) {
$args = (object) $args;
return array(
'templates' => array(
array(
'id' => 'main',
'html' => 'The HTML code',
),
),
'javascript' => 'alert();',
'otherdata' => array('otherdata1' => $args->param1),
'restrict' => array('users' => array(1, 2), 'courses' => array(3, 4)),
'files' => array(),
'disabled' => true,
);
}
}
@@ -0,0 +1,212 @@
<?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/>.
/**
* Base class for unit tests for tool_mobile.
*
* @package tool_mobile
* @category test
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_mobile\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\local\request\writer;
use core_privacy\local\request\transform;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use tool_mobile\privacy\provider;
/**
* Unit tests for the tool_mobile implementation of the privacy API.
*
* @copyright 2018 Carlos Escobedo <carlos@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends \core_privacy\tests\provider_testcase {
/**
* Basic setup for these tests.
*/
public function setUp(): void {
$this->resetAfterTest(true);
}
/**
* Test to check export_user_preferences.
* returns user preferences data.
*/
public function test_export_user_preferences(): void {
$user = $this->getDataGenerator()->create_user();
$expectedtime = time();
set_user_preference('tool_mobile_autologin_request_last', time(), $user);
provider::export_user_preferences($user->id);
$writer = writer::with_context(\context_system::instance());
$prefs = $writer->get_user_preferences('tool_mobile');
$time = transform::datetime($expectedtime);
$this->assertEquals($time, $prefs->tool_mobile_autologin_request_last->value);
$this->assertEquals(get_string('privacy:metadata:preference:tool_mobile_autologin_request_last', 'tool_mobile'),
$prefs->tool_mobile_autologin_request_last->description);
}
/**
* Test getting the context for the user ID related to this plugin.
*/
public function test_get_contexts_for_userid(): void {
// Create user and Mobile user keys.
$user = $this->getDataGenerator()->create_user();
$context = \context_user::instance($user->id);
$key = get_user_key('tool_mobile', $user->id);
$contextlist = provider::get_contexts_for_userid($user->id);
$this->assertEquals($context->id, $contextlist->current()->id);
}
/**
* Test getting the users for a context related to this plugin.
*/
public function test_get_users_in_context(): void {
$component = 'tool_mobile';
// Create users and Mobile user keys.
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$context1 = \context_user::instance($user1->id);
$context2 = \context_user::instance($user2->id);
$key1 = get_user_key('tool_mobile', $user1->id);
$key2 = get_user_key('tool_mobile/qrlogin', $user1->id);
$key3 = get_user_key('tool_mobile', $user2->id);
// Ensure only user1 is found in context1.
$userlist = new \core_privacy\local\request\userlist($context1, $component);
provider::get_users_in_context($userlist);
$userids = $userlist->get_userids();
$userid = reset($userids);
$this->assertCount(1, $userids);
$this->assertEquals($user1->id, $userid);
}
/**
* Test that data is exported correctly for this plugin.
*/
public function test_export_user_data(): void {
global $DB;
// Create user and Mobile user keys.
$user = $this->getDataGenerator()->create_user();
$context = \context_user::instance($user->id);
$keyvalue = get_user_key('tool_mobile', $user->id);
$key = $DB->get_record('user_private_key', ['value' => $keyvalue]);
// Validate exported data.
$this->setUser($user);
/** @var \core_privacy\tests\request\content_writer $writer */
$writer = writer::with_context($context);
$this->assertFalse($writer->has_any_data());
$this->export_context_data_for_user($user->id, $context, 'tool_mobile');
$userkeydata = $writer->get_related_data([], 'userkeys');
$this->assertCount(1, $userkeydata->keys);
$this->assertEquals($key->script, reset($userkeydata->keys)->script);
}
/**
* Test for provider::delete_data_for_all_users_in_context().
*/
public function test_delete_data_for_all_users_in_context(): void {
global $DB;
// Create user and Mobile user keys.
$user = $this->getDataGenerator()->create_user();
$context = \context_user::instance($user->id);
$keyvalue = get_user_key('tool_mobile', $user->id);
$key = $DB->get_record('user_private_key', ['value' => $keyvalue]);
// Before deletion, we should have 1 user_private_key.
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
$this->assertEquals(1, $count);
// Delete data.
provider::delete_data_for_all_users_in_context($context);
// After deletion, the user_private_key entries should have been deleted.
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
$this->assertEquals(0, $count);
}
/**
* Test for provider::delete_data_for_user().
*/
public function test_delete_data_for_user(): void {
global $DB;
// Create user and Mobile user keys.
$user = $this->getDataGenerator()->create_user();
$context = \context_user::instance($user->id);
$keyvalue = get_user_key('tool_mobile', $user->id);
$key = $DB->get_record('user_private_key', ['value' => $keyvalue]);
// Before deletion, we should have 1 user_private_key.
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
$this->assertEquals(1, $count);
// Delete data.
$contextlist = provider::get_contexts_for_userid($user->id);
$approvedcontextlist = new approved_contextlist($user, 'tool_mobile', $contextlist->get_contextids());
provider::delete_data_for_user($approvedcontextlist);
// After deletion, the user_private_key entries should have been deleted.
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
$this->assertEquals(0, $count);
}
/**
* Test for provider::test_delete_data_for_users().
*/
public function test_delete_data_for_users(): void {
global $DB;
$component = 'tool_mobile';
// Create users and Mobile user keys.
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$context1 = \context_user::instance($user1->id);
$context2 = \context_user::instance($user2->id);
$keyvalue1 = get_user_key('tool_mobile', $user1->id);
$keyvalue2 = get_user_key('tool_mobile/qrlogin', $user1->id);
$keyvalue3 = get_user_key('tool_mobile', $user2->id);
$key1 = $DB->get_record('user_private_key', ['value' => $keyvalue1]);
// Before deletion, we should have 2 user_private_keys for tool_mobile and one for tool_mobile/qrlogin.
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
$this->assertEquals(2, $count);
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile/qrlogin']);
$this->assertEquals(1, $count);
// Ensure deleting wrong user in the user context does nothing.
$approveduserids = [$user2->id];
$approvedlist = new approved_userlist($context1, $component, $approveduserids);
provider::delete_data_for_users($approvedlist);
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
$this->assertEquals(2, $count);
// Delete for user1 in context1.
$approveduserids = [$user1->id];
$approvedlist = new approved_userlist($context1, $component, $approveduserids);
provider::delete_data_for_users($approvedlist);
// Ensure only user1's data is deleted, user2's remains.
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
$this->assertEquals(1, $count);
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile/qrlogin']);
$this->assertEquals(0, $count);
$params = ['script' => $component];
$userid = $DB->get_field_select('user_private_key', 'userid', 'script = :script', $params);
$this->assertEquals($user2->id, $userid);
}
}
+39
View File
@@ -0,0 +1,39 @@
This files describes changes in tool_mobile code.
Information provided here is intended especially for developers.
=== 4.2 ===
* External function tool_mobile::get_config now returns the timezone and forcetimezone settings.
=== 4.1 ===
* External function tool_mobile::get_config now returns the customusermenuitems setting.
=== 4.0 ===
* The function tool_mobile\api::get_qrlogin_key() now requires as parameter an object with all the mobile plugin settings.
* The tool_mobile_external::get_config external function now returns the tool_mobile_autologinmintimebetweenreq setting.
* External function tool_mobile::get_config now returns the enabledashboard setting.
=== 3.7 ===
* New external function tool_mobile::tool_mobile_call_external_function allows calling multiple external functions and returns all responses.
* External function tool_mobile::get_autologin_key now only works if the request comes from the Moodle mobile or desktop app.
This increases confidence that requests did originate from the mobile app, decreasing the likelihood of an XSS attack.
If you want to use this functionality, please override the Web Service via the override_webservice_execution callback although
this is not recommended or encouraged.
=== 3.5 ===
* External function tool_mobile::tool_mobile_get_plugins_supporting_mobile now returns additional plugins information required by
Moodle Mobile 3.5.0.
=== 3.4 ===
* External function tool_mobile::tool_mobile_get_plugins_supporting_mobile is now available via AJAX for not logged users.
When called via AJAX without a user session the function will return only auth plugins.
=== 3.3 ===
* External function tool_mobile::get_public_config now returns the mobilecssurl field (Mobile custom CSS theme).
* External function tool_mobile::get_public_config now returns the identityproviders field (list of external identity providers).
+31
View File
@@ -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/>.
/**
* Plugin version info
*
* @package tool_mobile
* @copyright 2016 Juan Leyva
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
$plugin->dependencies = [
'webservice_rest' => 2024041600,
];