Merge pull request #1973 from moodlehq/integration

Integration
main
Juan Leyva 2019-06-07 12:19:19 +02:00 committed by GitHub
commit 630d44c520
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
520 changed files with 12404 additions and 3698 deletions

View File

@ -3,14 +3,14 @@ dist: xenial
group: edge
language: node_js
node_js: stable
node_js: 11
before_cache:
- rm -rf $HOME/.cache/electron-builder/wine
cache:
directories:
- node_modules
- $HOME/.npm
- $HOME/.cache/electron
- $HOME/.cache/electron-builder

View File

@ -11,15 +11,15 @@ EXPOSE 35729
# Port 53703 is the Chrome dev logger port.
EXPOSE 53703
# MoodleMobile uses Cordova, Ionic, and Gulp.
RUN npm install -g cordova ionic gulp && rm -rf /root/.npm
# MoodleMobile uses Ionic and Gulp.
RUN npm i -g ionic gulp && rm -rf /root/.npm
WORKDIR /app
COPY . /app
# The setup script will handle npm installation, cordova setup, and gulp setup.
RUN npm run setup && rm -rf /root/.npm
# Install npm libraries and run gulp to initialize the project.
RUN npm install && gulp && rm -rf /root/.npm
# Provide a Healthcheck command for easier use in CI.
HEALTHCHECK --interval=10s --timeout=3s --start-period=30s CMD curl -f http://localhost:8100 || exit 1

View File

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<widget id="com.moodle.moodlemobile" version="3.6.1" xmlns="http://www.w3.org/ns/widgets" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<widget id="com.moodle.moodlemobile" version="3.7.0" xmlns="http://www.w3.org/ns/widgets" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>Moodle</name>
<description>Moodle official app</description>
<author email="mobile@moodle.com" href="http://moodle.com">Moodle Mobile team</author>
@ -37,6 +37,7 @@
<preference name="SplashMaintainAspectRatio" value="true" />
<preference name="SplashShowOnlyFirstTime" value="false" />
<preference name="LoadUrlTimeoutValue" value="60000" />
<preference name="CustomURLSchemePluginClearsAndroidIntent" value="true" />
<feature name="StatusBar">
<param name="ios-package" onload="true" value="CDVStatusBar" />
</feature>
@ -112,14 +113,14 @@
<icon height="1024" src="resources/ios/icon/icon-1024.png" width="1024" />
<splash height="2732" src="resources/ios/splash/Default@2x~universal~anyany.png" width="2732" />
</platform>
<plugin name="com-darryncampbell-cordova-plugin-intent" spec="1.1.5" />
<plugin name="cordova-android-support-gradle-release" spec="2.0.1">
<plugin name="com-darryncampbell-cordova-plugin-intent" spec="1.1.7" />
<plugin name="cordova-android-support-gradle-release" spec="3.0.0">
<variable name="ANDROID_SUPPORT_VERSION" value="27.1.0" />
</plugin>
<plugin name="cordova-clipboard" spec="1.2.1" />
<plugin name="cordova-plugin-badge" spec="0.8.8" />
<plugin name="cordova-plugin-camera" spec="4.0.3" />
<plugin name="cordova-plugin-customurlscheme" spec="4.3.0">
<plugin name="cordova-plugin-customurlscheme" spec="4.4.0">
<variable name="URL_SCHEME" value="moodlemobile" />
</plugin>
<plugin name="cordova-plugin-device" spec="2.0.2" />
@ -129,7 +130,7 @@
<plugin name="cordova-plugin-globalization" spec="1.11.0" />
<plugin name="cordova-plugin-inappbrowser" spec="3.0.0" />
<plugin name="cordova-plugin-ionic-keyboard" spec="2.1.3" />
<plugin name="cordova-plugin-local-notification" spec="0.9.0-beta.3" />
<plugin name="cordova-plugin-local-notification" spec="https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle" />
<plugin name="cordova-plugin-media-capture" spec="3.0.2" />
<plugin name="cordova-plugin-network-information" spec="2.0.1" />
<plugin name="cordova-plugin-screen-orientation" spec="3.0.1" />
@ -139,13 +140,19 @@
<plugin name="cordova-plugin-zip" spec="3.1.0" />
<plugin name="cordova-sqlite-storage" spec="2.6.0" />
<plugin name="nl.kingsquare.cordova.background-audio" spec="1.0.1" />
<plugin name="phonegap-plugin-push" spec="https://github.com/moodlemobile/phonegap-plugin-push.git#moodle-v2">
<plugin name="phonegap-plugin-push" spec="https://github.com/moodlemobile/phonegap-plugin-push.git#moodle-v3">
<variable name="ANDROID_SUPPORT_V13_VERSION" value="27.+" />
<variable name="FCM_VERSION" value="17.0.+" />
<variable name="FCM_VERSION" value="17.5.+" />
</plugin>
<edit-config file="AndroidManifest.xml" mode="merge" target="/manifest/application/activity[@android:name='MainActivity']">
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|screenLayout|smallestScreenSize" android:debuggable="true" />
</edit-config>
<config-file parent="/manifest/application" target="AndroidManifest.xml">
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
</config-file>
<config-file parent="FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED" target="*-Info.plist">
<string>YES</string>
</config-file>
<engine name="android" spec="7.1.2" />
<engine name="ios" spec="4.5.5" />
</widget>

View File

@ -6,7 +6,7 @@
<Identity Name="3312ADB7.MoodleDesktop"
ProcessorArchitecture="x64"
Publisher="CN=33CDCDF6-1EB5-4827-9897-ED25C91A32F6"
Version="3.6.1.0" />
Version="3.7.0.0" />
<Properties>
<DisplayName>Moodle Desktop</DisplayName>
<PublisherDisplayName>Moodle Pty Ltd.</PublisherDisplayName>

View File

