MDL-87494 tool_mobile: Update Mobile app subscription page design
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
issueNumber: MDL-87494
|
||||
notes:
|
||||
tool_mobile:
|
||||
- message: >-
|
||||
Improve the mobile app subscription page UI and add a subscription
|
||||
cache refresh task and an application-level cache.
|
||||
The cache name used for mobile subscription information has changed,
|
||||
the get_subscription() helper now accepts additional parameters
|
||||
and the undocumented config.php setting
|
||||
$CFG->disablemobileappsubscription has been removed.
|
||||
type: changed
|
||||
@@ -0,0 +1,9 @@
|
||||
define("tool_mobile/featurecomparison",["exports","core/str","core/notification"],(function(_exports,_str,_notification){var obj;
|
||||
/**
|
||||
* Feature comparison toggle for tool_mobile subscription page.
|
||||
*
|
||||
* @copyright 2026 Daniel Urena
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};const initCard=card=>{if(!card)return;const rows=Array.from(card.querySelectorAll(".feature-comparison-rows .feature-comparison-row")),toggle=card.querySelector(".feature-comparison-toggle");if(!rows.length||!toggle)return;const textSpan=toggle.querySelector(".feature-comparison-toggle-text"),icon=toggle.querySelector(".feature-comparison-toggle-icon");let expanded=!1;const applyState=()=>(rows.forEach((row=>{row.classList.toggle("d-none",!expanded)})),textSpan&&(0,_str.getString)(expanded?"showless":"showmore").then((str=>(textSpan.textContent=str,null))).catch((error=>{_notification.default.exception(error)})),icon&&(icon.classList.toggle("fa-chevron-down",!expanded),icon.classList.toggle("fa-chevron-up",expanded)),null);toggle.addEventListener("click",(e=>{e.preventDefault(),expanded=!expanded,applyState()})),applyState()};var _default={init:()=>{document.querySelectorAll(".feature-comparison-card").forEach(initCard)}};return _exports.default=_default,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=featurecomparison.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"featurecomparison.min.js","sources":["../src/featurecomparison.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Feature comparison toggle for tool_mobile subscription page.\n *\n * @copyright 2026 Daniel Urena\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getString} from 'core/str';\nimport Notification from 'core/notification';\n\n/**\n * Initialise a single feature comparison card.\n *\n * @param {HTMLElement} card\n */\nconst initCard = (card) => {\n if (!card) {\n return;\n }\n\n const rows = Array.from(\n card.querySelectorAll('.feature-comparison-rows .feature-comparison-row')\n );\n const toggle = card.querySelector('.feature-comparison-toggle');\n\n if (!rows.length || !toggle) {\n return;\n }\n\n const textSpan = toggle.querySelector('.feature-comparison-toggle-text');\n const icon = toggle.querySelector('.feature-comparison-toggle-icon');\n\n let expanded = false;\n\n const applyState = () => {\n rows.forEach((row) => {\n row.classList.toggle('d-none', !expanded);\n });\n\n if (textSpan) {\n // Fire and forget: handle resolution/rejection explicitly.\n void getString(expanded ? 'showless' : 'showmore')\n .then((str) => {\n textSpan.textContent = str;\n return null;\n })\n .catch((error) => {\n Notification.exception(error);\n });\n }\n\n if (icon) {\n icon.classList.toggle('fa-chevron-down', !expanded);\n icon.classList.toggle('fa-chevron-up', expanded);\n }\n\n return null;\n };\n\n toggle.addEventListener('click', (e) => {\n e.preventDefault();\n expanded = !expanded;\n applyState();\n });\n\n applyState();\n};\n\n/**\n * Initialise all feature comparison cards.\n */\nconst init = () => {\n document.querySelectorAll('.feature-comparison-card')\n .forEach(initCard);\n};\n\nexport default {\n init,\n};\n"],"names":["initCard","card","rows","Array","from","querySelectorAll","toggle","querySelector","length","textSpan","icon","expanded","applyState","forEach","row","classList","then","str","textContent","catch","error","exception","addEventListener","e","preventDefault","init","document"],"mappings":";;;;;;+JA8BMA,SAAYC,WACTA,kBAICC,KAAOC,MAAMC,KACfH,KAAKI,iBAAiB,qDAEpBC,OAASL,KAAKM,cAAc,kCAE7BL,KAAKM,SAAWF,oBAIfG,SAAWH,OAAOC,cAAc,mCAChCG,KAAOJ,OAAOC,cAAc,uCAE9BI,UAAW,QAETC,WAAa,KACfV,KAAKW,SAASC,MACVA,IAAIC,UAAUT,OAAO,UAAWK,aAGhCF,WAEK,kBAAUE,SAAW,WAAa,YAClCK,MAAMC,MACHR,SAASS,YAAcD,IAChB,QAEVE,OAAOC,8BACSC,UAAUD,UAI/BV,OACAA,KAAKK,UAAUT,OAAO,mBAAoBK,UAC1CD,KAAKK,UAAUT,OAAO,gBAAiBK,WAGpC,MAGXL,OAAOgB,iBAAiB,SAAUC,IAC9BA,EAAEC,iBACFb,UAAYA,SACZC,gBAGJA,2BAWW,CACXa,KANS,KACTC,SAASrB,iBAAiB,4BACrBQ,QAAQb"}
|
||||
@@ -0,0 +1,94 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Feature comparison toggle for tool_mobile subscription page.
|
||||
*
|
||||
* @copyright 2026 Daniel Urena
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import {getString} from 'core/str';
|
||||
import Notification from 'core/notification';
|
||||
|
||||
/**
|
||||
* Initialise a single feature comparison card.
|
||||
*
|
||||
* @param {HTMLElement} card
|
||||
*/
|
||||
const initCard = (card) => {
|
||||
if (!card) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = Array.from(
|
||||
card.querySelectorAll('.feature-comparison-rows .feature-comparison-row')
|
||||
);
|
||||
const toggle = card.querySelector('.feature-comparison-toggle');
|
||||
|
||||
if (!rows.length || !toggle) {
|
||||
return;
|
||||
}
|
||||
|
||||
const textSpan = toggle.querySelector('.feature-comparison-toggle-text');
|
||||
const icon = toggle.querySelector('.feature-comparison-toggle-icon');
|
||||
|
||||
let expanded = false;
|
||||
|
||||
const applyState = () => {
|
||||
rows.forEach((row) => {
|
||||
row.classList.toggle('d-none', !expanded);
|
||||
});
|
||||
|
||||
if (textSpan) {
|
||||
// Fire and forget: handle resolution/rejection explicitly.
|
||||
void getString(expanded ? 'showless' : 'showmore')
|
||||
.then((str) => {
|
||||
textSpan.textContent = str;
|
||||
return null;
|
||||
})
|
||||
.catch((error) => {
|
||||
Notification.exception(error);
|
||||
});
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
icon.classList.toggle('fa-chevron-down', !expanded);
|
||||
icon.classList.toggle('fa-chevron-up', expanded);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
toggle.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
expanded = !expanded;
|
||||
applyState();
|
||||
});
|
||||
|
||||
applyState();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise all feature comparison cards.
|
||||
*/
|
||||
const init = () => {
|
||||
document.querySelectorAll('.feature-comparison-card')
|
||||
.forEach(initCard);
|
||||
};
|
||||
|
||||
export default {
|
||||
init,
|
||||
};
|
||||
@@ -802,15 +802,26 @@ class api {
|
||||
/**
|
||||
* Gets Moodle app plan subscription information for the current site as it is returned by the Apps Portal.
|
||||
*
|
||||
* @param bool $forcecache If true, return only cached data. Has priority over $ignorecache.
|
||||
* @param bool $ignorecache If true, ignore cached data and request information from the Application Portal.
|
||||
* @param int $timeout Time in seconds to wait for the Apps Portal response before giving up. Defaults to 10 seconds.
|
||||
* @return array Subscription information
|
||||
*/
|
||||
public static function get_subscription_information(): ?array {
|
||||
public static function get_subscription_information($forcecache = false, $ignorecache = false, $timeout = 10): ?array {
|
||||
global $CFG;
|
||||
|
||||
// Use session cache to prevent multiple requests.
|
||||
$cache = \cache::make('tool_mobile', 'subscriptiondata');
|
||||
$timeout = min(30, $timeout);
|
||||
// Manage cache of the subscription information to avoid requesting it too often to the Moodle Apps Portal.
|
||||
$cache = \cache::make('tool_mobile', 'subscriptioninfo');
|
||||
$subscriptiondata = $cache->get(0);
|
||||
if ($subscriptiondata !== false) {
|
||||
|
||||
// If we must force using cache, return it (or null if not present) and never contact the portal.
|
||||
if ($forcecache) {
|
||||
return $subscriptiondata !== false ? $subscriptiondata : null;
|
||||
}
|
||||
|
||||
// If we are allowed to use cache and we have data, return it early.
|
||||
if (!$ignorecache && $subscriptiondata !== false) {
|
||||
return $subscriptiondata;
|
||||
}
|
||||
|
||||
@@ -840,20 +851,25 @@ class api {
|
||||
'appids' => [$androidappid, $iosappid],
|
||||
'credentials' => $credentials,
|
||||
];
|
||||
// Get the current language to send to the WS.
|
||||
$settingslang = current_language();
|
||||
// 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));
|
||||
$curl->setopt([
|
||||
'CURLOPT_TIMEOUT' => $timeout,
|
||||
'CURLOPT_CONNECTTIMEOUT' => $timeout,
|
||||
]);
|
||||
|
||||
$serverurl = static::MOODLE_APPS_PORTAL_URL . "/lib/ajax/service-nologin.php";
|
||||
$serverurl = static::MOODLE_APPS_PORTAL_URL . "/lib/ajax/service-nologin.php?lang=$settingslang";
|
||||
$query = 'args=' . urlencode(json_encode($args));
|
||||
$wsresponse = @json_decode($curl->post($serverurl, $query), true);
|
||||
|
||||
@@ -861,10 +877,23 @@ class api {
|
||||
if ($curlerrno = $curl->get_errno()) {
|
||||
// CURL connection error.
|
||||
debugging("Unexpected response from the Moodle Apps Portal server, CURL error number: $curlerrno");
|
||||
if (!$ignorecache && $subscriptiondata !== false) {
|
||||
return $subscriptiondata;
|
||||
}
|
||||
return null;
|
||||
} else if (!empty($curl->error)) {
|
||||
// CURL error without an error number.
|
||||
debugging('Unexpected response from the Moodle Apps Portal server, CURL error: ' . $curl->error);
|
||||
if (!$ignorecache && $subscriptiondata !== false) {
|
||||
return $subscriptiondata;
|
||||
}
|
||||
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']);
|
||||
debugging('Unexpected response from the Moodle Apps Portal server, HTTP code:' . $info['http_code']);
|
||||
if (!$ignorecache && $subscriptiondata !== false) {
|
||||
return $subscriptiondata;
|
||||
}
|
||||
return null;
|
||||
} else if (!empty($wsresponse[0]['error'])) {
|
||||
// Unexpected error from Moodle Apps Portal.
|
||||
@@ -876,7 +905,7 @@ class api {
|
||||
}
|
||||
|
||||
$cache->set(0, $wsresponse[0]['data']);
|
||||
|
||||
set_config('subscriptioninfoupdated', time(), 'tool_mobile');
|
||||
return $wsresponse[0]['data'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -738,7 +738,7 @@ class external extends external_api {
|
||||
|
||||
$validated = false;
|
||||
$sitesubscriptionkey = get_config('tool_mobile', 'sitesubscriptionkey');
|
||||
if (!empty($sitesubscriptionkey) && $CFG->enablemobilewebservice && empty($CFG->disablemobileappsubscription)) {
|
||||
if (!empty($sitesubscriptionkey) && $CFG->enablemobilewebservice) {
|
||||
$sitesubscriptionkey = json_decode($sitesubscriptionkey);
|
||||
$validated = time() < $sitesubscriptionkey->validuntil && $params['key'] === $sitesubscriptionkey->key;
|
||||
// Delete existing, even if not validated to enforce security and attacks prevention.
|
||||
|
||||
@@ -59,59 +59,190 @@ class subscription implements \renderable, \templatable {
|
||||
* @return array with the subscription information
|
||||
*/
|
||||
public function export_for_template(\renderer_base $output): array {
|
||||
global $CFG;
|
||||
global $CFG, $USER;
|
||||
|
||||
$ms = get_config('tool_mobile'); // Get mobile settings.
|
||||
|
||||
$data = $this->subscriptiondata;
|
||||
// Check subscription information to prepare the page data.
|
||||
if (empty($data) || empty($data['subscription']) || !isset($data['availableplans'])) {
|
||||
// Prepare data for no subscription information case, with a message and a link to the apps portal.
|
||||
$datanosub['appsportalurl'] = \tool_mobile\api::MOODLE_APPS_PORTAL_URL;
|
||||
$datanosub['messagenosubscriptioninfo'][] = [
|
||||
'message' => get_string('nosubsblocked', 'tool_mobile', $datanosub['appsportalurl']),
|
||||
'title' => get_string('nosubswhyhappen', 'tool_mobile'),
|
||||
'extraclasses' => 'mt-4 mb-0',
|
||||
];
|
||||
return $datanosub;
|
||||
}
|
||||
|
||||
$livedataurl = \tool_mobile\api::MOODLE_APPS_PORTAL_URL;
|
||||
$data['messagelivedata'] = get_string('showinglivedata', 'tool_mobile', $livedataurl);
|
||||
|
||||
$lastupdate = get_config('tool_mobile', 'subscriptioninfoupdated');
|
||||
if (empty($lastupdate) || time() - $lastupdate > 10 * DAYSECS) {
|
||||
if (empty($lastupdate)) {
|
||||
$data['messageoldcache'] = get_string('planneverchecked', 'tool_mobile');
|
||||
} else {
|
||||
$diff = time() - $lastupdate;
|
||||
$oldcachetime['since'] = (int) floor($diff / DAYSECS);
|
||||
$oldcachetime['date'] = userdate($lastupdate, get_string('strftimedatetime', 'langconfig'));
|
||||
$data['messageoldcache'] = get_string('showingcacheddata', 'tool_mobile', $oldcachetime);
|
||||
}
|
||||
}
|
||||
|
||||
$data['appsportalurl'] = \tool_mobile\api::MOODLE_APPS_PORTAL_URL;
|
||||
$params = [
|
||||
'sitesecret' => md5(\core\hub\registration::get_secret()),
|
||||
'siteurl' => $CFG->wwwroot,
|
||||
'origin' => 'moodlelms',
|
||||
];
|
||||
$unregisteredurl = new \moodle_url(
|
||||
\tool_mobile\api::MOODLE_APPS_PORTAL_URL . '/local/apps/signup_site.php',
|
||||
$params,
|
||||
);
|
||||
|
||||
$params['plan'] = 'premium';
|
||||
$params['email'] = md5($USER->email);
|
||||
$appsportalupgradeurl = new \moodle_url(
|
||||
\tool_mobile\api::MOODLE_APPS_PORTAL_URL . '/local/apps/signup_site.php',
|
||||
$params,
|
||||
);
|
||||
$data['appsportalupgradeurl'] = $appsportalupgradeurl->out(false);
|
||||
|
||||
// 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']];
|
||||
switch ($msg['code'] ?? '') {
|
||||
case "deprecatedplan":
|
||||
$data['messagedeprecation'][] = [
|
||||
'message' => $msg['message'],
|
||||
'titleicon' => [
|
||||
'icon' => 'i/warning',
|
||||
'component' => 'core',
|
||||
],
|
||||
'title' => get_string('plancontinues', 'tool_mobile', $data['subscription']['name']),
|
||||
];
|
||||
break;
|
||||
case "missingcredentials":
|
||||
$data['messagemissingcredentials'][] = [
|
||||
'message' => $msg['message'],
|
||||
'extraclasses' => 'mt-4',
|
||||
];
|
||||
break;
|
||||
|
||||
case "unregisteredsite":
|
||||
$data['messageunregisteredsite'][] = [
|
||||
'message' => $msg['message'],
|
||||
'titleicon' => [
|
||||
'icon' => 'i/warning',
|
||||
'component' => 'core',
|
||||
],
|
||||
'buttonstr' => get_string('register', 'tool_mobile'),
|
||||
'buttonurl' => $unregisteredurl->out(false),
|
||||
'extrabuttonclasses' => 'text-dark',
|
||||
];
|
||||
break;
|
||||
|
||||
default:
|
||||
$data['messages' . $msg['type']][] = ['message' => $msg['message']];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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'])
|
||||
];
|
||||
}
|
||||
}
|
||||
// Derive plan flags for template logic-less checks.
|
||||
if (!empty($data['subscription']['plan'])) {
|
||||
$data['subscription']['isfree'] = ($data['subscription']['plan'] === 'free');
|
||||
$data['subscription']['ispremium'] =
|
||||
($data['subscription']['plan'] === 'premium' || $data['subscription']['plan'] === 'bma');
|
||||
}
|
||||
|
||||
if ($data['subscription']['ispremium']) {
|
||||
$data['messagefullaccess'][] = [
|
||||
'titleicon' => [
|
||||
'icon' => 'i/checkedcircle',
|
||||
'component' => 'core',
|
||||
],
|
||||
'title' => get_string('fullaccessfeatures', 'tool_mobile', $data['subscription']['name']),
|
||||
];
|
||||
}
|
||||
|
||||
// Review availableplans.
|
||||
foreach ($data['availableplans'] as $plan) {
|
||||
if ($plan['plan'] != 'premium') {
|
||||
continue;
|
||||
}
|
||||
$data['tocompareplan'] = $plan;
|
||||
}
|
||||
|
||||
// Now prepare statistics information.
|
||||
if (!isset($data['messagemissingcredentials'])) {
|
||||
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'),
|
||||
'icon' => [
|
||||
'icon' => 'i/risk_dataloss',
|
||||
'component' => 'core',
|
||||
],
|
||||
'buttonstr' => get_string('upgradetosubscription', 'tool_mobile', $data['tocompareplan']['name']),
|
||||
'buttonurl' => $appsportalupgradeurl->out(false),
|
||||
'extraclasses' => 'alert-danger',
|
||||
'extrabuttonclasses' => 'bg-danger text-light',
|
||||
];
|
||||
}
|
||||
$monthformat = get_string('strftimemonth');
|
||||
foreach ($data['notifications']['monthly'] as $key => $month) {
|
||||
if (!empty($month['year']) && !empty($month['month'])) {
|
||||
$timestamp = make_timestamp((int)$month['year'], (int)$month['month'], 1);
|
||||
$data['notifications']['monthly'][$key]['monthstr'] = userdate($timestamp, $monthformat);
|
||||
}
|
||||
}
|
||||
}
|
||||
$data['messagesnotificationsseemore'] = get_string('notificationsseemore', 'tool_mobile', $data['appsportalurl']);
|
||||
} else {
|
||||
$urlmessagesetting = new \moodle_url('/admin/settings.php', ['section' => 'messagesettingairnotifier']);
|
||||
$data['messagesnonotifications'][] = [
|
||||
'icon' => [
|
||||
'icon' => 'i/warning',
|
||||
'component' => 'core',
|
||||
],
|
||||
'message' => get_string('notificationsmissingwarning', 'tool_mobile', $urlmessagesetting),
|
||||
'extraclasses' => 'mt-4',
|
||||
];
|
||||
}
|
||||
}
|
||||
$subscribedfeatures = [];
|
||||
// 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;
|
||||
$feature['limit'] = get_string('unlimited');
|
||||
}
|
||||
|
||||
switch ($feature['name']) {
|
||||
// Check active devices.
|
||||
case 'pushnotificationsdevices':
|
||||
$feature['status'] = 0;
|
||||
if (isset($data['notifications']['currentactivedevices'])) {
|
||||
$feature['status'] = $data['notifications']['currentactivedevices'];
|
||||
}
|
||||
$feature['showstatus'] = 1;
|
||||
break;
|
||||
// Check menu items.
|
||||
case 'custommenuitems':
|
||||
@@ -140,6 +271,7 @@ class subscription implements \renderable, \templatable {
|
||||
}
|
||||
|
||||
$feature['status'] = count($custommenuitems) + count($customusermenuitems);
|
||||
$feature['showstatus'] = 1;
|
||||
break;
|
||||
// Check language strings.
|
||||
case 'customlanguagestrings':
|
||||
@@ -155,29 +287,59 @@ class subscription implements \renderable, \templatable {
|
||||
);
|
||||
}
|
||||
$feature['status'] = count($langstrings);
|
||||
$feature['showstatus'] = 1;
|
||||
break;
|
||||
// Check disabled features strings.
|
||||
case 'disabledfeatures':
|
||||
$feature['status'] = empty($ms->disabledfeatures) ? 0 : count(explode(',', $ms->disabledfeatures));
|
||||
$feature['showstatus'] = 1;
|
||||
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 (isset($feature['status'])) {
|
||||
$feature['humanstatus'] = (string)$feature['status'];
|
||||
if (is_int($feature['status']) && is_int($feature['limit'])) {
|
||||
$feature['showbar'] = 1;
|
||||
// Show currentussage with or without currentusage.
|
||||
if (!isset($data['subscription']['currentusage'])) {
|
||||
$data['subscription']['currentusage'] = [];
|
||||
}
|
||||
$feature['humanstatus'] = $feature['status'] . ' / ' . $feature['limit'];
|
||||
$feature['percentage'] = round(($feature['status'] / $feature['limit']) * 100);
|
||||
if ($feature['percentage'] > 100) {
|
||||
$feature['percentage'] = 100;
|
||||
}
|
||||
$feature['barclass'] = 'bg-success';
|
||||
|
||||
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');
|
||||
if ($feature['status'] >= ($feature['limit'] / 2)) {
|
||||
$feature['barclass'] = 'bg-warning';
|
||||
}
|
||||
if ($feature['status'] >= $feature['limit']) {
|
||||
$feature['barclass'] = 'bg-danger';
|
||||
if ($feature['status'] > $feature['limit']) {
|
||||
$feature['humanstatus'] .= ' - ' . get_string('subscriptionlimitsurpassed', 'tool_mobile');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$feature['showbar'] = 0;
|
||||
if ($feature['status'] >= $feature['limit']) {
|
||||
$feature['barclass'] = 'bg-danger';
|
||||
if ($feature['status'] > $feature['limit']) {
|
||||
$feature['humanstatus'] .= ' - ' . get_string('subscriptionlimitsurpassed', 'tool_mobile');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$subscribedfeatures[$feature['name']]['subscribedlimitstr'] = $feature['limitstr'];
|
||||
$subscribedfeatures[$feature['name']]['subscribedenabled'] = $feature['enabled'];
|
||||
$subscribedfeatures[$feature['name']]['subscribeddecoration'] =
|
||||
$feature['enabled'] ? 'text-primary' : 'text-danger';
|
||||
$subscribedfeatures[$feature['name']]['subscribedlimitstr'] = $feature['limitstr'];
|
||||
$subscribedfeatures[$feature['name']]['subscribedenabled'] = $feature['enabled'];
|
||||
$subscribedfeatures[$feature['name']]['subscribeddecoration'] =
|
||||
$feature['enabled'] ? 'text-primary' : 'text-danger';
|
||||
} else {
|
||||
$feature['humanstatus'] = empty($feature['enabled']) ? get_string('notincluded') : get_string('included');
|
||||
|
||||
@@ -206,6 +368,14 @@ class subscription implements \renderable, \templatable {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$subscribedfeatures[$feature['name']]['subscribedhumanstatus'] = $feature['humanstatus'];
|
||||
$subscribedfeatures[$feature['name']]['subscribedenabled'] = $feature['enabled'] ? 'fa-check' : 'fa-times';
|
||||
$subscribedfeatures[$feature['name']]['subscribeddecoration'] =
|
||||
$feature['enabled'] ? 'text-primary' : 'text-danger';
|
||||
$subscribedfeatures[$feature['name']]['subscribedhumanstatus'] = $feature['humanstatus'];
|
||||
$subscribedfeatures[$feature['name']]['subscribedenabled'] = $feature['enabled'] ? 'fa-check' : 'fa-times';
|
||||
$subscribedfeatures[$feature['name']]['subscribeddecoration'] =
|
||||
$feature['enabled'] ? 'text-primary' : 'text-danger';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +391,34 @@ class subscription implements \renderable, \templatable {
|
||||
}
|
||||
);
|
||||
|
||||
if (!$data['availableplans']) {
|
||||
return $data;
|
||||
}
|
||||
// Review availableplans features and filter to use premium.
|
||||
foreach ($data['tocompareplan']['features'] as &$feature) {
|
||||
if (array_key_exists('limit', $feature) && empty($feature['limit'])) {
|
||||
$feature['limit'] = 'unlimited';
|
||||
}
|
||||
$feature['humanstatus'] = $feature['enabled'] ? get_string('included') : get_string('notincluded');
|
||||
if (isset($subscribedfeatures[$feature['name']])) {
|
||||
$feature = array_merge($feature, $subscribedfeatures[$feature['name']]);
|
||||
}
|
||||
}
|
||||
if (!empty($data['tocompareplan'])) {
|
||||
usort(
|
||||
$data['tocompareplan']['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;
|
||||
}
|
||||
);
|
||||
}
|
||||
unset($data['availableplans']);
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?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 refresh subscription cache task definitions.
|
||||
*
|
||||
* @package tool_mobile
|
||||
* @copyright 2026 Daniel Urena
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_mobile\task;
|
||||
|
||||
use tool_mobile\api;
|
||||
|
||||
/**
|
||||
* Scheduled task to refresh Moodle app subscription information cache.
|
||||
*
|
||||
* @package tool_mobile
|
||||
* @copyright 2026
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class refresh_subscription_cache extends \core\task\scheduled_task {
|
||||
/**
|
||||
* Return the task name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('taskrefreshsubscriptioncache', 'tool_mobile');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the task.
|
||||
*/
|
||||
public function execute() {
|
||||
global $CFG;
|
||||
|
||||
if (empty($CFG->enablemobilewebservice)) {
|
||||
mtrace('tool_mobile: task not running, mobile app is not enabled.');
|
||||
return;
|
||||
}
|
||||
|
||||
$data = api::get_subscription_information(false, true);
|
||||
if ($data === null) {
|
||||
mtrace('tool_mobile: subscription cache refresh failed.');
|
||||
} else {
|
||||
mtrace('tool_mobile: subscription cache refreshed.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Adhoc task to refresh Moodle app subscription information cache.
|
||||
*
|
||||
* Used by upgrade step 2026020300 to refresh the cache once after
|
||||
* installing or upgrading the plugin.
|
||||
*
|
||||
* @package tool_mobile
|
||||
* @copyright 2026 Daniel Urena
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_mobile\task;
|
||||
|
||||
use tool_mobile\api;
|
||||
|
||||
/**
|
||||
* Adhoc task to refresh Moodle app subscription information cache.
|
||||
*
|
||||
* @package tool_mobile
|
||||
* @copyright 2026
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class refresh_subscription_cache_adhoc extends \core\task\adhoc_task {
|
||||
/**
|
||||
* Execute the task.
|
||||
*/
|
||||
public function execute() {
|
||||
global $CFG;
|
||||
|
||||
if (empty($CFG->enablemobilewebservice)) {
|
||||
mtrace('tool_mobile: adhoc task not running, mobile app is not enabled.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Force a fresh request to the Apps Portal, ignoring any cached value.
|
||||
$data = api::get_subscription_information(false, true);
|
||||
if ($data === null) {
|
||||
mtrace('tool_mobile: adhoc subscription cache refresh failed.');
|
||||
} else {
|
||||
mtrace('tool_mobile: adhoc subscription cache refreshed.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,9 @@ $definitions = array(
|
||||
'staticacceleration' => true,
|
||||
'staticaccelerationsize' => 1
|
||||
),
|
||||
'subscriptiondata' => array(
|
||||
'mode' => cache_store::MODE_SESSION,
|
||||
'subscriptioninfo' => [
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
'simplekeys' => true,
|
||||
'simpledata' => false,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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 tasks definitions.
|
||||
*
|
||||
* @package tool_mobile
|
||||
* @copyright 2026 Daniel Urena
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$tasks = [
|
||||
[
|
||||
'classname' => 'tool_mobile\task\refresh_subscription_cache',
|
||||
'blocking' => 0,
|
||||
'minute' => 'R',
|
||||
'hour' => 'R',
|
||||
'day' => '*',
|
||||
'dayofweek' => 'R',
|
||||
'month' => '*',
|
||||
],
|
||||
];
|
||||
@@ -41,5 +41,11 @@ function xmldb_tool_mobile_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v5.1.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2026012700) {
|
||||
// Run the subscription cache refresh task as soon as possible after upgrade by queueing an adhoc task.
|
||||
$task = new \tool_mobile\task\refresh_subscription_cache_adhoc();
|
||||
\core\task\manager::queue_adhoc_task($task, true);
|
||||
upgrade_plugin_savepoint(true, 2026012700, 'tool', 'mobile');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -38,10 +38,12 @@ $string['autologout_desc'] = 'For security reasons, you can enforce automatic lo
|
||||
$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['billedannually'] = 'Billed annually';
|
||||
$string['cachedef_plugininfo'] = 'This stores the list of plugins with mobile addons';
|
||||
$string['cachedef_subscriptiondata'] = 'This stores the Moodle app subscription information.';
|
||||
$string['cachedef_subscriptioninfo'] = '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['currentusage'] = 'Current usage';
|
||||
$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>
|
||||
@@ -78,6 +80,7 @@ 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['devicelimitnotrack'] = 'Per device limit (no usage tracking)';
|
||||
$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.';
|
||||
@@ -85,6 +88,9 @@ $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['feature'] = 'Feature';
|
||||
$string['featurecomparison'] = 'Feature comparison';
|
||||
$string['features'] = 'features';
|
||||
$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';
|
||||
@@ -92,6 +98,8 @@ $string['forcedurlscheme'] = 'If you want to allow only your custom branded 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['fullaccessfeatures'] = 'You have full access to {$a} features';
|
||||
$string['getmoodleonyourmobile'] = 'Get the mobile app';
|
||||
$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.';
|
||||
@@ -103,17 +111,19 @@ $string['invaliduserquotawarning'] = 'The user quota (userquota) is set to an in
|
||||
$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['limitedfeatures'] = 'Limited features';
|
||||
$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['manage'] = 'Manage';
|
||||
$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['mobileappenabled'] = 'This site has mobile app access enabled.<br /><a href="{$a}">Download the mobile app</a>.';
|
||||
$string['mobileappsubscription'] = 'Moodle app subscription';
|
||||
$string['mobileauthentication'] = 'Mobile authentication';
|
||||
$string['mobilecssurl'] = 'CSS';
|
||||
@@ -121,38 +131,55 @@ $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['nosubsblocked'] = 'Your Moodle server may be blocking outbound connections to the Moodle Apps Portal.';
|
||||
$string['nosubscouldntretrieve'] = "We couldn't retrieve your plan details";
|
||||
$string['nosubsoerror'] = "Unable to connect to Moodle Apps Portal. Your plan, feature limits, and usage data are temporarily unavailable. This doesn’t affect your active subscription.";
|
||||
$string['nosubstryagain'] = "Try again";
|
||||
$string['nosubsviewplan'] = "View your plan on the Moodle Apps Portal";
|
||||
$string['nosubswhyhappen'] = "Why might this happen?";
|
||||
$string['notifications'] = 'Notifications';
|
||||
$string['notificationsactivedevices'] = 'Active devices';
|
||||
$string['notificationscurrentactivedevices'] = 'Devices receiving notifications this month';
|
||||
$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['notificationslimitreached'] = 'Monthly device limit reached. Some users are not receiving push notifications. Upgrade your plan to ensure all students and staff receive mobile notifications.';
|
||||
$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 <a href="{$a}">Site Administration / Messaging / Mobile</a>.';
|
||||
$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['planbenefits'] = 'Your {$a} plan benefits';
|
||||
$string['plancontinues'] = 'Your {$a} plan continues without changes';
|
||||
$string['plandetails'] = 'Your Moodle app subscription';
|
||||
$string['planexpireson'] = 'Expires on ';
|
||||
$string['planneverchecked'] = 'Plan status has never been checked.';
|
||||
$string['pluginname'] = 'Moodle app tools';
|
||||
$string['pluginnotenabledorconfigured'] = 'Plugin not enabled or configured.';
|
||||
$string['privacy:metadata:core_userkey'] = 'User\'s keys used to create auto-login key for the current user.';
|
||||
$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['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['qrcodetypeurl'] = 'QR code with site URL';
|
||||
$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['qrsiteadminsnotallowed'] = 'For security reasons login via QR code is not allowed for site administrators or if you are logged in as another user.';
|
||||
$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['register'] = 'Register';
|
||||
$string['remoteaddons'] = 'Remote add-ons';
|
||||
$string['responsivemainmenuitems'] = 'Responsive menu items';
|
||||
$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['showingcacheddata'] = 'Last synced: {$a->since} days ago ({$a->date}).';
|
||||
$string['showinglivedata'] = 'View live data in the <a href="{$a}" target="_blank">Moodle Apps Portal</a>.';
|
||||
$string['smartappbanners'] = 'App Banners';
|
||||
$string['subscription'] = 'Subscription';
|
||||
$string['subscriptioncreated'] = 'Start date';
|
||||
@@ -163,11 +190,11 @@ $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['switchaccount'] = 'Switch account';
|
||||
$string['taskrefreshsubscriptioncache'] = 'Refresh Moodle app subscription information cache';
|
||||
$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['upgradeforunlimit'] = 'Upgrade to {$a} for unlimited access.';
|
||||
$string['upgradetosubscription'] = 'Upgrade to {$a}';
|
||||
$string['upgradeyourplan'] = 'Upgrade your plan';
|
||||
$string['viewqrcode'] = 'View QR code';
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use core_admin\local\settings\autocomplete;
|
||||
use tool_mobile\api;
|
||||
|
||||
|
||||
if ($hassiteconfig || has_capability('moodle/site:configview', context_system::instance())) {
|
||||
// We should wait to the installation to finish since we depend on some configuration values that are set once
|
||||
@@ -46,42 +48,60 @@ if ($hassiteconfig || has_capability('moodle/site:configview', context_system::i
|
||||
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
|
||||
// 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);
|
||||
|
||||
$temp->add(new admin_setting_configtext('tool_mobile/apppolicy', new lang_string('apppolicy', 'tool_mobile'),
|
||||
new lang_string('apppolicy_help', 'tool_mobile'), '', PARAM_URL));
|
||||
$visiblename = get_string('mobileappsubscription', 'tool_mobile');
|
||||
|
||||
$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);
|
||||
$ispremiumplan = false;
|
||||
$subscriptiondata = api::get_subscription_information(true);
|
||||
if (is_array($subscriptiondata) && !empty($subscriptiondata['subscription']['plan'])) {
|
||||
$plan = \core_text::strtolower(trim($subscriptiondata['subscription']['plan']));
|
||||
$ispremiumplan = ($plan === 'premium' || $plan === 'bma');
|
||||
}
|
||||
if (!$ispremiumplan) {
|
||||
$upgradetext = strtoupper(get_string('upgradeyourplan', 'tool_mobile'));
|
||||
if (right_to_left()) {
|
||||
$visiblename .= ' ' . $upgradetext . ' 🚀';
|
||||
} else {
|
||||
$visiblename .= ' 🚀 ' . $upgradetext;
|
||||
}
|
||||
}
|
||||
|
||||
$hideappsubscription = (isset($CFG->disablemobileappsubscription) && !empty($CFG->disablemobileappsubscription));
|
||||
$hideappsubscription = $ismobilewsdisabled || $hideappsubscription;
|
||||
|
||||
$ADMIN->add(
|
||||
'mobileapp',
|
||||
new admin_externalpage(
|
||||
'mobileappsubscription',
|
||||
new lang_string('mobileappsubscription', 'tool_mobile'),
|
||||
$visiblename,
|
||||
"$CFG->wwwroot/$CFG->admin/tool/mobile/subscription.php",
|
||||
'moodle/site:configview',
|
||||
$hideappsubscription
|
||||
$ismobilewsdisabled
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
$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);
|
||||
|
||||
// Type of login.
|
||||
$temp = new admin_settingpage(
|
||||
'mobileauthentication',
|
||||
@@ -169,9 +189,7 @@ if ($hassiteconfig || has_capability('moodle/site:configview', context_system::i
|
||||
$ismobilewsdisabled
|
||||
);
|
||||
|
||||
if (!empty($featuresnotice)) {
|
||||
$temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeaturesappearance', '', $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));
|
||||
@@ -210,9 +228,7 @@ if ($hassiteconfig || has_capability('moodle/site:configview', context_system::i
|
||||
$ismobilewsdisabled
|
||||
);
|
||||
|
||||
if (!empty($featuresnotice)) {
|
||||
$temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeatures', '', $featuresnotice));
|
||||
}
|
||||
$temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeatures', '', $featuresnotice));
|
||||
|
||||
$temp->add(new admin_setting_heading('tool_mobile/logout',
|
||||
new lang_string('logout'), ''));
|
||||
|
||||
@@ -32,20 +32,15 @@ admin_externalpage_setup('mobileappsubscription', '', null, '');
|
||||
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();
|
||||
$subscriptiondata = \tool_mobile\api::get_subscription_information(false, true, 5);
|
||||
|
||||
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);
|
||||
$subscriptiondata = [];
|
||||
}
|
||||
$templatable = new \tool_mobile\output\subscription($subscriptiondata);
|
||||
echo $PAGE->get_renderer('tool_mobile')->render($templatable);
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
{{!
|
||||
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/subscribe_alert
|
||||
|
||||
Subscription-related alert notification for the Mobile tool.
|
||||
|
||||
The purpose of this template is to render an alert with an
|
||||
optional action button (for example, to register the site or
|
||||
complete subscription details).
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Context variables required for this template:
|
||||
* message A cleaned string (use clean_text()) to display.
|
||||
* extraclasses Additional classes to apply to the notification.
|
||||
* buttonstr Text for the optional action button.
|
||||
* buttonurl URL for the optional action button.
|
||||
* extrabuttonclasses Additional classes for the button element.
|
||||
* icon Optional icon definition (with "icon" and "component").
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"message": "Your site is not registered in Moodle Apps Portal.",
|
||||
"extraclasses": "alert-danger",
|
||||
"buttonstr": "Register",
|
||||
"buttonurl": "https://apps.moodle.com/register",
|
||||
"extrabuttonclasses": "bg-danger",
|
||||
"icon": {
|
||||
"icon": "i/circleinfo",
|
||||
"component": "core"
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
<div class="alert alert-warning {{extraclasses}} mt-4">
|
||||
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between">
|
||||
<div class="d-flex align-items-center mb-3 mb-md-0">
|
||||
{{#icon}}
|
||||
<span>
|
||||
{{#pix}}{{icon}}, {{component}}{{/pix}}
|
||||
</span>
|
||||
{{/icon}}
|
||||
<span>
|
||||
{{{ message }}}
|
||||
</span>
|
||||
</div>
|
||||
{{#buttonurl}}
|
||||
<a href="{{buttonurl}}"
|
||||
class="btn button-register fw-normal flex-shrink-0 bg-warning {{extrabuttonclasses}}">{{buttonstr}}</a>
|
||||
{{/buttonurl}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,204 +26,487 @@
|
||||
* none
|
||||
|
||||
Context variables required for this template:
|
||||
* registered - Whether the site is registered
|
||||
* appsportalurl - Apps portal url
|
||||
* subscription - Object with the current subscription information (plan, name, description, isfree, ispremium,
|
||||
features[], optional expiretime, etc.).
|
||||
* appsportalurl - URL of the Moodle Apps portal used in links.
|
||||
* appsportalupgradeurl - URL in the Apps portal to upgrade or manage the subscription.
|
||||
* messageoldcache - Optional string describing how old the cached subscription data is.
|
||||
* messagelivedata - Optional string indicating that live data is being shown.
|
||||
* messageunregisteredsite - Optional array of notification objects for unregistered sites.
|
||||
* messagefullaccess - Optional array of notification objects when the subscription has full access.
|
||||
* messagedeprecation - Optional array of notification objects for deprecated plans.
|
||||
* messagemissingcredentials - Optional array of notification objects when required credentials are missing.
|
||||
* messageserror, messagessuccess, messageswarning, messagesinfo, messageshtml - Optional collections of generic
|
||||
notifications rendered at the top of the page.
|
||||
* notifications - Optional object with push notification statistics (currentactivedevices, monthly[] items, etc.).
|
||||
* messagesnotificationsseemore - Optional string with a link to more detailed statistics.
|
||||
* messagesnonotifications - Optional array of notification objects when there are no statistics available.
|
||||
* tocompareplan - Optional object describing the plan used to compare against the current subscription.
|
||||
* messagenosubscriptioninfo - Optional array of notification objects used when no subscription information
|
||||
could be retrieved.
|
||||
|
||||
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": [
|
||||
{
|
||||
"registered": true,
|
||||
"subscription": {
|
||||
"plan": "free",
|
||||
"name": "Free",
|
||||
"description": "Free subscription",
|
||||
"informationurl": "http:\/\/192.168.1.134:8080\/local\/apps\/index.php",
|
||||
"features": [
|
||||
{
|
||||
"message" : "You have surpassed your monthly active user devices limit, some messages are beign ignored. We recommend you to upgrade to a paid plan."
|
||||
"name": "qrautomaticlogin",
|
||||
"description": "QR Login",
|
||||
"enabled": false,
|
||||
"humanstatus": "Not included"
|
||||
},
|
||||
{
|
||||
"name": "pushnotificationsdevices",
|
||||
"description": "Active devices for push notifications",
|
||||
"descriptionwithlimit": "50 active devices for push notifications",
|
||||
"enabled": true,
|
||||
"limit": 50,
|
||||
"limitstr": "50",
|
||||
"status": 0,
|
||||
"showstatus": 1,
|
||||
"humanstatus": "0 \/ 50",
|
||||
"showbar": 1,
|
||||
"percentage": 0,
|
||||
"barclass": "bg-success"
|
||||
},
|
||||
{
|
||||
"name": "coursesdownload",
|
||||
"description": "Offline access to content",
|
||||
"descriptionwithlimit": "Offline access to content (2 courses \/ device)",
|
||||
"enabled": true,
|
||||
"limit": 2,
|
||||
"limitstr": "2 courses \/ device",
|
||||
"humanstatus": "?\/2"
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
]
|
||||
"price": "* \u20ac",
|
||||
"isfree": true,
|
||||
"ispremium": false,
|
||||
"currentusage": []
|
||||
},
|
||||
"statistics": [],
|
||||
"appsportalurl": "https:\/\/apps.moodle.com",
|
||||
"appsportalupgradeurl": "https:\/\/apps.moodle.com",
|
||||
"messagemissingcredentials": [
|
||||
{
|
||||
"message": "Airnotifier credentials are missing, configure them in order to get notification statistics.",
|
||||
"extraclasses": "mt-4"
|
||||
}
|
||||
],
|
||||
"messagesinfo": [
|
||||
{
|
||||
"message": "This site is not registered in the App Plans Portal, create an account to manage your subscription."
|
||||
}
|
||||
],
|
||||
"notifications": {
|
||||
"totalsentnotifications": null,
|
||||
"totaldevices": null,
|
||||
"monthly": [
|
||||
{
|
||||
"year": 2026,
|
||||
"month": 2,
|
||||
"sentnotifications": 0,
|
||||
"ignorednotifications": 0,
|
||||
"newdevices": 0,
|
||||
"activedevices": 0,
|
||||
"limitreachedtime": null,
|
||||
"monthstr": "February"
|
||||
}
|
||||
],
|
||||
"currentactivedevices": 0
|
||||
},
|
||||
"messagesnotificationsseemore": [
|
||||
{
|
||||
"message": "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=\"https:\/\/apps.moodle.com\" target=\"_blank\">Moodle Apps Portal<\/a>.",
|
||||
"extraclasses": "mt-4 mb-0"
|
||||
}
|
||||
],
|
||||
"tocompareplan": {
|
||||
"plan": "premium",
|
||||
"name": "Premium",
|
||||
"description": "Premium subscription",
|
||||
"informationurl": "http:\/\/192.168.1.134:8080\/local\/apps\/index.php",
|
||||
"features": [
|
||||
{
|
||||
"name": "qrautomaticlogin",
|
||||
"description": "QR Login",
|
||||
"enabled": true,
|
||||
"humanstatus": "Included",
|
||||
"subscribedhumanstatus": "Not included",
|
||||
"subscribedenabled": "fa-times",
|
||||
"subscribeddecoration": "text-danger"
|
||||
},
|
||||
{
|
||||
"name": "pushnotificationsdevices",
|
||||
"description": "Active devices for push notifications",
|
||||
"descriptionwithlimit": "Unlimited push notifications devices",
|
||||
"enabled": true,
|
||||
"limit": "unlimited",
|
||||
"limitstr": "Unlimited",
|
||||
"humanstatus": "Included",
|
||||
"subscribedlimitstr": "50",
|
||||
"subscribedenabled": true,
|
||||
"subscribeddecoration": "text-primary"
|
||||
},
|
||||
{
|
||||
"name": "coursesdownload",
|
||||
"description": "Offline access to content",
|
||||
"descriptionwithlimit": "Unlimited course downloads",
|
||||
"enabled": true,
|
||||
"limit": "unlimited",
|
||||
"limitstr": "Unlimited",
|
||||
"humanstatus": "Included",
|
||||
"subscribedlimitstr": "2 courses \/ device",
|
||||
"subscribedenabled": true,
|
||||
"subscribeddecoration": "text-primary"
|
||||
}
|
||||
],
|
||||
"price": "* \u20ac"
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
{{#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>
|
||||
|
||||
<div id="subscription-overview" class="box container-xl">
|
||||
{{#messageserror}}
|
||||
{{> core/notification_error}}
|
||||
{{/messageserror}}
|
||||
{{#messagessuccess}}
|
||||
{{> core/notification_success}}
|
||||
{{/messagessuccess}}
|
||||
{{#messageswarning}}
|
||||
{{> core/notification_warning}}
|
||||
{{/messageswarning}}
|
||||
{{#messagesinfo}}
|
||||
{{> core/notification_info}}
|
||||
{{/messagesinfo}}
|
||||
{{#messageshtml}}
|
||||
{{{message}}}
|
||||
{{/messageshtml}}
|
||||
|
||||
<h2>{{#str}} plandetails, tool_mobile {{/str}}</h2>
|
||||
|
||||
{{#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>
|
||||
{{#isfree}}
|
||||
<div class="d-flex align-items-center gap-3 mt-3 mb-3">
|
||||
<h3 class="fw-bold ml-2">{{name}}</h3>
|
||||
<span class="ml-2 badge fw-normal bg-secondary fs-6 text-dark">{{#str}} limitedfeatures, tool_mobile {{/str}}</span>
|
||||
</div>
|
||||
{{#messageoldcache}}
|
||||
<span class="ml-2 text-muted">{{{messageoldcache}}} {{{messagelivedata}}}</span>
|
||||
{{/messageoldcache}}
|
||||
{{#messageunregisteredsite}}
|
||||
{{> tool_mobile/subscribe_alert}}
|
||||
{{/messageunregisteredsite}}
|
||||
{{/isfree}}
|
||||
{{^isfree}}
|
||||
<div class="d-flex justify-content-between align-items-center mt-3 mb-3">
|
||||
<div>
|
||||
<h3 class="fw-bold ml-2"
|
||||
{{#messageoldcache}}
|
||||
title="{{messageoldcache}}"
|
||||
{{/messageoldcache}}
|
||||
>
|
||||
{{name}}
|
||||
</h3>
|
||||
{{#expiretime}}
|
||||
<div class="ml-2 text-muted">
|
||||
{{#str}} planexpireson, tool_mobile {{/str}}{{#userdate}} {{expiretime}}, {{#str}} strftimedatetimeshort {{/str}} {{/userdate}}.
|
||||
{{#messageoldcache}}
|
||||
{{{messagelivedata}}}
|
||||
{{/messageoldcache}}
|
||||
|
||||
{{^registered}}
|
||||
{{# str }} subscriptionregister, tool_mobile, {{ appsportalurl }} {{/ str }}
|
||||
{{/registered}}
|
||||
</div>
|
||||
{{/expiretime}}
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ appsportalurl }}" class="btn btn-secondary">{{#str}} manage, tool_mobile {{/str}}</a>
|
||||
</div>
|
||||
</div>
|
||||
{{/isfree}}
|
||||
|
||||
<h3>{{# str }} subscriptionfeatures, tool_mobile {{/ str }}</h3>
|
||||
<hr>
|
||||
{{#messagefullaccess}}
|
||||
{{> core/notification_info}}
|
||||
{{/messagefullaccess}}
|
||||
|
||||
{{#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}}
|
||||
{{#messagedeprecation}}
|
||||
{{> core/notification_warning}}
|
||||
{{/messagedeprecation}}
|
||||
|
||||
<div class="row g-4 align-items-stretch">
|
||||
<div class="col-12 col-md-6 d-flex mb-4 mb-md-0">
|
||||
<div class="card flex-fill">
|
||||
<div class="card-body p-4">
|
||||
<h4 class="h4 fw-bold card-title">{{#str}} currentusage, tool_mobile {{/str}}</h4>
|
||||
{{^ispremium}}
|
||||
<p class="text-muted">{{#str}} upgradeforunlimit, tool_mobile, {{tocompareplan.name}} {{/str}}</p>
|
||||
{{/ispremium}}
|
||||
{{#features}}
|
||||
{{#limit}}
|
||||
{{#ispremium}}
|
||||
{{#showstatus}}
|
||||
<div class="d-flex justify-content-between text-muted mt-3">
|
||||
<span>{{description}}</span>
|
||||
<span class="text-muted">{{status}}</span>
|
||||
</div>
|
||||
{{/showstatus}}
|
||||
{{/ispremium}}
|
||||
{{^ispremium}}
|
||||
<div class="d-flex justify-content-between text-muted mt-3">
|
||||
<span>{{description}}</span>
|
||||
{{#showbar}}
|
||||
<span class="text-muted">{{humanstatus}}</span>
|
||||
{{/showbar}}
|
||||
{{^showbar}}
|
||||
<span class="text-muted">{{limitstr}}</span>
|
||||
{{/showbar}}
|
||||
</div>
|
||||
{{#showbar}}
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="progress flex-grow-1">
|
||||
<div class="progress-bar progress-bar-animated {{barclass}}" role="progressbar" style="width: {{percentage}}%" aria-valuenow="{{status}}" aria-valuemin="0" aria-valuemax="{{limit}}"></div>
|
||||
</div>
|
||||
<span class="small fw-semibold">{{percentage}}%</span>
|
||||
</div>
|
||||
{{/showbar}}
|
||||
{{^showbar}}
|
||||
<span class="text-muted small">{{#str}} devicelimitnotrack, tool_mobile {{/str }}</span>
|
||||
{{/showbar}}
|
||||
{{/ispremium}}
|
||||
{{/limit}}
|
||||
{{/features}}
|
||||
</div>
|
||||
</div>
|
||||
{{/showbar}}
|
||||
{{^showbar}}
|
||||
{{humanstatus}}
|
||||
{{/showbar}}
|
||||
</dd>
|
||||
{{/limit}}
|
||||
{{#message}}
|
||||
<span class="badge badge-{{type}}">{{message}}</span>
|
||||
{{/message}}
|
||||
</dl>
|
||||
{{/features}}
|
||||
</div>
|
||||
{{#tocompareplan}}
|
||||
<div class="col-12 col-md-6 d-flex">
|
||||
<div class="card bg-primary bg-opacity-25 border-0 flex-fill d-flex">
|
||||
<div class="card-body p-4 d-flex flex-column">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<h4 class="h4 fw-bold card-title">{{name}} {{#str}} features, tool_mobile {{/str}}</h4>
|
||||
<div class="text-end">
|
||||
<div class="fw-bold fs-4">{{price}}</div>
|
||||
<small>{{#str}} billedannually, tool_mobile {{/str}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-unstyled flex-grow-1">
|
||||
{{#features}}
|
||||
{{#limit}}
|
||||
{{#enabled}}
|
||||
<li class="mt-3"><i class="icon fa fa-infinity fa-fw" role="img" aria-label="{{#str}} unlimited {{/str}}"></i>{{{descriptionwithlimit}}}</li>
|
||||
{{/enabled}}
|
||||
{{/limit}}
|
||||
{{/features}}
|
||||
{{#features}}
|
||||
{{^limit}}
|
||||
{{#enabled}}
|
||||
<li class="mt-3"><i class="icon fa fa-check fa-fw " role="img" aria-label="{{#str}} yes {{/str}}"></i>{{{description}}}</li>
|
||||
{{/enabled}}
|
||||
{{/limit}}
|
||||
{{/features}}
|
||||
</ul>
|
||||
|
||||
{{#registered}}
|
||||
{{# str }} subscriptionsseemore, tool_mobile, {{ appsportalurl }} {{/ str }}
|
||||
{{/registered}}
|
||||
<a href="{{ appsportalupgradeurl }}" class="btn btn-primary w-100 mt-auto">
|
||||
{{#str}} upgradetosubscription, tool_mobile, {{tocompareplan.name}} {{/str}} – {{price}} / {{#str}} year {{/str}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/tocompareplan}}
|
||||
{{^tocompareplan}}
|
||||
<div class="col-12 col-md-6 d-flex">
|
||||
<div class="h-100 d-flex flex-column">
|
||||
<div class="card h-100 flex-grow-1">
|
||||
<div class="card-body p-4 p-4">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h4 class="h4 fw-bold card-title">{{name}} {{#str}} features, tool_mobile {{/str}}</h4>
|
||||
</div>
|
||||
<ul class="list-unstyled row row-cols-1 row-cols-md-2 g-2 flex-grow-1">
|
||||
{{#features}}
|
||||
{{#limit}}
|
||||
{{#enabled}}
|
||||
<li class="mt-3 col"><i class="icon fa fa-infinity fa-fw" role="img" aria-label="{{#str}} unlimited {{/str}}"></i>{{{descriptionwithlimit}}}</li>
|
||||
{{/enabled}}
|
||||
{{/limit}}
|
||||
{{/features}}
|
||||
{{#features}}
|
||||
{{^limit}}
|
||||
{{#enabled}}
|
||||
<li class="mt-3 col"><i class="icon fa fa-check fa-fw " role="img" aria-label="{{#str}} yes {{/str}}"></i>{{{description}}}</li>
|
||||
{{/enabled}}
|
||||
{{/limit}}
|
||||
{{/features}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/tocompareplan}}
|
||||
</div>
|
||||
|
||||
{{#tocompareplan}}
|
||||
<div class="card mt-4 feature-comparison-card">
|
||||
<div class="card-body p-4 d-flex flex-column">
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-content-between align-items-start mb-4">
|
||||
<h4 class="h4 fw-bold mb-0">{{#str}} featurecomparison, tool_mobile {{/str}}</h4>
|
||||
</div>
|
||||
|
||||
<!-- Table header -->
|
||||
<div class="row fw-semibold fw-bold border-bottom pb-2">
|
||||
<div class="col-6">
|
||||
{{#str}} feature, tool_mobile {{/str}}
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
{{subscription.name}}
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
{{tocompareplan.name}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-comparison-rows">
|
||||
{{#features}}
|
||||
{{#limit}}
|
||||
<!-- Feature rows -->
|
||||
<div class="row align-items-center py-2 border-bottom">
|
||||
<div class="col-6">
|
||||
{{description}}
|
||||
</div>
|
||||
<div class="col-3 text-center text-muted">
|
||||
{{subscribedlimitstr}}
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
<i class="icon fa fa-infinity fa-fw text-primary" aria-hidden="true"></i>{{{limitstr}}}
|
||||
</div>
|
||||
</div>
|
||||
{{/limit}}
|
||||
{{/features}}
|
||||
{{#features}}
|
||||
{{^limit}}
|
||||
<div class="row align-items-center py-2 border-bottom feature-comparison-row">
|
||||
<div class="col-6">
|
||||
{{description}}
|
||||
</div>
|
||||
<div class="col-3 text-center text-muted">
|
||||
<i class="icon fa {{{subscribedenabled}}} fa-fw {{{subscribeddecoration}}}" role="img" aria-label="{{{subscribedhumanstatus}}}"></i>
|
||||
</div>
|
||||
<div class="col-3 text-center">
|
||||
{{#enabled}}
|
||||
<i class="icon fa fa-check fa-fw text-primary" role="img" aria-label="{{{humanstatus}}}"></i>
|
||||
{{/enabled}}
|
||||
{{^enabled}}
|
||||
<i class="icon fa fa-times fa-fw text-danger" role="img" aria-label="{{{humanstatus}}}"></i>
|
||||
{{/enabled}}
|
||||
</div>
|
||||
</div>
|
||||
{{/limit}}
|
||||
{{/features}}
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<button type="button" class="btn btn-link p-0 feature-comparison-toggle d-inline-flex align-items-center">
|
||||
<span class="feature-comparison-toggle-text">{{#str}} showmore {{/str}}</span>
|
||||
<i class="icon fa fa-chevron-down fa-fw feature-comparison-toggle-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/tocompareplan}}
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-body p-4 d-flex flex-column">
|
||||
<div id="notifications-overview" class="box">
|
||||
<h4 class="h4 fw-bold card-title">{{#str}} notifications, tool_mobile {{/str}}</h4>
|
||||
{{#messagemissingcredentials}}
|
||||
{{> core/notification_warning}}
|
||||
{{/messagemissingcredentials}}
|
||||
{{^messagemissingcredentials}}
|
||||
{{#notifications}}
|
||||
{{#ignorednotificationswarning}}
|
||||
{{> tool_mobile/subscribe_alert}}
|
||||
{{/ignorednotificationswarning}}
|
||||
<dl>
|
||||
<dt>{{#str}} notificationscurrentactivedevices, tool_mobile {{/str}}</dt><dd>{{currentactivedevices}}</dd>
|
||||
</dl>
|
||||
<div class="overflow-scroll">
|
||||
<table id="notificationstable" class="table generaltable">
|
||||
<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">{{monthstr}}</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>
|
||||
</div>
|
||||
{{/notifications}}
|
||||
{{^notifications}}
|
||||
{{#messagesnonotifications}}
|
||||
{{> core/notification_warning}}
|
||||
{{/messagesnonotifications}}
|
||||
{{/notifications}}
|
||||
{{/messagemissingcredentials}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{{#messagesnotificationsseemore}}
|
||||
<div class="card-footer text-muted small">
|
||||
{{{messagesnotificationsseemore}}}
|
||||
</div>
|
||||
{{/messagesnotificationsseemore}}
|
||||
</div>
|
||||
{{/subscription}}
|
||||
{{^subscription}}
|
||||
<div class="g-4 align-items-stretch">
|
||||
<div class="mt-5">
|
||||
<div class="card flex-fill">
|
||||
<div class="card-body p-4">
|
||||
<h4 class="h4 fw-bold card-title">{{#str}} nosubscouldntretrieve, tool_mobile {{/str}}</h4>
|
||||
<p class="mt-4">{{#str}} nosubsoerror, tool_mobile{{/str}}</p>
|
||||
<div class="mt-3">
|
||||
<a href="" class="btn btn-primary">
|
||||
{{#pix}} i/reload, core {{/pix}}
|
||||
{{#str}} nosubstryagain, tool_mobile {{/str}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<a href="{{appsportalurl}}" target="_blank" rel="noopener" class="">
|
||||
<span class="text-decoration-underline">{{#str}} nosubsviewplan, tool_mobile {{/str}}</span>
|
||||
{{#pix}} i/externallink, core {{/pix}}
|
||||
</a>
|
||||
</div>
|
||||
{{#messagenosubscriptioninfo}}
|
||||
{{> core/notification_info}}
|
||||
{{/messagenosubscriptioninfo}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/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="table generaltable">
|
||||
<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>
|
||||
{{#js}}
|
||||
require(['tool_mobile/featurecomparison'], function(featurecomparison) {
|
||||
featurecomparison.init();
|
||||
});
|
||||
{{/js}}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
$plugin->version = 2025102200; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2026012700; // The current plugin version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2025092600; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
|
||||
$plugin->dependencies = [
|
||||
|
||||
Reference in New Issue
Block a user