@ -6,6 +6,7 @@ const url = require('url');
const fs = require('fs');
const os = require('os');
const userAgent = 'MoodleMobile';
const isMac = os.platform().indexOf('darwin') != -1;
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
@ -68,6 +69,14 @@ function createWindow() {
// Append some text to the user agent.
mainWindow.webContents.setUserAgent(mainWindow.webContents.getUserAgent() + ' ' + userAgent);
// Add shortcut to open dev tools: Cmd + Option + I in MacOS, Ctrl + Shift + I in Windows/Linux.
mainWindow.webContents.on('before-input-event', function(e, input) {
if (input.type == 'keyDown' && !input.isAutoRepeat && input.code == 'KeyI' &&
((isMac && input.alt && input.meta) || (!isMac && input.shift && input.control))) {
mainWindow.webContents.toggleDevTools();
}
}, true)
}
// Make sure that only a single instance of the app is running.
@ -75,7 +84,7 @@ function createWindow() {
// See https://github.com/electron/electron/issues/15958
var gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock && os.platform().indexOf('darwin') == -1) {
if (!gotTheLock && !isMac) {
// It's not the main instance of the app, kill it.
app.exit();
return;
@ -221,22 +230,18 @@ function setAppMenu() {
submenu: [
{
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
role: 'cut'
},
{
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
role: 'copy'
},
{
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
role: 'paste'
},
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
role: 'selectall'
}
]

View File

@ -8,6 +8,8 @@ var gulp = require('gulp'),
gutil = require('gulp-util'),
flatten = require('gulp-flatten'),
npmPath = require('path'),
concat = require('gulp-concat'),
bufferFrom = require('buffer-from')
File = gutil.File,
exec = require('child_process').exec,
license = '' +
@ -113,7 +115,7 @@ function treatMergedData(data) {
mergedOrdered[k] = merged[k];
});
return new Buffer(JSON.stringify(mergedOrdered, null, 4));
return bufferFrom(JSON.stringify(mergedOrdered, null, 4));
}
/**
@ -257,7 +259,7 @@ gulp.task('config', function(done) {
contents += '}\n';
file.contents = new Buffer(contents);
file.contents = bufferFrom(contents);
this.emit('data', file);
}))
.pipe(rename('configconstants.ts'))
@ -296,3 +298,129 @@ gulp.task('copy-component-templates', function(done) {
.on('end', done);
});
/**
* Finds the file and returns its content.
*
* @param {string} capture Import file path.
* @param {string} baseDir Directory where the file was found.
* @param {string} paths Alternative paths where to find the imports.
* @param {Array} parsedFiles Yet parsed files to reduce size of the result.
* @return {string} Partially combined scss.
*/
function getReplace(capture, baseDir, paths, parsedFiles) {
var parse = path.parse(path.resolve(baseDir, capture + '.scss'));
var file = parse.dir + '/' + parse.name;
if (!fs.existsSync(file + '.scss')) {
// File not found, might be a partial file.
file = parse.dir + '/_' + parse.name;
}
// If file still not found, try to find the file in the alternative paths.
var x = 0;
while (!fs.existsSync(file + '.scss') && paths.length > x) {
parse = path.parse(path.resolve(paths[x], capture + '.scss'));
file = parse.dir + '/' + parse.name;
x++;
}
file = file + '.scss';
if (!fs.existsSync(file)) {
// File not found. Leave the import there.
console.log('File "' + capture + '" not found');
return '@import "' + capture + '";';
}
if (parsedFiles.indexOf(file) >= 0) {
console.log('File "' + capture + '" already parsed');
// File was already parsed, leave the import commented.
return '// @import "' + capture + '";';
}
parsedFiles.push(file);
var text = fs.readFileSync(file);
// Recursive call.
return scssCombine(text, parse.dir, paths, parsedFiles);
}
/**
* Combine scss files with its imports
*
* @param {string} content Scss string to read.
* @param {string} baseDir Directory where the file was found.
* @param {string} paths Alternative paths where to find the imports.
* @param {Array} parsedFiles Yet parsed files to reduce size of the result.
* @return {string} Scss string with the replaces done.
*/
function scssCombine(content, baseDir, paths, parsedFiles) {
// Content is a Buffer, convert to string.
if (typeof content != "string") {
content = content.toString();
}
// Search of single imports.
var regex = /@import[ ]*['"](.*)['"][ ]*;/g;
if (regex.test(content)) {
return content.replace(regex, function(m, capture) {
if (capture == "bmma") {
return m;
}
return getReplace(capture, baseDir, paths, parsedFiles);
});
}
// Search of multiple imports.
regex = /@import(?:[ \n]+['"](.*)['"][,]?[ \n]*)+;/gm;
if (regex.test(content)) {
return content.replace(regex, function(m, capture) {
var text = "";
// Divide the import into multiple files.
regex = /['"]([^'"]*)['"]/g;
var captures = m.match(regex);
for (var x in captures) {
text += getReplace(captures[x].replace(/['"]+/g, ''), baseDir, paths, parsedFiles) + "\n";
}
return text;
});
}
return content;
}
gulp.task('combine-scss', function(done) {
var paths = [
'node_modules/ionic-angular/themes/',
'node_modules/font-awesome/scss/',
'node_modules/ionicons/dist/scss/'
];
var parsedFiles = [];
gulp.src([
'./src/theme/variables.scss',
'./node_modules/ionic-angular/themes/ionic.globals.*.scss',
'./node_modules/ionic-angular/themes/ionic.components.scss',
'./src/**/*.scss']) // define a source files
.pipe(through(function(file, encoding, callback) {
if (file.isNull()) {
return;
}
parsedFiles.push(file);
file.contents = bufferFrom(scssCombine(file.contents, path.dirname(file.path), paths, parsedFiles));
this.emit('data', file);
})) // combine them based on @import and save it to stream
.pipe(concat('combined.scss')) // concat the stream output in single file
.pipe(gulp.dest('.')) // save file to destination.
.on('end', done);
});

107
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "moodlemobile",
"version": "3.6.1",
"version": "3.7.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -2473,9 +2473,9 @@
"dev": true
},
"com-darryncampbell-cordova-plugin-intent": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/com-darryncampbell-cordova-plugin-intent/-/com-darryncampbell-cordova-plugin-intent-1.1.1.tgz",
"integrity": "sha512-h+V54+qCFY1h5csX8lAKTxBn5DdbP/8/sm7vS6X0WZPI+OTKycxeoJC+oGtPHhlvTh4gSEVW5/MkDqANRcmaug=="
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/com-darryncampbell-cordova-plugin-intent/-/com-darryncampbell-cordova-plugin-intent-1.1.7.tgz",
"integrity": "sha512-e+CIaOTpZ7r178tmCijZcm/o5nJIWVnQaUrwm5xwX1zc5zutVCtz1oH3xqq6gzNk05C9i7n96xdenODHMYpiMw=="
},
"combined-stream": {
"version": "1.0.6",
@ -2521,6 +2521,15 @@
"typedarray": "^0.0.6"
}
},
"concat-with-sourcemaps": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz",
"integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==",
"dev": true,
"requires": {
"source-map": "^0.6.1"
}
},
"console-browserify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
@ -2808,18 +2817,18 @@
}
},
"cordova-android-support-gradle-release": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/cordova-android-support-gradle-release/-/cordova-android-support-gradle-release-2.0.1.tgz",
"integrity": "sha512-HlX75PN8b9y3LIlAFLQspSbO7dr7hTRi2/n4A2Hz4AHb7NxiVt6VU+6j+JcseDveVdddh1sKMZd0xPtFMVNjXA==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cordova-android-support-gradle-release/-/cordova-android-support-gradle-release-3.0.0.tgz",
"integrity": "sha512-vyiqQ6N9Qb+4xRizWSpUX/LyJ1HaDN0piWc8xoS9Hx9YodIS3vyi1UpQyfLQmCixoeLVcRieKXjuSMXnUrv1dw==",
"requires": {
"semver": "5.1.0",
"xml2js": "~0.4.19"
"q": "^1.4.1",
"semver": "5.6.0"
},
"dependencies": {
"semver": {
"version": "5.1.0",
"resolved": "http://registry.npmjs.org/semver/-/semver-5.1.0.tgz",
"integrity": "sha1-hfLPhVBGXE3wAM99hvawVBBqueU="
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
}
}
},
@ -3173,9 +3182,8 @@
"integrity": "sha512-6ucQ6FdlLdBm8kJfFnzozmBTjru/0xekHP/dAhjoCZggkGRlgs8TsUJFkxa/bV+qi7Dlo50JjmpE4UMWAO+aOQ=="
},
"cordova-plugin-local-notification": {
"version": "0.9.0-beta.3",
"resolved": "https://registry.npmjs.org/cordova-plugin-local-notification/-/cordova-plugin-local-notification-0.9.0-beta.3.tgz",
"integrity": "sha512-L3Z1velxrkm9nHFcvLnMgBPZjKFt6hwM6hn1lA+JFwIR26Yw6UF72z+/lRMBclAcOxBIDYCqeaLgvezmajjuEg=="
"version": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#5b2f3073a1c1fb39cad3566be792445c343db2c6",
"from": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle"
},
"cordova-plugin-media-capture": {
"version": "3.0.2",
@ -4082,9 +4090,9 @@
}
},
"esprima": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
"integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
},
"esrecurse": {
@ -5253,9 +5261,9 @@
}
},
"fstream": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
"integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
@ -5870,6 +5878,17 @@
"through2": "~2.0.1"
}
},
"gulp-concat": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz",
"integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=",
"dev": true,
"requires": {
"concat-with-sourcemaps": "^1.0.0",
"through2": "^2.0.0",
"vinyl": "^2.0.0"
}
},
"gulp-flatten": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/gulp-flatten/-/gulp-flatten-0.4.0.tgz",
@ -6672,9 +6691,9 @@
"dev": true
},
"js-yaml": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
"integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==",
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
@ -8560,7 +8579,7 @@
},
"pegjs": {
"version": "0.10.0",
"resolved": "http://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz",
"resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz",
"integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0="
},
"performance-now": {
@ -8575,8 +8594,8 @@
"integrity": "sha512-1wvc3iQOQpEBaQbXgLxA2JUiLSQ2azdF/bF29ghXDiQJWSpQ1BF8gSuqttM8WZoj081Ps8OKL0gYxdDBkFNPqA=="
},
"phonegap-plugin-push": {
"version": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#9b1d9fe575d1f21b517327c480e7fe0f73280e7a",
"from": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle-v2",
"version": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#cf8101e86adb774ae1d7ad6b65fb9d8802673f4b",
"from": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle-v3",
"requires": {
"babel-plugin-add-header-comment": "^1.0.3",
"install": "^0.8.2"
@ -8796,6 +8815,11 @@
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
"dev": true
},
"q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
},
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
@ -9588,7 +9612,8 @@
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"dev": true
},
"scss-tokenizer": {
"version": "0.2.3",
@ -10179,14 +10204,28 @@
"dev": true
},
"tar": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
"integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
"integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
"dev": true,
"requires": {
"block-stream": "*",
"fstream": "^1.0.2",
"fstream": "^1.0.12",
"inherits": "2"
},
"dependencies": {
"fstream": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"inherits": "~2.0.0",
"mkdirp": ">=0.5 0",
"rimraf": "2"
}
}
}
},
"temp-file": {
@ -12174,6 +12213,7 @@
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
"dev": true,
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~9.0.1"
@ -12182,7 +12222,8 @@
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=",
"dev": true
},
"xmldom": {
"version": "0.1.27",

View File

@ -1,6 +1,6 @@
{
"name": "moodlemobile",
"version": "3.6.1",
"version": "3.7.0",
"description": "The official app for Moodle.",
"author": {
"name": "Moodle Pty Ltd.",
@ -28,6 +28,7 @@
"build": "ionic-app-scripts build",
"lint": "ionic-app-scripts lint",
"ionic:build": "node --max-old-space-size=16384 ./node_modules/@ionic/app-scripts/bin/ionic-app-scripts.js build",
"ionic:serve:before": "gulp",
"ionic:serve": "gulp watch | ionic-app-scripts serve",
"ionic:build:before": "gulp",
"ionic:watch:before": "gulp",
@ -78,9 +79,9 @@
"@types/node": "8.10.19",
"@types/promise.prototype.finally": "2.0.2",
"chart.js": "2.7.2",
"com-darryncampbell-cordova-plugin-intent": "1.1.1",
"com-darryncampbell-cordova-plugin-intent": "1.1.7",
"cordova-android": "7.1.2",
"cordova-android-support-gradle-release": "2.0.1",
"cordova-android-support-gradle-release": "3.0.0",
"cordova-clipboard": "1.2.1",
"cordova-ios": "4.5.5",
"cordova-plugin-badge": "0.8.8",
@ -93,7 +94,7 @@
"cordova-plugin-globalization": "1.11.0",
"cordova-plugin-inappbrowser": "3.0.0",
"cordova-plugin-ionic-keyboard": "2.1.3",
"cordova-plugin-local-notification": "0.9.0-beta.3",
"cordova-plugin-local-notification": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle",
"cordova-plugin-media-capture": "3.0.2",
"cordova-plugin-network-information": "2.0.1",
"cordova-plugin-screen-orientation": "3.0.1",
@ -111,7 +112,7 @@
"moment": "2.22.2",
"nl.kingsquare.cordova.background-audio": "1.0.1",
"phonegap-plugin-multidex": "1.0.0",
"phonegap-plugin-push": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle-v2",
"phonegap-plugin-push": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle-v3",
"promise.prototype.finally": "3.1.0",
"rxjs": "5.5.11",
"sw-toolbox": "3.6.0",
@ -125,6 +126,7 @@
"electron-rebuild": "1.8.1",
"gulp": "4.0.0",
"gulp-clip-empty-files": "0.1.2",
"gulp-concat": "2.6.1",
"gulp-flatten": "0.4.0",
"gulp-rename": "1.3.0",
"gulp-slash": "1.1.3",
@ -227,6 +229,9 @@
},
"snap": {
"confinement": "classic"
},
"nsis": {
"deleteAppDataOnUninstall": true
}
}
}

BIN
resources/android/icon.png 100755 → 100644

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1 +1 @@
35bf4a4bbe8ec8e40270338abd041adc
5e8ac0ef8768e0fad3284434d24064f8

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1 +1 @@
3ac2bf0bded2c5da7d213095c12ead29
5225afcaf865b3e218501903bef688e0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -1 +1 @@
32dca3a9cd3c8e9d241f68a0850d2ace
4d2128e5cc9659b321956c1178057980

View File

@ -6,7 +6,7 @@ if [ $TRAVIS_BRANCH == 'integration' ] || [ $TRAVIS_BRANCH == 'master' ] || [ $T
./build_lang.sh
cd ..
if [ $TRAVIS_BRANCH == 'master' ] && [ ! -z $GIT_TOKEN ] ; then
if [ "$TRAVIS_BRANCH" == 'master' ] && [ ! -z $GIT_TOKEN ] ; then
git remote set-url origin https://$GIT_TOKEN@github.com/$TRAVIS_REPO_SLUG.git
git fetch -q origin
git add src/assets/lang
@ -17,7 +17,7 @@ if [ $TRAVIS_BRANCH == 'integration' ] || [ $TRAVIS_BRANCH == 'master' ] || [ $T
version=`grep versionname src/config.json| cut -d: -f2|cut -d'"' -f2`
date=`date +%Y%m%d`'00'
pushd ../../moodle-local_moodlemobileapp
pushd ../moodle-local_moodlemobileapp
sed -ie "s/release[ ]*=[ ]*'[^']*';/release = '$version';/1" version.php
sed -ie "s/version[ ]*=[ ]*[0-9]*;/version = $date;/1" version.php
rm version.phpe

View File

@ -111,9 +111,11 @@
"addon.competency.myplans": "tool_lp",
"addon.competency.noactivities": "tool_lp",
"addon.competency.nocompetencies": "local_moodlemobileapp",
"addon.competency.nocompetenciesincourse": "tool_lp",
"addon.competency.nocrossreferencedcompetencies": "tool_lp",
"addon.competency.noevidence": "tool_lp",
"addon.competency.noplanswerecreated": "tool_lp",
"addon.competency.nouserplanswithcompetency": "competency",
"addon.competency.path": "tool_lp",
"addon.competency.planstatusactive": "competency",
"addon.competency.planstatuscomplete": "competency",
@ -126,6 +128,7 @@
"addon.competency.reviewstatus": "tool_lp",
"addon.competency.status": "tool_lp",
"addon.competency.template": "tool_lp",
"addon.competency.uponcoursecompletion": "tool_lp",
"addon.competency.usercompetencystatus_idle": "competency",
"addon.competency.usercompetencystatus_inreview": "competency",
"addon.competency.usercompetencystatus_waitingforreview": "competency",
@ -177,9 +180,12 @@
"addon.messages.contactname": "local_moodlemobileapp",
"addon.messages.contactrequestsent": "message",
"addon.messages.contacts": "message",
"addon.messages.conversationactions": "message",
"addon.messages.decline": "message",
"addon.messages.deleteallconfirm": "message",
"addon.messages.deleteallselfconfirm": "message",
"addon.messages.deleteconversation": "message",
"addon.messages.deleteforeveryone": "message",
"addon.messages.deletemessage": "local_moodlemobileapp",
"addon.messages.deletemessageconfirmation": "local_moodlemobileapp",
"addon.messages.errordeletemessage": "local_moodlemobileapp",
@ -196,6 +202,8 @@
"addon.messages.messagenotsent": "local_moodlemobileapp",
"addon.messages.messagepreferences": "message",
"addon.messages.messages": "message",
"addon.messages.muteconversation": "message",
"addon.messages.mutedconversation": "message",
"addon.messages.newmessage": "message",
"addon.messages.newmessages": "local_moodlemobileapp",
"addon.messages.nocontactrequests": "message",
@ -214,9 +222,8 @@
"addon.messages.requests": "moodle",
"addon.messages.requirecontacttomessage": "message",
"addon.messages.searchcombined": "message",
"addon.messages.searchnocontactsfound": "message",
"addon.messages.searchnomessagesfound": "message",
"addon.messages.searchnononcontactsfound": "message",
"addon.messages.selfconversation": "message",
"addon.messages.selfconversationdefaultmessage": "message",
"addon.messages.sendcontactrequest": "message",
"addon.messages.showdeletemessages": "local_moodlemobileapp",
"addon.messages.type_blocked": "local_moodlemobileapp",
@ -227,6 +234,7 @@
"addon.messages.unabletomessage": "message",
"addon.messages.unblockuser": "message",
"addon.messages.unblockuserconfirm": "message",
"addon.messages.unmuteconversation": "message",
"addon.messages.useentertosend": "message",
"addon.messages.useentertosenddescdesktop": "local_moodlemobileapp",
"addon.messages.useentertosenddescmac": "local_moodlemobileapp",
@ -448,6 +456,8 @@
"addon.mod_feedback.feedbackclose": "feedback",
"addon.mod_feedback.feedbackopen": "feedback",
"addon.mod_feedback.mapcourses": "feedback",
"addon.mod_feedback.maximal": "feedback",
"addon.mod_feedback.minimal": "feedback",
"addon.mod_feedback.mode": "feedback",
"addon.mod_feedback.modulenameplural": "feedback",
"addon.mod_feedback.next_page": "feedback",
@ -474,11 +484,20 @@
"addon.mod_forum.addanewdiscussion": "forum",
"addon.mod_forum.addanewquestion": "forum",
"addon.mod_forum.addanewtopic": "forum",
"addon.mod_forum.addtofavourites": "forum",
"addon.mod_forum.advanced": "moodle",
"addon.mod_forum.cannotadddiscussion": "forum",
"addon.mod_forum.cannotadddiscussionall": "forum",
"addon.mod_forum.cannotcreatediscussion": "forum",
"addon.mod_forum.couldnotadd": "forum",
"addon.mod_forum.cutoffdatereached": "forum",
"addon.mod_forum.discussion": "forum",
"addon.mod_forum.discussionlistsortbycreatedasc": "forum",
"addon.mod_forum.discussionlistsortbycreateddesc": "forum",
"addon.mod_forum.discussionlistsortbylastpostasc": "forum",
"addon.mod_forum.discussionlistsortbylastpostdesc": "forum",
"addon.mod_forum.discussionlistsortbyrepliesasc": "forum",
"addon.mod_forum.discussionlistsortbyrepliesdesc": "forum",
"addon.mod_forum.discussionlocked": "forum",
"addon.mod_forum.discussionpinned": "forum",
"addon.mod_forum.discussionsubscription": "forum",
@ -487,8 +506,12 @@
"addon.mod_forum.erroremptysubject": "forum",
"addon.mod_forum.errorgetforum": "local_moodlemobileapp",
"addon.mod_forum.errorgetgroups": "local_moodlemobileapp",
"addon.mod_forum.errorposttoallgroups": "local_moodlemobileapp",
"addon.mod_forum.favouriteupdated": "forum",
"addon.mod_forum.forumnodiscussionsyet": "local_moodlemobileapp",
"addon.mod_forum.group": "local_moodlemobileapp",
"addon.mod_forum.lockdiscussion": "forum",
"addon.mod_forum.lockupdated": "forum",
"addon.mod_forum.message": "forum",
"addon.mod_forum.modeflatnewestfirst": "forum",
"addon.mod_forum.modeflatoldestfirst": "forum",
@ -496,12 +519,23 @@
"addon.mod_forum.modulenameplural": "forum",
"addon.mod_forum.numdiscussions": "local_moodlemobileapp",
"addon.mod_forum.numreplies": "local_moodlemobileapp",
"addon.mod_forum.pindiscussion": "forum",
"addon.mod_forum.pinupdated": "forum",
"addon.mod_forum.postisprivatereply": "forum",
"addon.mod_forum.posttoforum": "forum",
"addon.mod_forum.posttomygroups": "forum",
"addon.mod_forum.privatereply": "forum",
"addon.mod_forum.re": "forum",
"addon.mod_forum.refreshdiscussions": "local_moodlemobileapp",
"addon.mod_forum.refreshposts": "local_moodlemobileapp",
"addon.mod_forum.removefromfavourites": "forum",
"addon.mod_forum.reply": "forum",
"addon.mod_forum.replyplaceholder": "forum",
"addon.mod_forum.subject": "forum",
"addon.mod_forum.thisforumhasduedate": "forum",
"addon.mod_forum.thisforumisdue": "forum",
"addon.mod_forum.unlockdiscussion": "forum",
"addon.mod_forum.unpindiscussion": "forum",
"addon.mod_forum.unread": "forum",
"addon.mod_forum.unreadpostsnumber": "forum",
"addon.mod_glossary.addentry": "glossary",
@ -632,6 +666,7 @@
"addon.mod_quiz.attemptquiznow": "quiz",
"addon.mod_quiz.attemptstate": "quiz",
"addon.mod_quiz.cannotsubmitquizdueto": "local_moodlemobileapp",
"addon.mod_quiz.clearchoice": "qtype_multichoice",
"addon.mod_quiz.comment": "quiz",
"addon.mod_quiz.completedon": "quiz",
"addon.mod_quiz.confirmclose": "quiz",
@ -878,6 +913,11 @@
"addon.notifications.notifications": "local_moodlemobileapp",
"addon.notifications.playsound": "local_moodlemobileapp",
"addon.notifications.therearentnotificationsyet": "local_moodlemobileapp",
"addon.storagemanager.deletecourse": "local_moodlemobileapp",
"addon.storagemanager.deletedatafrom": "local_moodlemobileapp",
"addon.storagemanager.info": "local_moodlemobileapp",
"addon.storagemanager.managestorage": "local_moodlemobileapp",
"addon.storagemanager.storageused": "local_moodlemobileapp",
"assets.countries.AD": "countries",
"assets.countries.AE": "countries",
"assets.countries.AF": "countries",
@ -1184,6 +1224,7 @@
"core.agelocationverification": "moodle",
"core.ago": "message",
"core.all": "moodle",
"core.allgroups": "moodle",
"core.allparticipants": "moodle",
"core.android": "local_moodlemobileapp",
"core.answer": "moodle",
@ -1229,6 +1270,7 @@
"core.contentlinks.confirmurlothersite": "local_moodlemobileapp",
"core.contentlinks.errornoactions": "local_moodlemobileapp",
"core.contentlinks.errornosites": "local_moodlemobileapp",
"core.contentlinks.errorredirectothersite": "local_moodlemobileapp",
"core.continue": "moodle",
"core.copiedtoclipboard": "local_moodlemobileapp",
"core.course": "moodle",
@ -1237,9 +1279,11 @@
"core.course.activitynotyetviewablesiteupgradeneeded": "local_moodlemobileapp",
"core.course.allsections": "local_moodlemobileapp",
"core.course.askadmintosupport": "local_moodlemobileapp",
"core.course.availablespace": "local_moodlemobileapp",
"core.course.confirmdeletemodulefiles": "local_moodlemobileapp",
"core.course.confirmdownload": "local_moodlemobileapp",
"core.course.confirmdownloadunknownsize": "local_moodlemobileapp",
"core.course.confirmlimiteddownload": "local_moodlemobileapp",
"core.course.confirmpartialdownloadsize": "local_moodlemobileapp",
"core.course.contents": "local_moodlemobileapp",
"core.course.couldnotloadsectioncontent": "local_moodlemobileapp",
@ -1251,6 +1295,8 @@
"core.course.errorgetmodule": "local_moodlemobileapp",
"core.course.hiddenfromstudents": "moodle",
"core.course.hiddenoncoursepage": "moodle",
"core.course.insufficientavailablequota": "local_moodlemobileapp",
"core.course.insufficientavailablespace": "local_moodlemobileapp",
"core.course.manualcompletionnotsynced": "local_moodlemobileapp",
"core.course.nocontentavailable": "local_moodlemobileapp",
"core.course.overriddennotice": "grades",
@ -1319,6 +1365,7 @@
"core.dismiss": "local_moodlemobileapp",
"core.done": "survey",
"core.download": "moodle",
"core.downloaded": "local_moodlemobileapp",
"core.downloading": "local_moodlemobileapp",
"core.edit": "moodle",
"core.emptysplit": "local_moodlemobileapp",
@ -1342,6 +1389,7 @@
"core.favourites": "moodle",
"core.filename": "repository",
"core.filenameexist": "local_moodlemobileapp",
"core.filenotfound": "resource",
"core.fileuploader.addfiletext": "repository",
"core.fileuploader.audio": "local_moodlemobileapp",
"core.fileuploader.camera": "local_moodlemobileapp",
@ -1555,6 +1603,7 @@
"core.nopermissions": "error",
"core.noresults": "moodle",
"core.notapplicable": "local_moodlemobileapp",
"core.notenrolledprofile": "moodle",
"core.notice": "moodle",
"core.notingroup": "moodle",
"core.notsent": "local_moodlemobileapp",
@ -1596,14 +1645,14 @@
"core.question.questionno": "question",
"core.question.requiresgrading": "question",
"core.quotausage": "moodle",
"core.rating.aggregateavg": "moodle",
"core.rating.aggregatecount": "moodle",
"core.rating.aggregatemax": "moodle",
"core.rating.aggregatemin": "moodle",
"core.rating.aggregatesum": "moodle",
"core.rating.noratings": "moodle",
"core.rating.rating": "moodle",
"core.rating.ratings": "moodle",
"core.rating.aggregateavg": "rating",
"core.rating.aggregatecount": "rating",
"core.rating.aggregatemax": "rating",
"core.rating.aggregatemin": "rating",
"core.rating.aggregatesum": "rating",
"core.rating.noratings": "rating",
"core.rating.rating": "rating",
"core.rating.ratings": "rating",
"core.redirectingtosite": "local_moodlemobileapp",
"core.refresh": "moodle",
"core.remove": "moodle",
@ -1612,6 +1661,7 @@
"core.resourcedisplayopen": "moodle",
"core.resources": "moodle",
"core.restore": "moodle",
"core.restricted": "moodle",
"core.retry": "local_moodlemobileapp",
"core.save": "moodle",
"core.search": "moodle",
@ -1648,6 +1698,7 @@
"core.settings.enablerichtexteditor": "local_moodlemobileapp",
"core.settings.enablerichtexteditordescription": "local_moodlemobileapp",
"core.settings.enablesyncwifi": "local_moodlemobileapp",
"core.settings.entriesincache": "local_moodlemobileapp",
"core.settings.errordeletesitefiles": "local_moodlemobileapp",
"core.settings.errorsyncsite": "local_moodlemobileapp",
"core.settings.estimatedfreespace": "local_moodlemobileapp",
@ -1698,6 +1749,7 @@
"core.sizemb": "moodle",
"core.sizetb": "local_moodlemobileapp",
"core.sorry": "local_moodlemobileapp",
"core.sort": "moodle",
"core.sortby": "moodle",
"core.start": "grouptool",
"core.strftimedate": "langconfig",

View File

@ -27,6 +27,10 @@ if [ ! -z $GIT_ORG_PRIVATE ] && [ ! -z $GIT_TOKEN ] ; then
mv *i386.AppImage linux-ia32.AppImage
mv Moodle*.AppImage linux-x64.AppImage
ls
tar -czvf MoodleDesktop32.tar.gz linux-ia32.AppImage
tar -czvf MoodleDesktop64.tar.gz linux-x64.AppImage
rm *.AppImage
git add .
git commit -m "Linux desktop versions from Travis build $TRAVIS_BUILD_NUMBER"

View File

@ -34,9 +34,11 @@ $config_langs = array_keys(get_object_vars($config['languages']));
// Set languages to do. If script is called using a language it will be used as unique.
if (isset($argv[1]) && !empty($argv[1])) {
$forcedetect = false;
define('TOTRANSLATE', true);
$languages = explode(',', $argv[1]);
} else {
$forcedetect = true;
define('TOTRANSLATE', false);
$languages = $config_langs;
}
@ -160,16 +162,19 @@ function build_lang($lang, $keys, $total) {
$file = LANGPACKSFOLDER.'/'.$langfoldername.'/'.$value->file.'.php';
// Apply translations.
if (!file_exists($file)) {
if (TOTRANSLATE) {
echo "\n\t\To translate $value->string on $value->file";
}
continue;
}
$string = [];
include($file);
if (!isset($string[$value->string])) {
if (!isset($string[$value->string]) || ($lang == 'en' && $value->file == 'local_moodlemobileapp')) {
// Not yet translated. Do not override.
if (!$langFile) {
// Load lang fils just once.
// Load lang files just once.
$langFile = file_get_contents(ASSETSPATH.$lang.'.json');
$langFile = (array) json_decode($langFile);
}
@ -177,6 +182,9 @@ function build_lang($lang, $keys, $total) {
$translations[$key] = $langFile[$key];
$local++;
}
if (TOTRANSLATE) {
echo "\n\t\tTo translate $value->string on $value->file";
}
continue;
} else {
$text = $string[$value->string];

View File

@ -17,8 +17,10 @@ import { AddonBadgesProvider } from './providers/badges';
import { AddonBadgesUserHandler } from './providers/user-handler';
import { AddonBadgesMyBadgesLinkHandler } from './providers/mybadges-link-handler';
import { AddonBadgesBadgeLinkHandler } from './providers/badge-link-handler';
import { AddonBadgesPushClickHandler } from './providers/push-click-handler';
import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
import { CoreUserDelegate } from '@core/user/providers/user-delegate';
import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate';
// List of providers (without handlers).
export const ADDON_BADGES_PROVIDERS: any[] = [
@ -34,16 +36,19 @@ export const ADDON_BADGES_PROVIDERS: any[] = [
AddonBadgesProvider,
AddonBadgesUserHandler,
AddonBadgesMyBadgesLinkHandler,
AddonBadgesBadgeLinkHandler
AddonBadgesBadgeLinkHandler,
AddonBadgesPushClickHandler
]
})
export class AddonBadgesModule {
constructor(userDelegate: CoreUserDelegate, userHandler: AddonBadgesUserHandler,
contentLinksDelegate: CoreContentLinksDelegate, myBadgesLinkHandler: AddonBadgesMyBadgesLinkHandler,
badgeLinkHandler: AddonBadgesBadgeLinkHandler) {
badgeLinkHandler: AddonBadgesBadgeLinkHandler,
pushNotificationsDelegate: CorePushNotificationsDelegate, pushClickHandler: AddonBadgesPushClickHandler) {
userDelegate.registerHandler(userHandler);
contentLinksDelegate.registerHandler(myBadgesLinkHandler);
contentLinksDelegate.registerHandler(badgeLinkHandler);
pushNotificationsDelegate.registerClickHandler(pushClickHandler);
}
}

View File

@ -15,6 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CoreSite } from '@classes/site';
/**
* Service to handle badges.
@ -79,11 +80,12 @@ export class AddonBadgesProvider {
courseid : courseId,
userid : userId
},
presets = {
cacheKey: this.getBadgesCacheKey(courseId, userId)
preSets = {
cacheKey: this.getBadgesCacheKey(courseId, userId),
updateFrequency: CoreSite.FREQUENCY_RARELY
};
return site.read('core_badges_get_user_badges', data, presets).then((response) => {
return site.read('core_badges_get_user_badges', data, preSets).then((response) => {
if (response && response.badges) {
return response.badges;
} else {

View File

@ -0,0 +1,71 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CorePushNotificationsClickHandler } from '@core/pushnotifications/providers/delegate';
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
import { AddonBadgesProvider } from './badges';
/**
* Handler for badges push notifications clicks.
*/
@Injectable()
export class AddonBadgesPushClickHandler implements CorePushNotificationsClickHandler {
name = 'AddonBadgesPushClickHandler';
priority = 200;
featureName = 'CoreUserDelegate_AddonBadges';
constructor(private utils: CoreUtilsProvider, private badgesProvider: AddonBadgesProvider,
private loginHelper: CoreLoginHelperProvider) {}
/**
* Check if a notification click is handled by this handler.
*
* @param {any} notification The notification to check.
* @return {boolean} Whether the notification click is handled by this handler
*/
handles(notification: any): boolean | Promise<boolean> {
const data = notification.customdata || {};
if (this.utils.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'moodle' &&
(notification.name == 'badgerecipientnotice' || (notification.name == 'badgecreatornotice' && data.hash))) {
return this.badgesProvider.isPluginEnabled(notification.site);
}
return false;
}
/**
* Handle the notification click.
*
* @param {any} notification The notification to check.
* @return {Promise<any>} Promise resolved when done.
*/
handleClick(notification: any): Promise<any> {
const data = notification.customdata || {};
if (data.hash) {
// We have the hash, open the badge directly.
return this.loginHelper.redirect('AddonBadgesIssuedBadgePage', {courseId: 0, badgeHash: data.hash}, notification.site);
}
// No hash, open the list of user badges.
return this.badgesProvider.invalidateUserBadges(0, Number(notification.usertoid), notification.site).catch(() => {
// Ignore errors.
}).then(() => {
return this.loginHelper.redirect('AddonBadgesUserBadgesPage', {}, notification.site);
});
}
}

View File

@ -1,7 +1,7 @@
<ion-item-divider>
<h2>{{ 'addon.block_myoverview.pluginname' | translate }}</h2>
<!-- Download all courses. -->
<div *ngIf="downloadEnabled && courses[selectedFilter] && courses[selectedFilter].length > 1 && !showFilter" class="core-button-spinner" item-end>
<div *ngIf="downloadCoursesEnabled && downloadEnabled && courses[selectedFilter] && courses[selectedFilter].length > 1 && !showFilter" class="core-button-spinner" item-end>
<button *ngIf="prefetchCoursesData[selectedFilter].icon && prefetchCoursesData[selectedFilter].icon != 'spinner'" ion-button icon-only clear color="dark" (click)="prefetchCourses()">
<core-icon [name]="prefetchCoursesData[selectedFilter].icon"></core-icon>
</button>
@ -36,7 +36,7 @@
<ion-grid no-padding>
<ion-row no-padding>
<ion-col *ngFor="let course of filteredCourses" no-padding col-12 col-sm-6 col-md-6 col-lg-4 col-xl-4 align-self-stretch>
<core-courses-course-progress [course]="course" class="core-courseoverview" showAll="true" [showDownload]="downloadEnabled"></core-courses-course-progress>
<core-courses-course-progress [course]="course" class="core-courseoverview" showAll="true" [showDownload]="downloadCourseEnabled && downloadEnabled"></core-courses-course-progress>
</ion-col>
</ion-row>
</ion-grid>

View File

@ -62,11 +62,14 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
showHidden = false;
showSelectorFilter = false;
showSortFilter = false;
downloadCourseEnabled: boolean;
downloadCoursesEnabled: boolean;
protected prefetchIconsInitialized = false;
protected isDestroyed;
protected downloadButtonObserver;
protected coursesObserver;
protected updateSiteObserver;
protected courseIds = [];
protected fetchContentDefaultError = 'Error getting my overview data.';
@ -96,6 +99,16 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
}
});
this.downloadCourseEnabled = !this.coursesProvider.isDownloadCourseDisabledInSite();
this.downloadCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite();
// Refresh the enabled flags if site is updated.
this.updateSiteObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, () => {
this.downloadCourseEnabled = !this.coursesProvider.isDownloadCourseDisabledInSite();
this.downloadCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite();
}, this.sitesProvider.getCurrentSiteId());
this.coursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, () => {
this.refreshContent();
}, this.sitesProvider.getCurrentSiteId());
@ -336,6 +349,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
ngOnDestroy(): void {
this.isDestroyed = true;
this.coursesObserver && this.coursesObserver.off();
this.updateSiteObserver && this.updateSiteObserver.off();
this.downloadButtonObserver && this.downloadButtonObserver.off();
}
}

View File

@ -75,7 +75,7 @@ export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent impl
*/
protected fetchContent(): Promise<any> {
return this.courseProvider.getSections(this.siteHomeId, false, true).then((sections) => {
this.block = sections[0];
this.block = sections.find((section) => section.section == 0);
if (this.block) {
this.block.hasContent = this.courseHelper.sectionHasContent(this.block);

View File

@ -14,6 +14,7 @@
import { Injectable } from '@angular/core';
import { CoreSitesProvider } from '@providers/sites';
import { CoreCoursesDashboardProvider } from '@core/courses/providers/dashboard';
import * as moment from 'moment';
/**
@ -26,7 +27,7 @@ export class AddonBlockTimelineProvider {
// Cache key was maintained when moving the functions to this file. It comes from core myoverview.
protected ROOT_CACHE_KEY = 'myoverview:';
constructor(private sitesProvider: CoreSitesProvider) { }
constructor(private sitesProvider: CoreSitesProvider, private dashboardProvider: CoreCoursesDashboardProvider) { }
/**
* Get calendar action events for the given course.
@ -218,6 +219,11 @@ export class AddonBlockTimelineProvider {
*/
isAvailable(siteId?: string): Promise<boolean> {
return this.sitesProvider.getSite(siteId).then((site) => {
// First check if dashboard is disabled.
if (this.dashboardProvider.isDisabledInSite(site)) {
return false;
}
return site.wsAvailable('core_calendar_get_action_events_by_courses') &&
site.wsAvailable('core_calendar_get_action_events_by_timesort');
});

View File

@ -29,7 +29,7 @@
</ion-item>
<ion-card-content>
<core-format-text [text]="entry.summary" [component]="this.component" [componentId]="entry.id"></core-format-text>
<ion-item>
<ion-item *ngIf="commentsEnabled">
<core-comments [component]="this.component" [itemId]="entry.id" area="format_blog" [instanceId]="entry.userid" contextLevel="user"></core-comments>
</ion-item>
<core-file *ngFor="let file of entry.attachmentfiles" [file]="file" [component]="this.component" [componentId]="entry.id"></core-file>

View File

@ -18,6 +18,7 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUserProvider } from '@core/user/providers/user';
import { AddonBlogProvider } from '../../providers/blog';
import { CoreCommentsProvider } from '@core/comments/providers/comments';
/**
* Component that displays the blog entries.
@ -47,9 +48,11 @@ export class AddonBlogEntriesComponent implements OnInit {
showMyIssuesToggle = false;
onlyMyEntries = false;
component = AddonBlogProvider.COMPONENT;
commentsEnabled: boolean;
constructor(protected blogProvider: AddonBlogProvider, protected domUtils: CoreDomUtilsProvider,
protected userProvider: CoreUserProvider, sitesProvider: CoreSitesProvider) {
protected userProvider: CoreUserProvider, sitesProvider: CoreSitesProvider,
protected commentsProvider: CoreCommentsProvider) {
this.currentUserId = sitesProvider.getCurrentSiteUserId();
}
@ -81,6 +84,8 @@ export class AddonBlogEntriesComponent implements OnInit {
this.filter['tagid'] = this.tagId;
}
this.commentsEnabled = !this.commentsProvider.areCommentsDisabledInSite();
this.fetchEntries().then(() => {
this.blogProvider.logView(this.filter).catch(() => {
// Ignore errors.

View File

@ -16,6 +16,8 @@ import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications';
import { CoreSite } from '@classes/site';
/**
* Service to handle blog entries.
@ -27,7 +29,8 @@ export class AddonBlogProvider {
protected ROOT_CACHE_KEY = 'addonBlog:';
protected logger;
constructor(logger: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, protected utils: CoreUtilsProvider) {
constructor(logger: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, protected utils: CoreUtilsProvider,
protected pushNotificationsProvider: CorePushNotificationsProvider) {
this.logger = logger.getInstance('AddonBlogProvider');
}
@ -74,7 +77,8 @@ export class AddonBlogProvider {
};
const preSets = {
cacheKey: this.getEntriesCacheKey(filter)
cacheKey: this.getEntriesCacheKey(filter),
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
};
return site.read('core_blog_get_entries', data, preSets);
@ -102,6 +106,8 @@ export class AddonBlogProvider {
* @return {Promise<any>} Promise to be resolved when done.
*/
logView(filter: any = {}, siteId?: string): Promise<any> {
this.pushNotificationsProvider.logViewListEvent('blog', 'core_blog_view_entries', filter, siteId);
return this.sitesProvider.getSite(siteId).then((site) => {
const data = {
filters: this.utils.objectToArrayOfObjects(filter, 'name', 'value')

View File

@ -24,7 +24,7 @@ import { AddonBlogProvider } from './blog';
@Injectable()
export class AddonBlogIndexLinkHandler extends CoreContentLinksHandlerBase {
name = 'AddonBlogIndexLinkHandler';
featureName = 'CoreUserDelegate_AddonBlog';
featureName = 'CoreUserDelegate_AddonBlog:blogs';
pattern = /\/blog\/index\.php/;
constructor(private blogProvider: AddonBlogProvider, private loginHelper: CoreLoginHelperProvider) {

View File

@ -57,7 +57,7 @@
<h2>{{ 'addon.calendar.reminders' | translate }}</h2>
</ion-item>
<ng-container *ngFor="let reminder of reminders">
<ion-item *ngIf="reminder.time > 0 || defaultTime > 0" [class.item-dimmed]="(reminder.time == -1 ? (event.timestart - defaultTime) : reminder.time) <= currentTime" >
<ion-item text-wrap *ngIf="reminder.time > 0 || defaultTime > 0" [class.item-dimmed]="(reminder.time == -1 ? (event.timestart - defaultTime) : reminder.time) <= currentTime" >
<p *ngIf="reminder.time == -1">{{ 'core.defaultvalue' | translate :{$a: ((event.timestart - defaultTime) * 1000) | coreFormatDate } }}</p>
<p *ngIf="reminder.time > 0">{{ reminder.time * 1000 | coreFormatDate }}</p>
<button ion-button icon-only clear="true" (click)="cancelNotification(reminder.id, $event)" [attr.aria-label]=" 'core.delete' | translate" item-end *ngIf="(reminder.time == -1 ? (event.timestart - defaultTime) : reminder.time) > currentTime">
@ -68,11 +68,11 @@
<ng-container *ngIf="event.timestart + event.timeduration > currentTime">
<ion-item>
<ion-label stacked>{{ 'addon.calendar.setnewreminder' | translate }}</ion-label>
<ion-label stacked><h2>{{ 'addon.calendar.setnewreminder' | translate }}</h2></ion-label>
<ion-datetime [(ngModel)]="notificationTimeText" [placeholder]="'core.choosedots' | translate" [displayFormat]="notificationFormat" [min]="notificationMin" [max]="notificationMax"></ion-datetime>
</ion-item>
<ion-item>
<button ion-button block color="primary" (click)="addNotificationTime($event)" [disabled]="!notificationTimeText">{{ 'addon.calendar.setnewreminder' | translate }}</button>
<button ion-button block color="primary" (click)="addNotificationTime($event)" [disabled]="!notificationTimeText">{{ 'core.save' | translate }}</button>
</ion-item>
</ng-container>
</ion-card>

View File

@ -292,7 +292,8 @@ export class AddonCalendarProvider {
getEvent(id: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
const preSets = {
cacheKey: this.getEventCacheKey(id)
cacheKey: this.getEventCacheKey(id),
updateFrequency: CoreSite.FREQUENCY_RARELY
},
data = {
options: {
@ -329,7 +330,8 @@ export class AddonCalendarProvider {
getEventById(id: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
const preSets = {
cacheKey: this.getEventCacheKey(id)
cacheKey: this.getEventCacheKey(id),
updateFrequency: CoreSite.FREQUENCY_RARELY
},
data = {
eventid: id
@ -469,7 +471,8 @@ export class AddonCalendarProvider {
// We need to retrieve cached data using cache key because we have timestamp in the params.
const preSets = {
cacheKey: this.getEventsListCacheKey(daysToStart, daysInterval),
getCacheUsingCacheKey: true
getCacheUsingCacheKey: true,
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
};
return site.read('core_calendar_get_calendar_events', data, preSets).then((response) => {
@ -646,6 +649,7 @@ export class AddonCalendarProvider {
id: reminderId,
title: event.name,
text: this.timeUtils.userDate(event.timestart * 1000, 'core.strftimedaydatetime', true),
icon: 'file://assets/img/icons/calendar.png',
trigger: {
at: new Date(time)
},

View File

@ -18,10 +18,17 @@ import { AddonCompetencyHelperProvider } from './providers/helper';
import { AddonCompetencyCourseOptionHandler } from './providers/course-option-handler';
import { AddonCompetencyMainMenuHandler } from './providers/mainmenu-handler';
import { AddonCompetencyUserHandler } from './providers/user-handler';
import { AddonCompetencyCompetencyLinkHandler } from './providers/competency-link-handler';
import { AddonCompetencyPlanLinkHandler } from './providers/plan-link-handler';
import { AddonCompetencyPlansLinkHandler } from './providers/plans-link-handler';
import { AddonCompetencyUserCompetencyLinkHandler } from './providers/user-competency-link-handler';
import { AddonCompetencyPushClickHandler } from './providers/push-click-handler';
import { AddonCompetencyComponentsModule } from './components/components.module';
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
import { CoreUserDelegate } from '@core/user/providers/user-delegate';
import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate';
// List of providers (without handlers).
export const ADDON_COMPETENCY_PROVIDERS: any[] = [
@ -40,16 +47,30 @@ export const ADDON_COMPETENCY_PROVIDERS: any[] = [
AddonCompetencyHelperProvider,
AddonCompetencyCourseOptionHandler,
AddonCompetencyMainMenuHandler,
AddonCompetencyUserHandler
AddonCompetencyUserHandler,
AddonCompetencyCompetencyLinkHandler,
AddonCompetencyPlanLinkHandler,
AddonCompetencyPlansLinkHandler,
AddonCompetencyUserCompetencyLinkHandler,
AddonCompetencyPushClickHandler
]
})
export class AddonCompetencyModule {
constructor(mainMenuDelegate: CoreMainMenuDelegate, mainMenuHandler: AddonCompetencyMainMenuHandler,
courseOptionsDelegate: CoreCourseOptionsDelegate, courseOptionHandler: AddonCompetencyCourseOptionHandler,
userDelegate: CoreUserDelegate, userHandler: AddonCompetencyUserHandler) {
userDelegate: CoreUserDelegate, userHandler: AddonCompetencyUserHandler,
contentLinksDelegate: CoreContentLinksDelegate, competencyLinkHandler: AddonCompetencyCompetencyLinkHandler,
planLinkHandler: AddonCompetencyPlanLinkHandler, plansLinkHandler: AddonCompetencyPlansLinkHandler,
userComptencyLinkHandler: AddonCompetencyUserCompetencyLinkHandler,
pushNotificationsDelegate: CorePushNotificationsDelegate, pushClickHandler: AddonCompetencyPushClickHandler) {
mainMenuDelegate.registerHandler(mainMenuHandler);
courseOptionsDelegate.registerHandler(courseOptionHandler);
userDelegate.registerHandler(userHandler);
contentLinksDelegate.registerHandler(competencyLinkHandler);
contentLinksDelegate.registerHandler(planLinkHandler);
contentLinksDelegate.registerHandler(plansLinkHandler);
contentLinksDelegate.registerHandler(userComptencyLinkHandler);
pushNotificationsDelegate.registerClickHandler(pushClickHandler);
}
}

View File

@ -4,18 +4,19 @@
</ion-refresher>
<core-loading [hideUntil]="competenciesLoaded">
<ion-card *ngIf="!user && competencies && competencies.statistics.competencycount > 0">
<ion-item text-wrap *ngIf="competencies.settings.pushratingstouserplans">
{{ 'addon.competency.coursecompetencyratingsarepushedtouserplans' | translate }}
</ion-item>
<ion-item text-wrap *ngIf="!competencies.settings.pushratingstouserplans">
{{ 'addon.competency.coursecompetencyratingsarenotpushedtouserplans' | translate }}
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.progress' | translate }}</strong>:
{{ 'addon.competency.xcompetenciesproficientoutofyincourse' | translate:{$a: {x: competencies.statistics.proficientcompetencycount, y: competencies.statistics.competencycount} } }} ({{ competencies.statistics.proficientcompetencypercentageformatted }}%)
<ng-container *ngIf="competencies.cangradecompetencies">
<ion-item text-wrap *ngIf="competencies.settings.pushratingstouserplans">
{{ 'addon.competency.coursecompetencyratingsarepushedtouserplans' | translate }}
</ion-item>
<ion-item text-wrap *ngIf="!competencies.settings.pushratingstouserplans" color="danger">
{{ 'addon.competency.coursecompetencyratingsarenotpushedtouserplans' | translate }}
</ion-item>
</ng-container>
<ion-item text-wrap *ngIf="competencies.statistics.canbegradedincourse">
{{ 'addon.competency.xcompetenciesproficientoutofyincourse' | translate:{$a: {x: competencies.statistics.proficientcompetencycount, y: competencies.statistics.competencycount} } }}
<core-progress-bar [progress]="competencies.statistics.proficientcompetencypercentage"></core-progress-bar>
</ion-item>
<ion-item text-wrap *ngIf="competencies.statistics.leastproficientcount > 0">
<ion-item text-wrap *ngIf="competencies.statistics.canmanagecoursecompetencies && competencies.statistics.leastproficientcount > 0">
<strong>{{ 'addon.competency.competenciesmostoftennotproficientincourse' | translate }}</strong>:
<p *ngFor="let comp of competencies.statistics.leastproficient">
<a (click)="openCompetencySummary(comp.id)">
@ -25,42 +26,63 @@
</ion-item>
</ion-card>
<h3 margin-horizontal *ngIf="competencies && competencies.statistics.competencycount > 0">{{ 'addon.competency.competencies' | translate }}</h3>
<h3 margin-horizontal *ngIf="competencies && competencies.statistics.competencycount > 0">{{ 'addon.competency.coursecompetencies' | translate }}</h3>
<ion-card *ngIf="user">
<ion-item text-wrap>
<ion-avatar core-user-avatar [user]="user" item-start></ion-avatar>
<h2><core-format-text [text]="user.fullname"></core-format-text></h2>
</ion-item>
</ion-card>
<core-empty-box *ngIf="competencies && competencies.statistics.competencycount == 0" icon="ribbon" message="{{ 'addon.competency.nocompetencies' | translate }}"></core-empty-box>
<core-empty-box *ngIf="competencies && competencies.statistics.competencycount == 0" icon="ribbon" message="{{ 'addon.competency.nocompetenciesincourse' | translate }}"></core-empty-box>
<div *ngIf="competencies">
<ion-card *ngFor="let competency of competencies.competencies">
<a ion-item text-wrap (click)="openCompetency(competency.competency.id)" [title]="competency.competency.shortname">
{{competency.competency.shortname}} <small>{{competency.competency.idnumber}}</small>
<ion-item text-wrap (click)="openCompetency(competency.competency.id)" [title]="competency.competency.shortname" detail-push>
<h2><strong>{{competency.competency.shortname}} <em>{{competency.competency.idnumber}}</em></strong></h2>
<ion-badge item-end *ngIf="competency.usercompetencycourse && competency.usercompetencycourse.gradename" [color]="competency.usercompetencycourse.proficiency ? 'success' : 'danger'">{{ competency.usercompetencycourse.gradename }}</ion-badge>
</a>
</ion-item>
<ion-item text-wrap>
<div *ngIf="competency.competency.description">
<p *ngIf="competency.competency.description">
<core-format-text [text]=" competency.competency.description "></core-format-text>
</div>
</p>
<div>
<strong>{{ 'addon.competency.path' | translate }}</strong>
{{ competency.comppath.framework.name }}
<a *ngIf="competency.comppath.showlinks" [href]="competency.comppath.pluginbaseurl + '/competencies.php?competencyframeworkid=' + competency.comppath.framework.id + '&pagecontextid=' + competency.comppath.pagecontextid" core-link [title]="competency.comppath.framework.name">{{ competency.comppath.framework.name }}</a>
<ng-container *ngIf="!competency.comppath.showlinks">{{ competency.comppath.framework.name }}</ng-container>
&nbsp;/&nbsp;
<span *ngFor="let ancestor of competency.comppath.ancestors">
&nbsp;/&nbsp;<a (click)="openCompetencySummary(ancestor.id)">{{ ancestor.name }}</a>
<a *ngIf="competency.comppath.showlinks" (click)="openCompetencySummary(ancestor.id)">{{ ancestor.name }}</a>
<ng-container *ngIf="!competency.comppath.showlinks">{{ ancestor.name }}</ng-container>
<ng-container *ngIf="!ancestor.last">&nbsp;/&nbsp;</ng-container>
</span>
</div>
<div *ngIf="competencies.statistics.canmanagecoursecompetencies">
<strong>{{ 'addon.competency.uponcoursecompletion' | translate }}</strong>
<ng-container *ngFor="let ruleoutcome of competency.ruleoutcomeoptions">
<span *ngIf="ruleoutcome.selected">
<core-format-text [text]="ruleoutcome.text"></core-format-text>
</span>
</ng-container>
</div>
<div>
<strong>{{ 'addon.competency.activities' | translate }}</strong>:
<span *ngIf="competency.coursemodules.length == 0">
<strong>{{ 'addon.competency.activities' | translate }}</strong>
<p *ngIf="competency.coursemodules.length == 0">
{{ 'addon.competency.noactivities' | translate }}
</span>
<a ion-item text-wrap *ngFor="let activity of competency.coursemodules" [href]="activity.url" [title]="activity.name" core-link capture="true">
</p>
<a ion-item text-wrap *ngFor="let activity of competency.coursemodules" [href]="activity.url" [title]="activity.name" core-link capture="true" class="core-course-module-handler item-media">
<img item-start [src]="activity.iconurl" core-external-content alt="" role="presentation" *ngIf="activity.iconurl" class="core-module-icon">
<core-format-text [text]="activity.name"></core-format-text>
</a>
</div>
<div *ngIf="competency.plans">
<strong>{{ 'addon.competency.userplans' | translate }}</strong>
<p *ngIf="competency.plans.length == 0">
{{ 'addon.competency.nouserplanswithcompetency' | translate }}
</p>
<a ion-item text-wrap *ngFor="let plan of competency.plans" [href]="plan.url" [title]="plan.name" core-link capture="true">
<core-format-text [text]="plan.name"></core-format-text>
</a>
</div>
</ion-item>
</ion-card>
</div>

View File

@ -23,9 +23,11 @@
"myplans": "My learning plans",
"noactivities": "No activities",
"nocompetencies": "No competencies",
"nocompetenciesincourse": "No competencies have been linked to this course.",
"nocrossreferencedcompetencies": "No other competencies have been cross-referenced to this competency.",
"noevidence": "No evidence",
"noplanswerecreated": "No learning plans were created.",
"nouserplanswithcompetency": "No learning plans contain this competency.",
"path": "Path:",
"planstatusactive": "Active",
"planstatuscomplete": "Complete",
@ -38,6 +40,7 @@
"reviewstatus": "Review status",
"status": "Status",
"template": "Learning plan template",
"uponcoursecompletion": "Upon course completion:",
"usercompetencystatus_idle": "Idle",
"usercompetencystatus_inreview": "In review",
"usercompetencystatus_waitingforreview": "Waiting for review",

View File

@ -11,7 +11,7 @@
<core-loading [hideUntil]="competenciesLoaded">
<ion-list>
<a ion-item text-wrap *ngFor="let competency of competencies" [title]="competency.competency.shortname" (click)="openCompetency(competency.competency.id)" [class.core-split-item-selected]="competency.competency.id == competencyId">
{{ competency.competency.shortname }} <small>{{competency.competency.idnumber}}</small>
<h2>{{ competency.competency.shortname }} <em>{{competency.competency.idnumber}}</em></h2>
<ion-badge item-end *ngIf="competency.usercompetency" [color]="competency.usercompetency.proficiency ? 'success' : 'danger'">{{ competency.usercompetency.gradename }}</ion-badge>
<ion-badge item-end *ngIf="competency.usercompetencycourse" [color]="competency.usercompetencycourse.proficiency ? 'success' : 'danger'">{{ competency.usercompetencycourse.gradename }}</ion-badge>
</a>

View File

@ -21,9 +21,13 @@
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.path' | translate }}</strong>
{{ competency.competency.comppath.framework.name }}
<a *ngIf="competency.competency.comppath.showlinks" [href]="competency.competency.comppath.pluginbaseurl + '/competencies.php?competencyframeworkid=' + competency.competency.comppath.framework.id + '&pagecontextid=' + competency.competency.comppath.pagecontextid" core-link [title]="competency.competency.comppath.framework.name">{{ competency.competency.comppath.framework.name }}</a>
<ng-container *ngIf="!competency.competency.comppath.showlinks">{{ competency.competency.comppath.framework.name }}</ng-container>
&nbsp;/&nbsp;
<span *ngFor="let ancestor of competency.competency.comppath.ancestors">
&nbsp;/&nbsp;<a (click)="openCompetencySummary(ancestor.id)">{{ ancestor.name }}</a>
<a *ngIf="competency.competency.comppath.showlinks" (click)="openCompetencySummary(ancestor.id)">{{ ancestor.name }}</a>
<ng-container *ngIf="!competency.competency.comppath.showlinks">{{ ancestor.name }}</ng-container>
<ng-container *ngIf="!ancestor.last">&nbsp;/&nbsp;</ng-container>
</span>
</ion-item>
<ion-item text-wrap>
@ -38,21 +42,21 @@
</div>
</ion-item>
<ion-item text-wrap *ngIf="coursemodules">
<strong>{{ 'addon.competency.activities' | translate }}</strong>:
<span *ngIf="coursemodules.length == 0">
<strong>{{ 'addon.competency.activities' | translate }}</strong>
<p *ngIf="coursemodules.length == 0">
{{ 'addon.competency.noactivities' | translate }}
</span>
</p>
<a ion-item text-wrap *ngFor="let activity of coursemodules" [href]="activity.url" [title]="activity.name" core-link capture="true">
<img item-start core-external-content [src]="activity.iconurl" alt="" role="presentation" *ngIf="activity.iconurl" class="core-module-icon">
<core-format-text [text]="activity.name"></core-format-text>
</a>
</ion-item>
<ion-item text-wrap *ngIf="competency.usercompetency.status">
<strong>{{ 'addon.competency.reviewstatus' | translate }}</strong>:
<strong>{{ 'addon.competency.reviewstatus' | translate }}</strong>
{{ competency.usercompetency.statusname }}
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.proficient' | translate }}</strong>:
<strong>{{ 'addon.competency.proficient' | translate }}</strong>
<ion-badge color="success" *ngIf="competency.usercompetency.proficiency">
{{ 'core.yes' | translate }}
</ion-badge>
@ -61,7 +65,7 @@
</ion-badge>
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.rating' | translate }}</strong>:
<strong>{{ 'addon.competency.rating' | translate }}</strong>
<ion-badge color="dark">{{ competency.usercompetency.gradename }}</ion-badge>
</ion-item>
</ion-card>

View File

@ -55,13 +55,16 @@ export class AddonCompetencyCompetencyPage {
*/
ionViewDidLoad(): void {
this.fetchCompetency().then(() => {
const name = this.competency && this.competency.competency && this.competency.competency.competency &&
this.competency.competency.competency.shortname;
if (this.planId) {
this.competencyProvider.logCompetencyInPlanView(this.planId, this.competencyId, this.planStatus, this.userId)
.catch(() => {
this.competencyProvider.logCompetencyInPlanView(this.planId, this.competencyId, this.planStatus, name,
this.userId).catch(() => {
// Ignore errors.
});
} else {
this.competencyProvider.logCompetencyInCourseView(this.courseId, this.competencyId, this.userId).catch(() => {
this.competencyProvider.logCompetencyInCourseView(this.courseId, this.competencyId, name, this.userId).catch(() => {
// Ignore errors.
});
}

View File

@ -41,7 +41,10 @@ export class AddonCompetencyCompetencySummaryPage {
*/
ionViewDidLoad(): void {
this.fetchCompetency().then(() => {
this.competencyProvider.logCompetencyView(this.competencyId).catch(() => {
const name = this.competency.competency && this.competency.competency.competency &&
this.competency.competency.competency.shortname;
this.competencyProvider.logCompetencyView(this.competencyId, name).catch(() => {
// Ignore errors.
});
}).finally(() => {

View File

@ -16,6 +16,9 @@
</ion-card>
<ion-card *ngIf="plan">
<ion-list>
<ion-item text-wrap *ngIf="plan.plan.description">
<core-format-text [text]="plan.plan.description"></core-format-text>
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.status' | translate }}</strong>:
{{ plan.plan.statusname }}
@ -36,13 +39,13 @@
</ion-list>
</ion-card>
<ion-card *ngIf="plan">
<ion-card-header text-wrap>{{ 'addon.competency.learningplancompetencies' | translate }}</ion-card-header>
<ion-card-header text-wrap><h2>{{ 'addon.competency.learningplancompetencies' | translate }}</h2></ion-card-header>
<ion-list>
<ion-item text-wrap *ngIf="plan.competencycount == 0">
{{ 'addon.competency.nocompetencies' | translate }}
</ion-item>
<a ion-item text-wrap *ngFor="let competency of plan.competencies" (click)="openCompetency(competency.competency.id)" [title]="competency.competency.shortname">
{{competency.competency.shortname}} <small>{{competency.competency.idnumber}}</small>
<h2>{{competency.competency.shortname}} <em>{{competency.competency.idnumber}}</em></h2>
<ion-badge item-end [color]="competency.usercompetency.proficiency ? 'success' : 'danger'">{{ competency.usercompetency.gradename }}</ion-badge>
</a>
</ion-list>

View File

@ -15,7 +15,7 @@
<a ion-item text-wrap *ngFor="let plan of plans" [title]="plan.name" (click)="openPlan(plan.id)" [class.core-split-item-selected]="plan.id == planId">
<h2>{{ plan.name }}</h2>
<p *ngIf="plan.duedate > 0">{{ 'addon.competency.duedate' | translate }}: {{ plan.duedate * 1000 | coreFormatDate :'strftimedatetimeshort' }}</p>
<ion-badge text-wrap [color]="plan.statuscolor">{{ plan.statusname }}</ion-badge>
<ion-badge item-end text-wrap [color]="plan.statuscolor">{{ plan.statusname }}</ion-badge>
</a>
</ion-list>
</core-loading>

View File

@ -0,0 +1,74 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
import { AddonCompetencyProvider } from './competency';
/**
* Handler to treat links to a competency in a plan or in a course.
*/
@Injectable()
export class AddonCompetencyCompetencyLinkHandler extends CoreContentLinksHandlerBase {
name = 'AddonCompetencyCompetencyLinkHandler';
pattern = /\/admin\/tool\/lp\/(user_competency_in_course|user_competency_in_plan)\.php/;
constructor(private linkHelper: CoreContentLinksHelperProvider, private competencyProvider: AddonCompetencyProvider) {
super();
}
/**
* Get the list of actions for a link (url).
*
* @param {string[]} siteIds List of sites the URL belongs to.
* @param {string} url The URL to treat.
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
* @return {CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
*/
getActions(siteIds: string[], url: string, params: any, courseId?: number):
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
courseId = courseId || params.courseid || params.cid;
return [{
action: (siteId, navCtrl?): void => {
this.linkHelper.goInSite(navCtrl, 'AddonCompetencyCompetencyPage', {
planId: params.planid,
competencyId: params.competencyid,
courseId: courseId,
userId: params.userid
}, siteId);
}
}];
}
/**
* Check if the handler is enabled for a certain site (site + user) and a URL.
* If not defined, defaults to true.
*
* @param {string} siteId The site ID.
* @param {string} url The URL to treat.
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
* @return {boolean|Promise<boolean>} Whether the handler is enabled for the URL and site.
*/
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
// Handler is disabled if all competency features are disabled.
return this.competencyProvider.allCompetenciesDisabled(siteId).then((disabled) => {
return !disabled;
});
}
}

View File

@ -15,6 +15,8 @@
import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications';
import { CoreSite } from '@classes/site';
/**
* Service to handle caompetency learning plans.
@ -38,10 +40,25 @@ export class AddonCompetencyProvider {
protected logger;
constructor(loggerProvider: CoreLoggerProvider, private sitesProvider: CoreSitesProvider) {
constructor(loggerProvider: CoreLoggerProvider, private sitesProvider: CoreSitesProvider,
protected pushNotificationsProvider: CorePushNotificationsProvider) {
this.logger = loggerProvider.getInstance('AddonCompetencyProvider');
}
/**
* Check if all competencies features are disabled.
*
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with boolean: whether all competency features are disabled.
*/
allCompetenciesDisabled(siteId?: string): Promise<boolean> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.isFeatureDisabled('CoreMainMenuDelegate_AddonCompetency') &&
site.isFeatureDisabled('CoreCourseOptionsDelegate_AddonCompetency') &&
site.isFeatureDisabled('CoreUserDelegate_AddonCompetency');
});
}
/**
* Get cache key for user learning plans data WS calls.
*
@ -140,7 +157,8 @@ export class AddonCompetencyProvider {
userid: userId
},
preSets = {
cacheKey: this.getLearningPlansCacheKey(userId)
cacheKey: this.getLearningPlansCacheKey(userId),
updateFrequency: CoreSite.FREQUENCY_RARELY
};
return site.read('tool_lp_data_for_plans_page', params, preSets).then((response) => {
@ -169,7 +187,8 @@ export class AddonCompetencyProvider {
planid: planId
},
preSets = {
cacheKey: this.getLearningPlanCacheKey(planId)
cacheKey: this.getLearningPlanCacheKey(planId),
updateFrequency: CoreSite.FREQUENCY_RARELY
};
return site.read('tool_lp_data_for_plan_page', params, preSets).then((response) => {
@ -200,7 +219,8 @@ export class AddonCompetencyProvider {
competencyid: competencyId
},
preSets = {
cacheKey: this.getCompetencyInPlanCacheKey(planId, competencyId)
cacheKey: this.getCompetencyInPlanCacheKey(planId, competencyId),
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
};
return site.read('tool_lp_data_for_user_competency_summary_in_plan', params, preSets).then((response) => {
@ -237,7 +257,8 @@ export class AddonCompetencyProvider {
userid: userId
},
preSets: any = {
cacheKey: this.getCompetencyInCourseCacheKey(courseId, competencyId, userId)
cacheKey: this.getCompetencyInCourseCacheKey(courseId, competencyId, userId),
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
};
if (ignoreCache) {
@ -275,7 +296,8 @@ export class AddonCompetencyProvider {
userid: userId
},
preSets: any = {
cacheKey: this.getCompetencySummaryCacheKey(competencyId, userId)
cacheKey: this.getCompetencySummaryCacheKey(competencyId, userId),
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
};
if (ignoreCache) {
@ -311,7 +333,8 @@ export class AddonCompetencyProvider {
courseid: courseId
},
preSets: any = {
cacheKey: this.getCourseCompetenciesCacheKey(courseId)
cacheKey: this.getCourseCompetenciesCacheKey(courseId),
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
};
if (ignoreCache) {
@ -457,13 +480,15 @@ export class AddonCompetencyProvider {
* @param {number} planId ID of the plan.
* @param {number} competencyId ID of the competency.
* @param {number} planStatus Current plan Status to decide what action should be logged.
* @param {string} [name] Name of the competency.
* @param {number} [userId] User ID. If not defined, current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the WS call is successful.
*/
logCompetencyInPlanView(planId: number, competencyId: number, planStatus: number, userId?: number, siteId?: string)
: Promise<any> {
logCompetencyInPlanView(planId: number, competencyId: number, planStatus: number, name?: string, userId?: number,
siteId?: string): Promise<any> {
if (planId && competencyId) {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -474,13 +499,17 @@ export class AddonCompetencyProvider {
},
preSets = {
typeExpected: 'boolean'
};
},
wsName = planStatus == AddonCompetencyProvider.STATUS_COMPLETE ?
'core_competency_user_competency_plan_viewed' : 'core_competency_user_competency_viewed_in_plan';
if (planStatus == AddonCompetencyProvider.STATUS_COMPLETE) {
return site.write('core_competency_user_competency_plan_viewed', params, preSets);
} else {
return site.write('core_competency_user_competency_viewed_in_plan', params, preSets);
}
this.pushNotificationsProvider.logViewEvent(competencyId, name, 'competency', wsName, {
planid: planId,
planstatus: planStatus,
userid: userId
}, siteId);
return site.write(wsName, params, preSets);
});
}
@ -492,11 +521,14 @@ export class AddonCompetencyProvider {
*
* @param {number} courseId ID of the course.
* @param {number} competencyId ID of the competency.
* @param {string} [name] Name of the competency.
* @param {number} [userId] User ID. If not defined, current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the WS call is successful.
*/
logCompetencyInCourseView(courseId: number, competencyId: number, userId?: number, siteId?: string): Promise<any> {
logCompetencyInCourseView(courseId: number, competencyId: number, name?: string, userId?: number, siteId?: string)
: Promise<any> {
if (courseId && competencyId) {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -509,8 +541,14 @@ export class AddonCompetencyProvider {
const preSets = {
typeExpected: 'boolean'
};
const wsName = 'core_competency_user_competency_viewed_in_course';
return site.write('core_competency_user_competency_viewed_in_course', params, preSets);
this.pushNotificationsProvider.logViewEvent(competencyId, name, 'competency', wsName, {
courseid: courseId,
userid: userId
}, siteId);
return site.write(wsName, params, preSets);
});
}
@ -521,10 +559,11 @@ export class AddonCompetencyProvider {
* Report the competency as being viewed.
*
* @param {number} competencyId ID of the competency.
* @param {string} [name] Name of the competency.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the WS call is successful.
*/
logCompetencyView(competencyId: number, siteId?: string): Promise<any> {
logCompetencyView(competencyId: number, name?: string, siteId?: string): Promise<any> {
if (competencyId) {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
@ -533,6 +572,9 @@ export class AddonCompetencyProvider {
const preSets = {
typeExpected: 'boolean'
};
const wsName = 'core_competency_competency_viewed';
this.pushNotificationsProvider.logViewEvent(competencyId, name, 'competency', wsName, {}, siteId);
return site.write('core_competency_competency_viewed', params, preSets);
});

View File

@ -0,0 +1,68 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
import { AddonCompetencyProvider } from './competency';
/**
* Handler to treat links to a plan.
*/
@Injectable()
export class AddonCompetencyPlanLinkHandler extends CoreContentLinksHandlerBase {
name = 'AddonCompetencyPlanLinkHandler';
pattern = /\/admin\/tool\/lp\/plan\.php.*([\?\&]id=\d+)/;
constructor(private linkHelper: CoreContentLinksHelperProvider, private competencyProvider: AddonCompetencyProvider) {
super();
}
/**
* Get the list of actions for a link (url).
*
* @param {string[]} siteIds List of sites the URL belongs to.
* @param {string} url The URL to treat.
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
* @return {CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
*/
getActions(siteIds: string[], url: string, params: any, courseId?: number):
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
return [{
action: (siteId, navCtrl?): void => {
this.linkHelper.goInSite(navCtrl, 'AddonCompetencyPlanPage', { planId: params.id }, siteId);
}
}];
}
/**
* Check if the handler is enabled for a certain site (site + user) and a URL.
* If not defined, defaults to true.
*
* @param {string} siteId The site ID.
* @param {string} url The URL to treat.
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
* @return {boolean|Promise<boolean>} Whether the handler is enabled for the URL and site.
*/
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
// Handler is disabled if all competency features are disabled.
return this.competencyProvider.allCompetenciesDisabled(siteId).then((disabled) => {
return !disabled;
});
}
}

View File

@ -0,0 +1,69 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
import { AddonCompetencyProvider } from './competency';
/**
* Handler to treat links to user plans.
*/
@Injectable()
export class AddonCompetencyPlansLinkHandler extends CoreContentLinksHandlerBase {
name = 'AddonCompetencyPlansLinkHandler';
pattern = /\/admin\/tool\/lp\/plans\.php/;
constructor(private loginHelper: CoreLoginHelperProvider, private competencyProvider: AddonCompetencyProvider) {
super();
}
/**
* Get the list of actions for a link (url).
*
* @param {string[]} siteIds List of sites the URL belongs to.
* @param {string} url The URL to treat.
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
* @return {CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
*/
getActions(siteIds: string[], url: string, params: any, courseId?: number):
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
return [{
action: (siteId, navCtrl?): void => {
// Always use redirect to make it the new history root (to avoid "loops" in history).
this.loginHelper.redirect('AddonCompetencyPlanListPage', { userId: params.userid }, siteId);
}
}];
}
/**
* Check if the handler is enabled for a certain site (site + user) and a URL.
* If not defined, defaults to true.
*
* @param {string} siteId The site ID.
* @param {string} url The URL to treat.
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
* @return {boolean|Promise<boolean>} Whether the handler is enabled for the URL and site.
*/
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
// Handler is disabled if all competency features are disabled.
return this.competencyProvider.allCompetenciesDisabled(siteId).then((disabled) => {
return !disabled;
});
}
}

Some files were not shown because too many files have changed in this diff Show More